EasyLoggerAJM 1.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- EasyLoggerAJM/__init__.py +7 -0
- EasyLoggerAJM/_version.py +1 -0
- EasyLoggerAJM/custom_loggers.py +58 -0
- EasyLoggerAJM/easy_logger.py +593 -0
- EasyLoggerAJM/filters.py +20 -0
- EasyLoggerAJM/formatters.py +50 -0
- easyloggerajm-1.5.dist-info/METADATA +19 -0
- easyloggerajm-1.5.dist-info/RECORD +11 -0
- easyloggerajm-1.5.dist-info/WHEEL +5 -0
- easyloggerajm-1.5.dist-info/licenses/LICENSE.txt +17 -0
- easyloggerajm-1.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from EasyLoggerAJM.custom_loggers import _EasyLoggerCustomLogger
|
|
2
|
+
from EasyLoggerAJM.formatters import ColorizedFormatter, NO_COLORIZER
|
|
3
|
+
from EasyLoggerAJM.filters import ConsoleOneTimeFilter
|
|
4
|
+
from EasyLoggerAJM.easy_logger import EasyLogger
|
|
5
|
+
|
|
6
|
+
__all__ = ['_EasyLoggerCustomLogger', 'ColorizedFormatter',
|
|
7
|
+
'ConsoleOneTimeFilter', 'EasyLogger', 'NO_COLORIZER']
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '1.5'
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from logging import Logger
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class _EasyLoggerCustomLogger(Logger):
|
|
5
|
+
"""
|
|
6
|
+
This class defines a custom logger that extends the logging.Logger class.
|
|
7
|
+
It includes methods for logging at different levels such as info, warning, error, debug, and critical.
|
|
8
|
+
Additionally, there is a private static method _print_msg that can be used to print a log message
|
|
9
|
+
based on the provided kwargs. Each logging method in this class calls _print_msg before delegating
|
|
10
|
+
the actual logging to the corresponding method in the parent class.
|
|
11
|
+
The logging methods accept parameters for the log message, additional arguments,
|
|
12
|
+
exception information, stack information, stack level, and extra information.
|
|
13
|
+
Additional keyword arguments can be provided to control printing behavior.
|
|
14
|
+
"""
|
|
15
|
+
@staticmethod
|
|
16
|
+
def _print_msg(msg, **kwargs):
|
|
17
|
+
if kwargs.get('print_msg', False):
|
|
18
|
+
print(msg)
|
|
19
|
+
|
|
20
|
+
def info(self, msg: object, *args: object, exc_info=None,
|
|
21
|
+
stack_info: bool = False, stacklevel: int = 1,
|
|
22
|
+
extra=None, **kwargs):
|
|
23
|
+
self._print_msg(msg, print_msg=kwargs.get('print_msg', False))
|
|
24
|
+
super().info(msg, *args, exc_info=exc_info,
|
|
25
|
+
stack_info=stack_info, stacklevel=stacklevel,
|
|
26
|
+
extra=extra)
|
|
27
|
+
|
|
28
|
+
def warning(self, msg: object, *args: object, exc_info=None,
|
|
29
|
+
stack_info: bool = False, stacklevel: int = 1,
|
|
30
|
+
extra=None, **kwargs):
|
|
31
|
+
self._print_msg(msg, print_msg=kwargs.get('print_msg', False))
|
|
32
|
+
super().warning(msg, *args, exc_info=exc_info,
|
|
33
|
+
stack_info=stack_info, stacklevel=stacklevel,
|
|
34
|
+
extra=extra)
|
|
35
|
+
|
|
36
|
+
def error(self, msg: object, *args: object, exc_info=None,
|
|
37
|
+
stack_info: bool = False, stacklevel: int = 1,
|
|
38
|
+
extra=None, **kwargs):
|
|
39
|
+
self._print_msg(msg, print_msg=kwargs.get('print_msg', False))
|
|
40
|
+
super().error(msg, *args, exc_info=exc_info,
|
|
41
|
+
stack_info=stack_info, stacklevel=stacklevel,
|
|
42
|
+
extra=extra)
|
|
43
|
+
|
|
44
|
+
def debug(self, msg: object, *args: object, exc_info=None,
|
|
45
|
+
stack_info: bool = False, stacklevel: int = 1,
|
|
46
|
+
extra=None, **kwargs):
|
|
47
|
+
self._print_msg(msg, print_msg=kwargs.get('print_msg', False))
|
|
48
|
+
super().debug(msg, *args, exc_info=exc_info,
|
|
49
|
+
stack_info=stack_info, stacklevel=stacklevel,
|
|
50
|
+
extra=extra)
|
|
51
|
+
|
|
52
|
+
def critical(self, msg: object, *args: object, exc_info=None,
|
|
53
|
+
stack_info: bool = False, stacklevel: int = 1,
|
|
54
|
+
extra=None, **kwargs):
|
|
55
|
+
self._print_msg(msg, print_msg=kwargs.get('print_msg', False))
|
|
56
|
+
super().critical(msg, *args, exc_info=exc_info,
|
|
57
|
+
stack_info=stack_info, stacklevel=stacklevel,
|
|
58
|
+
extra=extra)
|
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
"""
|
|
2
|
+
easy_logger.py
|
|
3
|
+
|
|
4
|
+
logger with already set up generalized file handlers
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
import logging
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Union, List
|
|
11
|
+
|
|
12
|
+
from EasyLoggerAJM import ConsoleOneTimeFilter
|
|
13
|
+
from EasyLoggerAJM import _EasyLoggerCustomLogger
|
|
14
|
+
from EasyLoggerAJM import ColorizedFormatter, NO_COLORIZER
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _LogSpec:
|
|
18
|
+
"""
|
|
19
|
+
Class `_LogSpec` is a container for predefined log specifications and timestamp formats,
|
|
20
|
+
organized by time intervals (daily, hourly, and minute-based).
|
|
21
|
+
These specifications are auto-generated based on the current date and time.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
MINUTE_LOG_SPEC_FORMAT : tuple
|
|
25
|
+
A tuple containing the current date (ISO formatted) and time up to minutes in string format without colons.
|
|
26
|
+
MINUTE_TIMESTAMP : str
|
|
27
|
+
A compact ISO formatted timestamp up to minutes excluding colons.
|
|
28
|
+
|
|
29
|
+
HOUR_LOG_SPEC_FORMAT : tuple
|
|
30
|
+
A tuple containing the current date (ISO formatted) and time truncated to the hour in string format
|
|
31
|
+
(e.g., "1400" for 2 PM).
|
|
32
|
+
HOUR_TIMESTAMP : str
|
|
33
|
+
A compact ISO formatted timestamp indicating the hour without colons.
|
|
34
|
+
|
|
35
|
+
DAILY_LOG_SPEC_FORMAT : str
|
|
36
|
+
The current date as an ISO formatted string.
|
|
37
|
+
DAILY_TIMESTAMP : str
|
|
38
|
+
The current date truncated to the hour component formatted in ISO standard.
|
|
39
|
+
|
|
40
|
+
LOG_SPECS : dict
|
|
41
|
+
A dictionary containing log specifications for daily, hourly, and minute time intervals.
|
|
42
|
+
Each key corresponds to the time interval ('daily', 'hourly', 'minute') and its value is another dictionary with:
|
|
43
|
+
- 'name': Name of the log interval.
|
|
44
|
+
- 'format': Predefined time format of the given interval.
|
|
45
|
+
- 'timestamp': Compact timestamp matching the logical interval.
|
|
46
|
+
"""
|
|
47
|
+
# this is a tuple of the date and the time down to the minute
|
|
48
|
+
MINUTE_LOG_SPEC_FORMAT = (datetime.now().date().isoformat(),
|
|
49
|
+
''.join(datetime.now().time().isoformat().split('.')[0].split(":")[:-1]))
|
|
50
|
+
MINUTE_TIMESTAMP = datetime.now().isoformat(timespec='minutes').replace(':', '')
|
|
51
|
+
|
|
52
|
+
HOUR_LOG_SPEC_FORMAT = datetime.now().date().isoformat(), (
|
|
53
|
+
datetime.now().time().isoformat().split('.')[0].split(':')[0] + '00')
|
|
54
|
+
HOUR_TIMESTAMP = datetime.now().time().isoformat().split('.')[0].split(':')[0] + '00'
|
|
55
|
+
|
|
56
|
+
DAILY_LOG_SPEC_FORMAT = datetime.now().date().isoformat()
|
|
57
|
+
DAILY_TIMESTAMP = datetime.now().isoformat(timespec='hours').split('T')[0]
|
|
58
|
+
|
|
59
|
+
LOG_SPECS = {
|
|
60
|
+
'daily': {
|
|
61
|
+
'name': 'daily',
|
|
62
|
+
'format': DAILY_LOG_SPEC_FORMAT,
|
|
63
|
+
'timestamp': DAILY_TIMESTAMP
|
|
64
|
+
},
|
|
65
|
+
'hourly': {
|
|
66
|
+
'name': 'hourly',
|
|
67
|
+
'format': HOUR_LOG_SPEC_FORMAT,
|
|
68
|
+
'timestamp': HOUR_TIMESTAMP
|
|
69
|
+
},
|
|
70
|
+
'minute': {
|
|
71
|
+
'name': 'minute',
|
|
72
|
+
'format': MINUTE_LOG_SPEC_FORMAT,
|
|
73
|
+
'timestamp': MINUTE_TIMESTAMP
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# noinspection PyUnresolvedReferences
|
|
79
|
+
class _InternalLoggerMethods:
|
|
80
|
+
"""
|
|
81
|
+
This class contains internal utility methods for configuring and logging internal
|
|
82
|
+
operations of the logger. These methods are designed for internal use and handle
|
|
83
|
+
logging of initial attributes, setting up file and stream handlers, and initializing
|
|
84
|
+
the internal logger.
|
|
85
|
+
|
|
86
|
+
Methods
|
|
87
|
+
-------
|
|
88
|
+
_log_attributes_internal(logger_kwargs)
|
|
89
|
+
Logs the initial state of key instance attributes and any additional keyword
|
|
90
|
+
arguments passed during initialization.
|
|
91
|
+
|
|
92
|
+
_setup_internal_logger_handlers(verbose=False)
|
|
93
|
+
Sets up handlers for the internal logger, including a file handler to log into
|
|
94
|
+
a predefined file and, optionally, a stream handler for console output.
|
|
95
|
+
|
|
96
|
+
_setup_internal_logger(**kwargs)
|
|
97
|
+
Initializes and configures the internal logger with a designated logging level
|
|
98
|
+
and handlers. Returns the initialized logger.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
def _log_attributes_internal(self, logger_kwargs):
|
|
102
|
+
"""
|
|
103
|
+
Logs internal attributes and initialization parameters for debugging purposes.
|
|
104
|
+
|
|
105
|
+
:param logger_kwargs: Arguments passed during the initialization of the instance.
|
|
106
|
+
:type logger_kwargs: dict
|
|
107
|
+
"""
|
|
108
|
+
self._internal_logger.info(f"root_log_location set to {self._root_log_location}")
|
|
109
|
+
self._internal_logger.info(f"chosen_format set to {self._chosen_format}")
|
|
110
|
+
self._internal_logger.info(f"no_stream_color set to {self._no_stream_color}")
|
|
111
|
+
self._internal_logger.info(f"kwargs passed to __init__ are {logger_kwargs}")
|
|
112
|
+
|
|
113
|
+
def _setup_internal_logger_handlers(self, verbose=False):
|
|
114
|
+
"""
|
|
115
|
+
:param verbose: Indicates whether to enable verbose logging. If True, adds a StreamHandler to log messages to the console.
|
|
116
|
+
:type verbose: bool
|
|
117
|
+
:return: None
|
|
118
|
+
:rtype: None
|
|
119
|
+
"""
|
|
120
|
+
log_file_path = Path(self._root_log_location,
|
|
121
|
+
'EasyLogger_internal.log'.replace('\\', '/'))
|
|
122
|
+
fmt = logging.Formatter(self._chosen_format)
|
|
123
|
+
|
|
124
|
+
log_file_mode = 'w'
|
|
125
|
+
if not log_file_path.exists():
|
|
126
|
+
Path(self._root_log_location).mkdir(parents=True, exist_ok=True)
|
|
127
|
+
h = logging.FileHandler(log_file_path, mode=log_file_mode)
|
|
128
|
+
h.setFormatter(fmt)
|
|
129
|
+
self._internal_logger.addHandler(h)
|
|
130
|
+
|
|
131
|
+
if verbose:
|
|
132
|
+
h2 = logging.StreamHandler()
|
|
133
|
+
h2.setFormatter(fmt)
|
|
134
|
+
self._internal_logger.addHandler(h2)
|
|
135
|
+
|
|
136
|
+
def _setup_internal_logger(self, **kwargs):
|
|
137
|
+
"""
|
|
138
|
+
Sets up the internal logger for the application.
|
|
139
|
+
|
|
140
|
+
:param kwargs: Optional keyword arguments for configuring the logger.
|
|
141
|
+
The key 'verbose' can be used to enable or disable verbose logging.
|
|
142
|
+
:return: The configured internal logger instance.
|
|
143
|
+
:rtype: logging.Logger
|
|
144
|
+
"""
|
|
145
|
+
self._internal_logger = logging.getLogger('EasyLogger_internal')
|
|
146
|
+
self._internal_logger.propagate = False
|
|
147
|
+
self._internal_logger.setLevel(10)
|
|
148
|
+
self._setup_internal_logger_handlers(verbose=kwargs.get('verbose', False))
|
|
149
|
+
|
|
150
|
+
self._internal_logger.info("internal logger initialized")
|
|
151
|
+
return self._internal_logger
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class EasyLogger(_LogSpec, _InternalLoggerMethods):
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
EasyLogger
|
|
158
|
+
==========
|
|
159
|
+
|
|
160
|
+
Class to provide an easy logging mechanism for projects.
|
|
161
|
+
|
|
162
|
+
Attributes:
|
|
163
|
+
-----------
|
|
164
|
+
DEFAULT_FORMAT : str
|
|
165
|
+
Default log format used in the absence of a specified format.
|
|
166
|
+
|
|
167
|
+
INT_TO_STR_LOGGER_LEVELS : dict
|
|
168
|
+
Mapping of integer logger levels to their string representations.
|
|
169
|
+
|
|
170
|
+
STR_TO_INT_LOGGER_LEVELS : dict
|
|
171
|
+
Mapping of string logger levels to their integer representations.
|
|
172
|
+
|
|
173
|
+
MINUTE_LOG_SPEC_FORMAT : tuple
|
|
174
|
+
Tuple representing the log specification format at minute granularity.
|
|
175
|
+
|
|
176
|
+
MINUTE_TIMESTAMP : str
|
|
177
|
+
Timestamp at minute granularity.
|
|
178
|
+
|
|
179
|
+
HOUR_LOG_SPEC_FORMAT : tuple
|
|
180
|
+
Tuple representing the log specification format at hour granularity.
|
|
181
|
+
|
|
182
|
+
HOUR_TIMESTAMP : str
|
|
183
|
+
Timestamp at hour granularity.
|
|
184
|
+
|
|
185
|
+
DAILY_LOG_SPEC_FORMAT : str
|
|
186
|
+
String representing the log specification format at daily granularity.
|
|
187
|
+
|
|
188
|
+
DAILY_TIMESTAMP : str
|
|
189
|
+
Timestamp at daily granularity.
|
|
190
|
+
|
|
191
|
+
LOG_SPECS : dict
|
|
192
|
+
Dictionary containing predefined logging specifications.
|
|
193
|
+
|
|
194
|
+
Methods:
|
|
195
|
+
--------
|
|
196
|
+
__init__(self, project_name=None, root_log_location="../logs", chosen_format=DEFAULT_FORMAT, logger=None, **kwargs)
|
|
197
|
+
Initialize EasyLogger instance with provided parameters.
|
|
198
|
+
|
|
199
|
+
file_logger_levels(self)
|
|
200
|
+
Property to handle file logger levels.
|
|
201
|
+
|
|
202
|
+
project_name(self)
|
|
203
|
+
Property method to get the project name.
|
|
204
|
+
|
|
205
|
+
inner_log_fstructure(self)
|
|
206
|
+
Get the inner log file structure.
|
|
207
|
+
|
|
208
|
+
log_location(self)
|
|
209
|
+
Get the log location for file handling.
|
|
210
|
+
|
|
211
|
+
log_spec(self)
|
|
212
|
+
Handle logging specifications.
|
|
213
|
+
|
|
214
|
+
classmethod UseLogger(cls, **kwargs)
|
|
215
|
+
Instantiate a class with a specified logger.
|
|
216
|
+
|
|
217
|
+
Note:
|
|
218
|
+
-----
|
|
219
|
+
The EasyLogger class provides easy logging functionality for projects,
|
|
220
|
+
allowing customization of log formats and levels.
|
|
221
|
+
|
|
222
|
+
"""
|
|
223
|
+
DEFAULT_FORMAT = '%(asctime)s | %(name)s | %(levelname)s | %(message)s'
|
|
224
|
+
|
|
225
|
+
# TODO: replace these with checks using logging.getlevelname()
|
|
226
|
+
INT_TO_STR_LOGGER_LEVELS = {
|
|
227
|
+
10: 'DEBUG',
|
|
228
|
+
20: 'INFO',
|
|
229
|
+
30: 'WARNING',
|
|
230
|
+
40: 'ERROR',
|
|
231
|
+
50: 'CRITICAL'
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
STR_TO_INT_LOGGER_LEVELS = {
|
|
235
|
+
'DEBUG': 10,
|
|
236
|
+
'INFO': 20,
|
|
237
|
+
'WARNING': 30,
|
|
238
|
+
'ERROR': 40,
|
|
239
|
+
'CRITICAL': 50
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
def __init__(self, project_name=None, root_log_location="../logs",
|
|
243
|
+
chosen_format=DEFAULT_FORMAT, logger=None, **kwargs):
|
|
244
|
+
self._chosen_format = chosen_format
|
|
245
|
+
self._no_stream_color = kwargs.get('no_stream_color', False)
|
|
246
|
+
self._root_log_location = root_log_location
|
|
247
|
+
|
|
248
|
+
self._internal_logger = self._setup_internal_logger(verbose=kwargs.get('internal_verbose', False))
|
|
249
|
+
|
|
250
|
+
self._log_attributes_internal(kwargs)
|
|
251
|
+
|
|
252
|
+
# properties
|
|
253
|
+
self._file_logger_levels = kwargs.get('file_logger_levels', [])
|
|
254
|
+
self._project_name = project_name
|
|
255
|
+
self._inner_log_fstructure = None
|
|
256
|
+
self._log_location = None
|
|
257
|
+
self._log_spec = kwargs.get('log_spec', None)
|
|
258
|
+
|
|
259
|
+
self.show_warning_logs_in_console = kwargs.get('show_warning_logs_in_console', False)
|
|
260
|
+
self._internal_logger.info(f'show_warning_logs_in_console set to {self.show_warning_logs_in_console}')
|
|
261
|
+
|
|
262
|
+
self.timestamp = kwargs.get('timestamp', self.log_spec['timestamp'])
|
|
263
|
+
if self.timestamp != self.log_spec['timestamp']:
|
|
264
|
+
self.timestamp = self.set_timestamp(**{'timestamp': self.timestamp})
|
|
265
|
+
|
|
266
|
+
self.formatter, self.stream_formatter = self._setup_formatters(**kwargs)
|
|
267
|
+
|
|
268
|
+
self.logger = self.initialize_logger(logger=logger)
|
|
269
|
+
|
|
270
|
+
self.make_file_handlers()
|
|
271
|
+
|
|
272
|
+
if self.show_warning_logs_in_console:
|
|
273
|
+
self._internal_logger.info('warning logs will be printed to console - creating stream handler')
|
|
274
|
+
self.create_stream_handler()
|
|
275
|
+
|
|
276
|
+
self.post_handler_setup()
|
|
277
|
+
|
|
278
|
+
@staticmethod
|
|
279
|
+
def _get_level_handler_string(handlers: List[logging.Handler]) -> str:
|
|
280
|
+
return ', '.join([' - '.join((x.__class__.__name__, logging.getLevelName(x.level)))
|
|
281
|
+
for x in handlers])
|
|
282
|
+
|
|
283
|
+
@classmethod
|
|
284
|
+
def UseLogger(cls, **kwargs):
|
|
285
|
+
"""
|
|
286
|
+
This method is a class method that can be used to instantiate a class with a logger.
|
|
287
|
+
It takes in keyword arguments and returns an instance of the class with the specified logger.
|
|
288
|
+
|
|
289
|
+
Parameters:
|
|
290
|
+
- **kwargs: Keyword arguments that are used to instantiate the class.
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
- An instance of the class with the specified logger.
|
|
294
|
+
|
|
295
|
+
Usage:
|
|
296
|
+
MyClass.UseLogger(arg1=value1, arg2=value2)
|
|
297
|
+
|
|
298
|
+
Note:
|
|
299
|
+
The logger used for instantiation is obtained from the `logging` module and is named 'logger'.
|
|
300
|
+
"""
|
|
301
|
+
return cls(**kwargs, logger=kwargs.get('logger', None)).logger
|
|
302
|
+
|
|
303
|
+
@property
|
|
304
|
+
def file_logger_levels(self):
|
|
305
|
+
if self._file_logger_levels:
|
|
306
|
+
if [x for x in self._file_logger_levels
|
|
307
|
+
if x in self.__class__.STR_TO_INT_LOGGER_LEVELS
|
|
308
|
+
or x in self.__class__.INT_TO_STR_LOGGER_LEVELS]:
|
|
309
|
+
if any([isinstance(x, str) and not x.isdigit() for x in self._file_logger_levels]):
|
|
310
|
+
self._file_logger_levels = [self.__class__.STR_TO_INT_LOGGER_LEVELS[x] for x in
|
|
311
|
+
self._file_logger_levels]
|
|
312
|
+
elif any([isinstance(x, int) for x in self._file_logger_levels]):
|
|
313
|
+
pass
|
|
314
|
+
else:
|
|
315
|
+
self._file_logger_levels = [self.__class__.STR_TO_INT_LOGGER_LEVELS["DEBUG"],
|
|
316
|
+
self.__class__.STR_TO_INT_LOGGER_LEVELS["INFO"],
|
|
317
|
+
self.__class__.STR_TO_INT_LOGGER_LEVELS["ERROR"]]
|
|
318
|
+
return self._file_logger_levels
|
|
319
|
+
|
|
320
|
+
@property
|
|
321
|
+
def project_name(self):
|
|
322
|
+
"""
|
|
323
|
+
Getter for the project_name property.
|
|
324
|
+
|
|
325
|
+
Returns the name of the project. If the project name has not been set previously,
|
|
326
|
+
it is determined based on the filename of the current file.
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
str: The name of the project.
|
|
330
|
+
"""
|
|
331
|
+
if self._project_name:
|
|
332
|
+
pass
|
|
333
|
+
else:
|
|
334
|
+
self._project_name = __file__.split('\\')[-1].split(".")[0]
|
|
335
|
+
|
|
336
|
+
return self._project_name
|
|
337
|
+
|
|
338
|
+
@property
|
|
339
|
+
def inner_log_fstructure(self):
|
|
340
|
+
"""
|
|
341
|
+
Getter method for retrieving the inner log format structure.
|
|
342
|
+
|
|
343
|
+
This method checks the type of the log_spec['format'] attribute and returns
|
|
344
|
+
the inner log format structure accordingly.
|
|
345
|
+
If the log_spec['format'] is of type str, the inner log format structure is set as
|
|
346
|
+
"{}".format(self.log_spec['format']).
|
|
347
|
+
If the log_spec['format'] is of type tuple, the inner log format structure is set as
|
|
348
|
+
"{}/{}".format(self.log_spec['format'][0], self.log_spec['format'][1]).
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
str: The inner log format structure.
|
|
352
|
+
"""
|
|
353
|
+
if isinstance(self.log_spec['format'], str):
|
|
354
|
+
self._inner_log_fstructure = "{}".format(self.log_spec['format'])
|
|
355
|
+
elif isinstance(self.log_spec['format'], tuple):
|
|
356
|
+
self._inner_log_fstructure = "{}/{}".format(self.log_spec['format'][0], self.log_spec['format'][1])
|
|
357
|
+
return self._inner_log_fstructure
|
|
358
|
+
|
|
359
|
+
@property
|
|
360
|
+
def log_location(self) -> Path:
|
|
361
|
+
"""
|
|
362
|
+
Getter method for retrieving the log_location property.
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
str: The absolute path of the log location.
|
|
366
|
+
"""
|
|
367
|
+
self._log_location = Path(self._root_log_location,
|
|
368
|
+
self.inner_log_fstructure)
|
|
369
|
+
if self._log_location.is_dir():
|
|
370
|
+
pass
|
|
371
|
+
else:
|
|
372
|
+
self._log_location.mkdir(parents=True, exist_ok=True)
|
|
373
|
+
return self._log_location
|
|
374
|
+
|
|
375
|
+
@property
|
|
376
|
+
def log_spec(self):
|
|
377
|
+
if self._log_spec is not None:
|
|
378
|
+
if isinstance(self._log_spec, dict):
|
|
379
|
+
try:
|
|
380
|
+
self._log_spec = self._log_spec['name']
|
|
381
|
+
except KeyError:
|
|
382
|
+
raise KeyError("if log_spec is given as a dictionary, "
|
|
383
|
+
"it must include the key/value for 'name'."
|
|
384
|
+
" otherwise it should be passed in as a string.") from None
|
|
385
|
+
|
|
386
|
+
elif isinstance(self._log_spec, str):
|
|
387
|
+
pass
|
|
388
|
+
|
|
389
|
+
# since all the keys are in lower case, the passed in self._log_spec should be set to .lower()
|
|
390
|
+
if self._log_spec.lower() in list(self.LOG_SPECS.keys()):
|
|
391
|
+
self._log_spec = self.LOG_SPECS[self._log_spec.lower()]
|
|
392
|
+
else:
|
|
393
|
+
raise AttributeError(
|
|
394
|
+
f"log spec must be one of the following: {str(list(self.LOG_SPECS.keys()))[1:-1]}.")
|
|
395
|
+
else:
|
|
396
|
+
self._log_spec = self.LOG_SPECS['minute']
|
|
397
|
+
return self._log_spec
|
|
398
|
+
|
|
399
|
+
def initialize_logger(self, logger=None) -> Union[logging.Logger, _EasyLoggerCustomLogger]:
|
|
400
|
+
if not logger:
|
|
401
|
+
self._internal_logger.info('no passed in logger detected')
|
|
402
|
+
logging.setLoggerClass(_EasyLoggerCustomLogger)
|
|
403
|
+
self._internal_logger.info('logger class set to _EasyLoggerCustomLogger')
|
|
404
|
+
# Create a logger with a specified name and make sure propagate is True
|
|
405
|
+
self.logger = logging.getLogger('logger')
|
|
406
|
+
else:
|
|
407
|
+
self._internal_logger.info(f'passed in logger ({logger}) detected')
|
|
408
|
+
self.logger: logging.getLogger = logger
|
|
409
|
+
self.logger.propagate = True
|
|
410
|
+
self._internal_logger.info('logger initialized')
|
|
411
|
+
self._internal_logger.info(f'propagate set to {self.logger.propagate}')
|
|
412
|
+
return self.logger
|
|
413
|
+
|
|
414
|
+
def post_handler_setup(self):
|
|
415
|
+
# set the logger level back to DEBUG, so it handles all messages
|
|
416
|
+
self.logger.setLevel(10)
|
|
417
|
+
self._internal_logger.info(f'logger level set back to {self.logger.level}')
|
|
418
|
+
self.logger.info(f"Starting {self.project_name} with the following handlers: "
|
|
419
|
+
f"{self._get_level_handler_string(self.logger.handlers)}")
|
|
420
|
+
if not self._no_stream_color and NO_COLORIZER:
|
|
421
|
+
self.logger.warning("colorizer not available, logs may not be colored as expected.")
|
|
422
|
+
self._internal_logger.info("final logger initialized")
|
|
423
|
+
# print("logger initialized")
|
|
424
|
+
|
|
425
|
+
def set_timestamp(self, **kwargs):
|
|
426
|
+
"""
|
|
427
|
+
This method, `set_timestamp`, is a static method that can be used to set a timestamp for logging purposes.
|
|
428
|
+
The method takes in keyword arguments as parameters.
|
|
429
|
+
|
|
430
|
+
Parameters:
|
|
431
|
+
**kwargs (dict): Keyword arguments that can contain the following keys:
|
|
432
|
+
- timestamp (datetime or str, optional): A datetime object or a string representing a timestamp.
|
|
433
|
+
By default, this key is set to None.
|
|
434
|
+
|
|
435
|
+
Returns:
|
|
436
|
+
str: Returns a string representing the set timestamp.
|
|
437
|
+
|
|
438
|
+
Raises:
|
|
439
|
+
AttributeError: If the provided timestamp is not a datetime object or a string.
|
|
440
|
+
|
|
441
|
+
Notes:
|
|
442
|
+
- If the keyword argument 'timestamp' is provided, the method will return the provided timestamp if it is a
|
|
443
|
+
datetime object or a string representing a timestamp.
|
|
444
|
+
- If the keyword argument 'timestamp' is not provided or is set to None, the method will generate a
|
|
445
|
+
timestamp using the current date and time in ISO format without seconds and colons.
|
|
446
|
+
|
|
447
|
+
Example:
|
|
448
|
+
# Set a custom timestamp
|
|
449
|
+
timestamp = set_timestamp(timestamp='2022-01-01 12:34')
|
|
450
|
+
|
|
451
|
+
# Generate a timestamp using current date and time
|
|
452
|
+
current_timestamp = set_timestamp()
|
|
453
|
+
"""
|
|
454
|
+
timestamp = kwargs.get('timestamp', None)
|
|
455
|
+
if timestamp is not None:
|
|
456
|
+
if isinstance(timestamp, (datetime, str)):
|
|
457
|
+
self._internal_logger.info(f"timestamp set to {timestamp}")
|
|
458
|
+
return timestamp
|
|
459
|
+
else:
|
|
460
|
+
try:
|
|
461
|
+
raise AttributeError("timestamp must be a datetime object or a string")
|
|
462
|
+
except AttributeError as e:
|
|
463
|
+
self._internal_logger.error(e, exc_info=True)
|
|
464
|
+
raise e from None
|
|
465
|
+
else:
|
|
466
|
+
timestamp = datetime.now().isoformat(timespec='minutes').replace(':', '')
|
|
467
|
+
self._internal_logger.info(f"timestamp set to {timestamp}")
|
|
468
|
+
return timestamp
|
|
469
|
+
|
|
470
|
+
def _setup_formatters(self, **kwargs) -> (logging.Formatter, Union[ColorizedFormatter, logging.Formatter]):
|
|
471
|
+
formatter = kwargs.get('formatter', logging.Formatter(self._chosen_format))
|
|
472
|
+
|
|
473
|
+
if not self._no_stream_color:
|
|
474
|
+
stream_formatter = kwargs.get('stream_formatter', ColorizedFormatter(self._chosen_format))
|
|
475
|
+
else:
|
|
476
|
+
stream_formatter = kwargs.get('stream_formatter', logging.Formatter(self._chosen_format))
|
|
477
|
+
return formatter, stream_formatter
|
|
478
|
+
|
|
479
|
+
def _add_filter_to_file_handler(self, handler: logging.FileHandler):
|
|
480
|
+
"""
|
|
481
|
+
this is meant to be overwritten in a subclass to allow for filters
|
|
482
|
+
to be added to file handlers without rewriting the entire method.
|
|
483
|
+
|
|
484
|
+
Ex: new_filter = MyFilter()
|
|
485
|
+
handler.addFilter(new_filter)
|
|
486
|
+
:param handler:
|
|
487
|
+
:type handler:
|
|
488
|
+
:return:
|
|
489
|
+
:rtype:
|
|
490
|
+
"""
|
|
491
|
+
pass
|
|
492
|
+
|
|
493
|
+
def _add_filter_to_stream_handler(self, handler: logging.StreamHandler):
|
|
494
|
+
"""
|
|
495
|
+
this is meant to be overwritten in a subclass to allow for filters
|
|
496
|
+
to be added to stream handlers without rewriting the entire method.
|
|
497
|
+
|
|
498
|
+
Ex: new_filter = MyFilter()
|
|
499
|
+
handler.addFilter(new_filter)
|
|
500
|
+
|
|
501
|
+
:param handler:
|
|
502
|
+
:type handler:
|
|
503
|
+
:return:
|
|
504
|
+
:rtype:
|
|
505
|
+
"""
|
|
506
|
+
pass
|
|
507
|
+
|
|
508
|
+
def make_file_handlers(self):
|
|
509
|
+
"""
|
|
510
|
+
This method is used to create file handlers for the logger.
|
|
511
|
+
It sets the logging level for each handler based on the file_logger_levels attribute.
|
|
512
|
+
It also sets the log file location based on the logger level, project name, and timestamp.
|
|
513
|
+
|
|
514
|
+
Parameters:
|
|
515
|
+
None
|
|
516
|
+
|
|
517
|
+
Returns:
|
|
518
|
+
None
|
|
519
|
+
|
|
520
|
+
Raises:
|
|
521
|
+
None
|
|
522
|
+
"""
|
|
523
|
+
self._internal_logger.info("creating file handlers for each logger level and log file location")
|
|
524
|
+
for lvl in self.file_logger_levels:
|
|
525
|
+
self.logger.setLevel(lvl)
|
|
526
|
+
level_string = self.__class__.INT_TO_STR_LOGGER_LEVELS[self.logger.level]
|
|
527
|
+
|
|
528
|
+
log_path = Path(self.log_location, '{}-{}-{}.log'.format(level_string,
|
|
529
|
+
self.project_name, self.timestamp))
|
|
530
|
+
|
|
531
|
+
# Create a file handler for the logger, and specify the log file location
|
|
532
|
+
file_handler = logging.FileHandler(log_path)
|
|
533
|
+
# Set the logging format for the file handler
|
|
534
|
+
file_handler.setFormatter(self.formatter)
|
|
535
|
+
file_handler.setLevel(self.logger.level)
|
|
536
|
+
# doesn't do anything unless subclassed
|
|
537
|
+
self._add_filter_to_file_handler(file_handler)
|
|
538
|
+
|
|
539
|
+
# Add the file handlers to the loggers
|
|
540
|
+
self.logger.addHandler(file_handler)
|
|
541
|
+
|
|
542
|
+
def create_stream_handler(self, log_level_to_stream=logging.WARNING, **kwargs):
|
|
543
|
+
"""
|
|
544
|
+
Creates and configures a StreamHandler for warning messages to print to the console.
|
|
545
|
+
|
|
546
|
+
This method creates a StreamHandler and sets its logging format.
|
|
547
|
+
The StreamHandler is then set to handle only warning level log messages.
|
|
548
|
+
|
|
549
|
+
A one-time filter is added to the StreamHandler to ensure that warning messages are only printed to the console once.
|
|
550
|
+
|
|
551
|
+
Finally, the StreamHandler is added to the logger.
|
|
552
|
+
|
|
553
|
+
Note: This method assumes that `self.logger` and `self.formatter` are already defined.
|
|
554
|
+
"""
|
|
555
|
+
|
|
556
|
+
if (log_level_to_stream not in self.__class__.INT_TO_STR_LOGGER_LEVELS
|
|
557
|
+
and log_level_to_stream not in self.__class__.STR_TO_INT_LOGGER_LEVELS):
|
|
558
|
+
raise ValueError(f"log_level_to_stream must be one of {list(self.__class__.STR_TO_INT_LOGGER_LEVELS)} or "
|
|
559
|
+
f"{list(self.__class__.INT_TO_STR_LOGGER_LEVELS)}, "
|
|
560
|
+
f"not {log_level_to_stream}")
|
|
561
|
+
|
|
562
|
+
self._internal_logger.info(
|
|
563
|
+
f"creating StreamHandler() for {logging.getLevelName(log_level_to_stream)} messages to print to console")
|
|
564
|
+
|
|
565
|
+
use_one_time_filter = kwargs.get('use_one_time_filter', True)
|
|
566
|
+
self._internal_logger.info(f"use_one_time_filter set to {use_one_time_filter}")
|
|
567
|
+
|
|
568
|
+
# Create a stream handler for the logger
|
|
569
|
+
stream_handler = logging.StreamHandler()
|
|
570
|
+
# Set the logging format for the stream handler
|
|
571
|
+
stream_handler.setFormatter(self.stream_formatter)
|
|
572
|
+
stream_handler.setLevel(log_level_to_stream)
|
|
573
|
+
if use_one_time_filter:
|
|
574
|
+
# set the one time filter, so that log_level_to_stream messages will only be printed to the console once.
|
|
575
|
+
one_time_filter = ConsoleOneTimeFilter()
|
|
576
|
+
stream_handler.addFilter(one_time_filter)
|
|
577
|
+
|
|
578
|
+
# doesn't do anything unless subclassed
|
|
579
|
+
self._add_filter_to_stream_handler(stream_handler)
|
|
580
|
+
|
|
581
|
+
# Add the stream handler to logger
|
|
582
|
+
self.logger.addHandler(stream_handler)
|
|
583
|
+
self._internal_logger.info(
|
|
584
|
+
f"StreamHandler() for {logging.getLevelName(log_level_to_stream)} messages added. "
|
|
585
|
+
f"{logging.getLevelName(log_level_to_stream)}s will be printed to console")
|
|
586
|
+
if use_one_time_filter:
|
|
587
|
+
self._internal_logger.info(f'Added filter {self.logger.handlers[-1].filters[0].name} to StreamHandler()')
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
if __name__ == '__main__':
|
|
591
|
+
el = EasyLogger(internal_verbose=True,
|
|
592
|
+
show_warning_logs_in_console=True)
|
|
593
|
+
el.logger.warning("this is an info message", print_msg=True)
|
EasyLoggerAJM/filters.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from logging import Filter
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ConsoleOneTimeFilter(Filter):
|
|
5
|
+
"""
|
|
6
|
+
ConsoleOneTimeFilter class filters log messages to only allow them to be logged once.
|
|
7
|
+
:param logging.Filter: A class representing a log filter.
|
|
8
|
+
:param name: A string indicating the name of the filter.
|
|
9
|
+
:ivar logged_messages: A set to store logged messages.
|
|
10
|
+
"""
|
|
11
|
+
def __init__(self, name="ConsoleWarnOneTime"):
|
|
12
|
+
super().__init__(name)
|
|
13
|
+
self.logged_messages = set()
|
|
14
|
+
|
|
15
|
+
def filter(self, record):
|
|
16
|
+
# We only log the message if it has not been logged before
|
|
17
|
+
if record.msg not in self.logged_messages:
|
|
18
|
+
self.logged_messages.add(record.msg)
|
|
19
|
+
return True
|
|
20
|
+
return False
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from logging import Formatter
|
|
2
|
+
|
|
3
|
+
NO_COLORIZER = False
|
|
4
|
+
try:
|
|
5
|
+
from ColorizerAJM.ColorizerAJM import Colorizer
|
|
6
|
+
except (ModuleNotFoundError, ImportError):
|
|
7
|
+
NO_COLORIZER = True
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ColorizedFormatter(Formatter):
|
|
11
|
+
"""
|
|
12
|
+
Class that extends logging.Formatter to provide colored output based on log level.
|
|
13
|
+
It includes methods to format log messages and exceptions with colors specified for
|
|
14
|
+
warnings, errors, and other log levels.
|
|
15
|
+
"""
|
|
16
|
+
YELLOW = 'YELLOW'
|
|
17
|
+
RED = 'RED'
|
|
18
|
+
GRAY = 'GRAY'
|
|
19
|
+
|
|
20
|
+
def __init__(self, fmt=None, datefmt=None, style='%', validate=True):
|
|
21
|
+
super().__init__(fmt, datefmt, style, validate)
|
|
22
|
+
if NO_COLORIZER:
|
|
23
|
+
return
|
|
24
|
+
else:
|
|
25
|
+
self.colorizer = Colorizer()
|
|
26
|
+
self.warning_color = self.__class__.YELLOW
|
|
27
|
+
self.error_color = self.__class__.RED
|
|
28
|
+
self.other_color = self.__class__.GRAY
|
|
29
|
+
|
|
30
|
+
def _get_record_color(self, record):
|
|
31
|
+
if record.levelname == "WARNING":
|
|
32
|
+
return self.warning_color
|
|
33
|
+
elif record.levelname == "ERROR":
|
|
34
|
+
return self.error_color
|
|
35
|
+
else:
|
|
36
|
+
return self.other_color
|
|
37
|
+
|
|
38
|
+
def formatMessage(self, record):
|
|
39
|
+
if NO_COLORIZER:
|
|
40
|
+
return super().formatMessage(record)
|
|
41
|
+
else:
|
|
42
|
+
return self.colorizer.colorize(text=super().formatMessage(record),
|
|
43
|
+
color=self._get_record_color(record), bold=True)
|
|
44
|
+
|
|
45
|
+
def formatException(self, ei):
|
|
46
|
+
if NO_COLORIZER:
|
|
47
|
+
return super().formatException(ei)
|
|
48
|
+
else:
|
|
49
|
+
return self.colorizer.colorize(text=super().formatException(ei),
|
|
50
|
+
color=self._get_record_color(ei), bold=True)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: EasyLoggerAJM
|
|
3
|
+
Version: 1.5
|
|
4
|
+
Summary: logger with already set up generalized file handlers
|
|
5
|
+
Home-page: https://github.com/amcsparron2793-Water/EasyLoggerAJM
|
|
6
|
+
Download-URL: https://github.com/amcsparron2793-Water/EasyLoggerAJM/archive/refs/tags/1.5.tar.gz
|
|
7
|
+
Author: Amcsparron
|
|
8
|
+
Author-email: amcsparron@albanyny.gov
|
|
9
|
+
License: MIT License
|
|
10
|
+
Keywords: logging
|
|
11
|
+
License-File: LICENSE.txt
|
|
12
|
+
Dynamic: author
|
|
13
|
+
Dynamic: author-email
|
|
14
|
+
Dynamic: download-url
|
|
15
|
+
Dynamic: home-page
|
|
16
|
+
Dynamic: keywords
|
|
17
|
+
Dynamic: license
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
Dynamic: summary
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
EasyLoggerAJM/__init__.py,sha256=ic2Rnfc4i5IEnb74T7ZVup1uqWO7k5o8pS6kHaGyePE,370
|
|
2
|
+
EasyLoggerAJM/_version.py,sha256=O64MFLSUrp0GxlMO1giYxb9VRxPwi0xvCQ0BjktaPrs,21
|
|
3
|
+
EasyLoggerAJM/custom_loggers.py,sha256=JnL2Rbin-Pp7v2HI_C9HLAD_6zJDhKwyZ8XDERhvJ0g,2945
|
|
4
|
+
EasyLoggerAJM/easy_logger.py,sha256=ARq9BdTiZ4RJgexrgkMIKXv00GrcD4Dz1s9Z3UwFcy8,24890
|
|
5
|
+
EasyLoggerAJM/filters.py,sha256=fs8kArPrwdkae1C58zmcUzzxsoJBRey45ThkipJ2jOk,735
|
|
6
|
+
EasyLoggerAJM/formatters.py,sha256=o5pgrm_GEuBa6MTij3KjQUTjT5luJvL1OTT7iQSj9f4,1765
|
|
7
|
+
easyloggerajm-1.5.dist-info/licenses/LICENSE.txt,sha256=7TxqSLofaZz1NmKH8ljjGHxsgR1OyIeGah5ZZrKYVXI,1084
|
|
8
|
+
easyloggerajm-1.5.dist-info/METADATA,sha256=shMFL1DvTNOYEnFPWJG4VxriY-YCJo1qyEkSJsPYcFE,573
|
|
9
|
+
easyloggerajm-1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
+
easyloggerajm-1.5.dist-info/top_level.txt,sha256=pbtK-9qsxkQp2I_s8yZXW5ekdmjY59JmQGUreqXxblU,14
|
|
11
|
+
easyloggerajm-1.5.dist-info/RECORD,,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
Copyright (c) 2018 Andrew McSparron
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
The above copyright notice and this permission notice shall be included in all
|
|
10
|
+
copies or substantial portions of the Software.
|
|
11
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
12
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
13
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
14
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
15
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
16
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
17
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
EasyLoggerAJM
|