44usage examples to initialise a logger:
55 ```python
66 # 1. initialise logger to stderr:
7- logger = getLogger('my_logger', level=logging.DEBUG, print_stream =sys.stderr)
7+ logger = getLogger('my_logger', level=logging.DEBUG, stream =sys.stderr)
88
99 # 2. initialise logger to file:
1010 logger = getLogger('my_logger', level=logging.DEBUG, filename='my_log.log')
1111
1212 # 3. initialise logger to both stderr and file:
13- logger = getLogger('my_logger', level=logging.DEBUG, print_stream =sys.stderr, filename='my_log .log')
13+ logger = getLogger('my_logger', level=logging.DEBUG, stream =sys.stderr, files={LogLevel.INFO: 'info .log'} )
1414 ```
1515
1616usage examples to log messages:
3535
3636from pp .pp import _json_default
3737
38+ class LogLevel :
39+ 'An enum type for log levels.'
40+ CRITICAL = logging .CRITICAL
41+ ERROR = logging .ERROR
42+ WARNING = logging .WARNING
43+ INFO = logging .INFO
44+ DEBUG = logging .DEBUG
45+ NOTSET = logging .NOTSET
46+
47+
3848LOG_ROOT_NAME = 'root'
3949
50+
4051class LogFormatter (logging .Formatter ):
4152 'Custom log formatter that formats log messages as JSON, aka "Structured Logging".'
4253
@@ -56,10 +67,7 @@ def format(self, record) -> str:
5667 {
5768 'timestamp' : datetime .now ().isoformat (),
5869 'msg' : record .msg ,
59- 'data' : {
60- ** ({'args' : args } if args else {}),
61- ** (kwargs if kwargs else {}),
62- }
70+ 'data' : {'args' : args } if args else {} | kwargs or {}
6371 },
6472 default = _json_default ,
6573 )
@@ -75,9 +83,10 @@ def _getLogger(name: str, level: int = logging.CRITICAL, handlers: list[logging.
7583 - the same log level is applied to all handlers.
7684 '''
7785
78- # create the logger
86+ # create the root logger
7987 logger = logging .getLogger (LOG_ROOT_NAME )
8088 logger .setLevel (level )
89+
8190 # close/remove any existing handlers
8291 while logger .handlers :
8392 for handler in logger .handlers :
@@ -96,7 +105,6 @@ def _getLogger(name: str, level: int = logging.CRITICAL, handlers: list[logging.
96105
97106 # add the new handlers
98107 for handler in handlers :
99- handler .setLevel (level )
100108 logger .addHandler (handler )
101109
102110 if logger .handlers :
@@ -106,44 +114,33 @@ def _getLogger(name: str, level: int = logging.CRITICAL, handlers: list[logging.
106114 return logger
107115
108116
109- @dataclass
110- class Logger :
111- '''
112- A class to create a logger with custom handlers and a custom formatter.
113- Logger(name, level, handlers, print_stream, filename)
114- '''
115- name : str
116- level : int = logging .CRITICAL
117- print_stream : io .TextIOBase = field (default = None )
118- filename : str = field (default = None )
119- handlers : list [logging .Handler ] = field (init = False , default_factory = list )
120-
121- def __post_init__ (self , filename : str = None ):
122- if self .print_stream :
123- self .handlers .append (logging .StreamHandler (self .print_stream ))
124- if self .filename :
125- self .handlers .append (logging .FileHandler (self .filename ))
126-
127- def getLogger (self ) -> logging .Logger :
128- return _getLogger (self .name , self .level , handlers = self .handlers )
129-
130- @property
131- def logger (self ) -> logging .Logger :
132- return self .getLogger ()
133-
134-
135- def getLogger (name : str , level : int = logging .CRITICAL , print_stream : io .TextIOBase = None , filename : str = None ) -> logging .Logger :
117+ def getLogger (
118+ name : str ,
119+ level : int = logging .INFO ,
120+ stream : io .TextIOBase = sys .stderr ,
121+ files : dict [LogLevel , str ] = dict (),
122+ ) -> logging .Logger :
136123 '''
137124 Creates a logger with the given name, level, and handlers.
138- - if `print_stream` is provided, the logger will output logs to it.
139- - if `filename` is provided, the logger will output logs to it.
140- - if both are provided, the logger will output logs to both.
141- - if neither are provided, the logger will not output any logs.
125+ - `name` is the name of the logger.
126+ - `stream` is the output stream for the logger (default is STDERR).
127+ - `files` is a dictionary of log levels and filenames for file handlers.
128+ - The keys are log levels (e.g., LogLevel.INFO, LogLevel.DEBUG).
129+ - The values are the filenames to log to at the corresponding level.
130+ - The file handlers will use `TimedRotatingFileHandler` to rotate logs at midnight and keep 7 backups.
131+ - `level` is the log level for the logger and all handlers (default is INFO).
142132 '''
143133 handlers = []
144- if print_stream :
145- handlers .append (logging .StreamHandler (print_stream ))
146- if filename :
147- handlers .append (logging .FileHandler (filename ))
134+ if stream :
135+ handler = logging .StreamHandler (stream )
136+ handler .setLevel (level )
137+ handlers .append (handler )
138+
139+ for flevel , filename in files .items ():
140+ handler = logging .handlers .TimedRotatingFileHandler (
141+ filename , when = 'midnight' , backupCount = 7 , encoding = 'utf-8' ,
142+ )
143+ handler .setLevel (flevel )
144+ handlers .append (handler )
148145
149146 return _getLogger (name , level , handlers )
0 commit comments