@@ -208,107 +208,138 @@ def emit(self, record: LogRecord) -> None:
208
208
209
209
210
210
class FileHandler (StreamHandler ):
211
- """File handler for working with log files off of the microcontroller (like an SD card).
212
- This handler implements a very simple log rotating system. If LogFileSizeLimit is set, the
213
- handler will check to see if the log file is larger than the given limit. If the log file is
214
- larger than the limit, it is renamed and a new file is started for log entries. The old log
215
- file is renamed with the OldFilePostfix variable appended to the name. If another file exsists
216
- with this old file name, it will be deleted. Because there are two log files. The max size of
217
- the log files is two times the limit set by LogFileSizeLimit.
211
+ """File handler for working with log files off of the microcontroller (like
212
+ an SD card)
218
213
219
214
:param str filename: The filename of the log file
220
215
:param str mode: Whether to write ('w') or append ('a'); default is to append
221
- :param int LogFileSizeLimit: The max allowable size of the log file in bytes.
222
- :param str OldFilePostfix: What to append to the filename for the old log file. Defaults
223
- to '_old'.
216
+ """
217
+
218
+ def __init__ (self , filename : str , mode : str = "a" ) -> None :
219
+ # pylint: disable=consider-using-with
220
+ if mode == "r" :
221
+ raise ValueError ("Can't write to a read only file" )
222
+ super ().__init__ (open (filename , mode = mode ))
223
+
224
+ def close (self ) -> None :
225
+ """Closes the file"""
226
+ self .stream .flush ()
227
+ self .stream .close ()
228
+
229
+ def format (self , record : LogRecord ) -> str :
230
+ """Generate a string to log
231
+
232
+ :param record: The record (message object) to be logged
233
+ """
234
+ return super ().format (record ) + "\r \n "
235
+
236
+ def emit (self , record : LogRecord ) -> None :
237
+ """Generate the message and write it to the file.
238
+
239
+ :param record: The record (message object) to be logged
240
+ """
241
+ self .stream .write (self .format (record ))
242
+
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.
224
264
"""
225
265
226
266
def __init__ (
227
267
self ,
228
268
filename : str ,
229
269
mode : str = "a" ,
230
- LogFileSizeLimit : int = None ,
231
- OldFilePostfix : str = "_old" ,
270
+ maxBytes : int = 0 ,
271
+ backupCount : int = 0 ,
232
272
) -> None :
233
- if mode == "r" :
234
- raise ValueError ("Can't write to a read only file" )
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" )
235
277
236
278
self ._LogFileName = filename
237
279
self ._WriteMode = mode
238
- self ._OldFilePostfix = OldFilePostfix
239
- self ._LogFileSizeLimit = LogFileSizeLimit
240
-
241
- # Here we are assuming that if there is a period in the filename, the stuff after the period
242
- # is the extension of the file. It is possible that is not the case, but probably unlikely.
243
- if "." in filename :
244
- [basefilename , extension ] = filename .rsplit ("." , 1 )
245
- self ._OldLogFileName = basefilename + self ._OldFilePostfix + "." + extension
246
- else :
247
- basefilename = filename
248
- self ._OldLogFileName = basefilename + self ._OldFilePostfix
249
-
250
- # pylint: disable=consider-using-with
251
- super ().__init__ (open (self ._LogFileName , mode = self ._WriteMode ))
252
- self .CheckLogSize ()
280
+ self ._maxBytes = maxBytes
281
+ self ._backupCount = backupCount
253
282
254
- def CheckLogSize (self ) -> None :
255
- """Check the size of the log file and rotate if needed."""
256
- if self ._LogFileSizeLimit is None :
257
- # No log limit set
258
- return
283
+ # Open the file and save the handle to self.stream
284
+ super ().__init__ (self ._LogFileName , mode = self ._WriteMode )
285
+ # TODO: What do we do if the log file already exsists?
259
286
287
+ def doRollover (self ) -> None :
288
+ """Roll over the log files. This should not need to be called directly"""
289
+ # At this point, we have already determined that we need to roll the log files.
260
290
# Close the log file. Probably needed if we want to delete/rename files.
261
291
self .close ()
262
292
263
- # Get the size of the log file.
293
+ for i in range (self ._backupCount , 0 , - 1 ):
294
+ CurrentFileName = self ._LogFileName + "." + str (i )
295
+ CurrentFileNamePlus = self ._LogFileName + "." + str (i + 1 )
296
+ try :
297
+ if i == self ._backupCount :
298
+ # This is the oldest log file. Delete this one.
299
+ os .remove (CurrentFileName )
300
+ else :
301
+ # Rename the current file to the next number in the sequence.
302
+ os .rename (CurrentFileName , CurrentFileNamePlus )
303
+ except OSError as e :
304
+ if e .args [0 ] == 2 :
305
+ # File does not exsist. This is okay.
306
+ pass
307
+ else :
308
+ raise e
309
+
310
+ # Rename the current log to the first backup
311
+ os .rename (self ._LogFileName , CurrentFileName )
312
+
313
+ # Reopen the file.
314
+ # pylint: disable=consider-using-with
315
+ self .stream = open (self ._LogFileName , mode = self ._WriteMode )
316
+
317
+ def GetLogSize (self ) -> int :
318
+ """Check the size of the log file."""
319
+ # TODO: Is this needed? I am catching the case where the file does not exsist,
320
+ # but I don't know if that is a realistic case anymore.
264
321
try :
322
+ self .stream .flush () # We need to call this or the file size is always zero.
265
323
LogFileSize = os .stat (self ._LogFileName )[6 ]
266
324
except OSError as e :
267
325
if e .args [0 ] == 2 :
268
326
# Log file does not exsist. This is okay.
269
327
LogFileSize = None
270
328
else :
271
329
raise e
272
-
273
- # Get the size of the old log file.
274
- try :
275
- OldLogFileSize = os .stat (self ._OldLogFileName )[6 ]
276
- except OSError as e :
277
- if e .args [0 ] == 2 :
278
- # Log file does not exsist. This is okay.
279
- OldLogFileSize = None
280
- else :
281
- raise e
282
-
283
- # This checks if the log file is too big. If so, it deletes the old log file and renames the
284
- # log file to the old log filename.
285
- if LogFileSize > self ._LogFileSizeLimit :
286
- if OldLogFileSize is not None :
287
- os .remove (self ._OldLogFileName )
288
- os .rename (self ._LogFileName , self ._OldLogFileName )
289
-
290
- # Reopen the file.
291
- # pylint: disable=consider-using-with
292
- self .stream = open (self ._LogFileName , mode = self ._WriteMode )
293
-
294
- def close (self ) -> None :
295
- """Closes the file"""
296
- self .stream .flush ()
297
- self .stream .close ()
298
-
299
- def format (self , record : LogRecord ) -> str :
300
- """Generate a string to log
301
-
302
- :param record: The record (message object) to be logged
303
- """
304
- return super ().format (record ) + "\r \n "
330
+ return LogFileSize
305
331
306
332
def emit (self , record : LogRecord ) -> None :
307
- """Generate the message and write it to the UART .
333
+ """Generate the message and write it to the file .
308
334
309
335
:param record: The record (message object) to be logged
310
336
"""
311
- self .CheckLogSize ()
337
+ if (
338
+ (self .GetLogSize () >= self ._maxBytes )
339
+ and (self ._maxBytes > 0 )
340
+ and (self ._backupCount > 0 )
341
+ ):
342
+ self .doRollover ()
312
343
self .stream .write (self .format (record ))
313
344
314
345
0 commit comments