@@ -208,107 +208,138 @@ def emit(self, record: LogRecord) -> None:
208208
209209
210210class 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)
218213
219214 :param str filename: The filename of the log file
220215 :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.
224264 """
225265
226266 def __init__ (
227267 self ,
228268 filename : str ,
229269 mode : str = "a" ,
230- LogFileSizeLimit : int = None ,
231- OldFilePostfix : str = "_old" ,
270+ maxBytes : int = 0 ,
271+ backupCount : int = 0 ,
232272 ) -> 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" )
235277
236278 self ._LogFileName = filename
237279 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
253282
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?
259286
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.
260290 # Close the log file. Probably needed if we want to delete/rename files.
261291 self .close ()
262292
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.
264321 try :
322+ self .stream .flush () # We need to call this or the file size is always zero.
265323 LogFileSize = os .stat (self ._LogFileName )[6 ]
266324 except OSError as e :
267325 if e .args [0 ] == 2 :
268326 # Log file does not exsist. This is okay.
269327 LogFileSize = None
270328 else :
271329 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
305331
306332 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 .
308334
309335 :param record: The record (message object) to be logged
310336 """
311- self .CheckLogSize ()
337+ if (
338+ (self .GetLogSize () >= self ._maxBytes )
339+ and (self ._maxBytes > 0 )
340+ and (self ._backupCount > 0 )
341+ ):
342+ self .doRollover ()
312343 self .stream .write (self .format (record ))
313344
314345
0 commit comments