Skip to content

Commit 767fda4

Browse files
committed
Update adafruit_logging.py
Update the code to make a subclass of filehandler that implements some of the RotatingFileHandler from: https://docs.python.org/3/library/logging.handlers.html#rotatingfilehandler
1 parent f517859 commit 767fda4

File tree

1 file changed

+101
-70
lines changed

1 file changed

+101
-70
lines changed

adafruit_logging.py

Lines changed: 101 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -208,107 +208,138 @@ def emit(self, record: LogRecord) -> None:
208208

209209

210210
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)
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

Comments
 (0)