22import logging as lg
33import sys
44import time
5+ from abc import ABC , abstractmethod
56from datetime import datetime
67from io import StringIO
78from logging import *
8- from typing import List , Callable , Optional , TypeVar , TYPE_CHECKING
9+ from typing import List , Callable , Optional , TypeVar , TYPE_CHECKING , Generic
910
1011from .time import format_duration
1112
1718
1819LOG_DEFAULT_FORMAT = '%(levelname)-5s %(asctime)-15s %(name)s:%(funcName)s:%(lineno)d - %(message)s'
1920T = TypeVar ("T" )
21+ THandler = TypeVar ("THandler" , bound = Handler )
2022
2123# Holds the log format that is configured by the user (using function `configure`), such
2224# that it can be reused in other places
@@ -142,18 +144,18 @@ def datetime_tag() -> str:
142144
143145_fileLoggerPaths : List [str ] = []
144146_isAtExitReportFileLoggerRegistered = False
145- _memoryLogStream : Optional [StringIO ] = None
146147
147148
148149def _at_exit_report_file_logger ():
149150 for path in _fileLoggerPaths :
150151 print (f"A log file was saved to { path } " )
151152
152153
153- def add_file_logger (path , register_atexit = True ):
154+ def add_file_logger (path , append = True , register_atexit = True ) -> FileHandler :
154155 global _isAtExitReportFileLoggerRegistered
155156 log .info (f"Logging to { path } ..." )
156- handler = FileHandler (path )
157+ mode = "a" if append else "w"
158+ handler = FileHandler (path , mode = mode )
157159 handler .setFormatter (Formatter (_logFormat ))
158160 Logger .root .addHandler (handler )
159161 _fileLoggerPaths .append (path )
@@ -163,22 +165,24 @@ def add_file_logger(path, register_atexit=True):
163165 return handler
164166
165167
166- def add_memory_logger () -> None :
168+ class MemoryStreamHandler (StreamHandler ):
169+ def __init__ (self , stream : StringIO ):
170+ super ().__init__ (stream )
171+
172+ def get_log (self ) -> str :
173+ stream : StringIO = self .stream
174+ return stream .getvalue ()
175+
176+
177+ def add_memory_logger () -> MemoryStreamHandler :
167178 """
168- Enables in-memory logging (if it is not already enabled) , i.e. all log statements are written to a memory buffer and can later be
169- read via function `get_memory_log()`
179+ Adds an in-memory logger , i.e. all log statements are written to a memory buffer which can be retrieved
180+ using the handler's `get_log` method.
170181 """
171- global _memoryLogStream
172- if _memoryLogStream is not None :
173- return
174- _memoryLogStream = StringIO ()
175- handler = StreamHandler (_memoryLogStream )
182+ handler = MemoryStreamHandler (StringIO ())
176183 handler .setFormatter (Formatter (_logFormat ))
177184 Logger .root .addHandler (handler )
178-
179-
180- def get_memory_log ():
181- return _memoryLogStream .getvalue ()
185+ return handler
182186
183187
184188class StopWatch :
@@ -334,29 +338,62 @@ def __enter__(self):
334338 return self
335339
336340
337- class FileLoggerContext :
341+ class LoggerContext ( Generic [ THandler ], ABC ) :
338342 """
339- A context handler to be used in conjunction with Python's `with` statement which enables file-based logging .
343+ Base class for context handlers to be used in conjunction with Python's `with` statement.
340344 """
341- def __init__ (self , path : str , enabled = True ):
345+
346+ def __init__ (self , enabled = True ):
342347 """
343- :param path: the path to the log file
344348 :param enabled: whether to actually perform any logging.
345349 This switch allows the with statement to be applied regardless of whether logging shall be enabled.
346350 """
347351 self .enabled = enabled
348- self .path = path
349352 self ._log_handler = None
350353
351- def __enter__ (self ):
354+ @abstractmethod
355+ def _create_log_handler (self ) -> THandler :
356+ pass
357+
358+ def __enter__ (self ) -> Optional [THandler ]:
352359 if self .enabled :
353- self ._log_handler = add_file_logger (self .path , register_atexit = False )
360+ self ._log_handler = self ._create_log_handler ()
361+ return self ._log_handler
354362
355363 def __exit__ (self , exc_type , exc_value , traceback ):
356364 if self ._log_handler is not None :
357365 remove_log_handler (self ._log_handler )
358366
359367
368+ class FileLoggerContext (LoggerContext [FileHandler ]):
369+ """
370+ A context handler to be used in conjunction with Python's `with` statement which enables file-based logging.
371+ """
372+
373+ def __init__ (self , path : str , append = True , enabled = True ):
374+ """
375+ :param path: the path to the log file
376+ :param append: whether to append in case the file already exists; if False, always create a new file.
377+ :param enabled: whether to actually perform any logging.
378+ This switch allows the with statement to be applied regardless of whether logging shall be enabled.
379+ """
380+ self .path = path
381+ self .append = append
382+ super ().__init__ (enabled = enabled )
383+
384+ def _create_log_handler (self ) -> FileHandler :
385+ return add_file_logger (self .path , append = self .append , register_atexit = False )
386+
387+
388+ class MemoryLoggerContext (LoggerContext [MemoryStreamHandler ]):
389+ """
390+ A context handler to be used in conjunction with Python's `with` statement which enables in-memory logging.
391+ """
392+
393+ def _create_log_handler (self ) -> MemoryStreamHandler :
394+ return add_memory_logger ()
395+
396+
360397class LoggingDisabledContext :
361398 """
362399 A context manager that will temporarily disable logging
0 commit comments