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.
@@ -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)
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -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