1
1
# SPDX-FileCopyrightText: 2019 Dave Astels for Adafruit Industries
2
+ # SPDX-FileCopyrightText: 2024 Pat Satyshur
2
3
#
3
4
# SPDX-License-Identifier: MIT
4
5
58
59
59
60
import time
60
61
import sys
62
+ import os
61
63
from collections import namedtuple
62
64
63
65
try :
66
+ # pylint: disable=deprecated-class
64
67
from typing import Optional , Hashable
65
68
from typing_extensions import Protocol
66
69
@@ -214,6 +217,8 @@ class FileHandler(StreamHandler):
214
217
215
218
def __init__ (self , filename : str , mode : str = "a" ) -> None :
216
219
# pylint: disable=consider-using-with
220
+ if mode == "r" :
221
+ raise ValueError ("Can't write to a read only file" )
217
222
super ().__init__ (open (filename , mode = mode ))
218
223
219
224
def close (self ) -> None :
@@ -229,13 +234,112 @@ def format(self, record: LogRecord) -> str:
229
234
return super ().format (record ) + "\r \n "
230
235
231
236
def emit (self , record : LogRecord ) -> None :
232
- """Generate the message and write it to the UART .
237
+ """Generate the message and write it to the file .
233
238
234
239
:param record: The record (message object) to be logged
235
240
"""
236
241
self .stream .write (self .format (record ))
237
242
238
243
244
+ class RotatingFileHandler (FileHandler ):
245
+ """File handler for writing log files to flash memory or external memory such as an SD card.
246
+ This handler implements a very simple log rotating system similar to the python function of the
247
+ same name (https://docs.python.org/3/library/logging.handlers.html#rotatingfilehandler)
248
+
249
+ If maxBytes is set, the handler will check to see if the log file is larger than the given
250
+ limit. If the log file is larger than the limit, it is renamed and a new file is started.
251
+ The old log file will be renamed with a numerical appendix '.1', '.2', etc... The variable
252
+ backupCount controls how many old log files to keep. For example, if the filename is 'log.txt'
253
+ and backupCount is 5, you will end up with six log files: 'log.txt', 'log.txt.1', 'log.txt.3',
254
+ up to 'log.txt.5' Therefore, the maximum amount of disk space the logs can use is
255
+ maxBytes*(backupCount+1).
256
+
257
+ If either maxBytes or backupCount is not set, or set to zero, the log rotation is disabled.
258
+ This will result in a single log file with a name `filename` that will grow without bound.
259
+
260
+ :param str filename: The filename of the log file
261
+ :param str mode: Whether to write ('w') or append ('a'); default is to append
262
+ :param int maxBytes: The max allowable size of the log file in bytes.
263
+ :param int backupCount: The number of old log files to keep.
264
+ """
265
+
266
+ def __init__ (
267
+ self ,
268
+ filename : str ,
269
+ mode : str = "a" ,
270
+ maxBytes : int = 0 ,
271
+ backupCount : int = 0 ,
272
+ ) -> None :
273
+ if maxBytes < 0 :
274
+ raise ValueError ("maxBytes must be a positive number" )
275
+ if backupCount < 0 :
276
+ raise ValueError ("backupCount must be a positive number" )
277
+
278
+ self ._LogFileName = filename
279
+ self ._WriteMode = mode
280
+ self ._maxBytes = maxBytes
281
+ self ._backupCount = backupCount
282
+
283
+ # Open the file and save the handle to self.stream
284
+ super ().__init__ (self ._LogFileName , mode = self ._WriteMode )
285
+
286
+ def doRollover (self ) -> None :
287
+ """Roll over the log files. This should not need to be called directly"""
288
+ # At this point, we have already determined that we need to roll the log files.
289
+ # Close the log file. Probably needed if we want to delete/rename files.
290
+ self .close ()
291
+
292
+ for i in range (self ._backupCount , 0 , - 1 ):
293
+ CurrentFileName = self ._LogFileName + "." + str (i )
294
+ CurrentFileNamePlus = self ._LogFileName + "." + str (i + 1 )
295
+ try :
296
+ if i == self ._backupCount :
297
+ # This is the oldest log file. Delete this one.
298
+ os .remove (CurrentFileName )
299
+ else :
300
+ # Rename the current file to the next number in the sequence.
301
+ os .rename (CurrentFileName , CurrentFileNamePlus )
302
+ except OSError as e :
303
+ if e .args [0 ] == 2 :
304
+ # File does not exsist. This is okay.
305
+ pass
306
+ else :
307
+ raise e
308
+
309
+ # Rename the current log to the first backup
310
+ os .rename (self ._LogFileName , CurrentFileName )
311
+
312
+ # Reopen the file.
313
+ # pylint: disable=consider-using-with
314
+ self .stream = open (self ._LogFileName , mode = self ._WriteMode )
315
+
316
+ def GetLogSize (self ) -> int :
317
+ """Check the size of the log file."""
318
+ try :
319
+ self .stream .flush () # We need to call this or the file size is always zero.
320
+ LogFileSize = os .stat (self ._LogFileName )[6 ]
321
+ except OSError as e :
322
+ if e .args [0 ] == 2 :
323
+ # Log file does not exsist. This is okay.
324
+ LogFileSize = None
325
+ else :
326
+ raise e
327
+ return LogFileSize
328
+
329
+ def emit (self , record : LogRecord ) -> None :
330
+ """Generate the message and write it to the file.
331
+
332
+ :param record: The record (message object) to be logged
333
+ """
334
+ if (
335
+ (self .GetLogSize () >= self ._maxBytes )
336
+ and (self ._maxBytes > 0 )
337
+ and (self ._backupCount > 0 )
338
+ ):
339
+ self .doRollover ()
340
+ self .stream .write (self .format (record ))
341
+
342
+
239
343
class NullHandler (Handler ):
240
344
"""Provide an empty log handler.
241
345
0 commit comments