logging-handler 1.0.7__tar.gz → 1.0.8__tar.gz

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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: logging_handler
3
- Version: 1.0.7
3
+ Version: 1.0.8
4
4
  Summary: Python Library to quickly create logging handlers
5
5
  Author-email: Thomas Dunteman <git@learningtopi.com>
6
6
  Project-URL: Homepage, https://www.learningtopi.com/python-modules-applications/python_logging_handler/
@@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3
14
14
  Requires-Python: >=3.6
15
15
  Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
+ Dynamic: license-file
17
18
 
18
19
  # Python Logging Helper
19
20
  Python library to quickly create logging handlers. Supports logging to the console as well as to file using a syslog style output format.
@@ -65,8 +66,16 @@ In the examples above, you can create multiple loggers with different names. If
65
66
 
66
67
  ## Release Notes
67
68
 
68
- v1.0.2
69
- - Moved console level to the 1st parameter. Make it quicker and easier to create a console logger
70
- - Chaned default console level to INFO from WARNING. This is what I typically end up using anyway
71
- - Updated the home page to our new logging_handler page: https://www.learningtopi.com/python-modules-applications/python_logging_handler/
72
- - Allow passing static values DEBUG, INFO, WARNING, ERROR and CRITICAL from the logging module (or you may import from this library)
69
+ Release notes can now be found in the release notes folder.
70
+
71
+ ## Changing Logging Level
72
+
73
+ As of v1.0.8, two functions, "update_console_level" and "update_file_level" have been added. To change the logging level on the fly:
74
+
75
+ >>> from logging_helper import create_logger, update_console_level
76
+ >>> logger = create_logger(console=True)
77
+ >>> update_console_level(logger, 'info')
78
+
79
+ ## CustomLogger class
80
+
81
+ As of v1.0.8, a CustomLogger class was added. This performs the same function as using create_logger, however rather than requiring a separate function to update logging levels, the class includes a "console_level" and "file_level" function that can be used. The class also utilizes threading to enable old logfile cleanup.
@@ -48,8 +48,16 @@ In the examples above, you can create multiple loggers with different names. If
48
48
 
49
49
  ## Release Notes
50
50
 
51
- v1.0.2
52
- - Moved console level to the 1st parameter. Make it quicker and easier to create a console logger
53
- - Chaned default console level to INFO from WARNING. This is what I typically end up using anyway
54
- - Updated the home page to our new logging_handler page: https://www.learningtopi.com/python-modules-applications/python_logging_handler/
55
- - Allow passing static values DEBUG, INFO, WARNING, ERROR and CRITICAL from the logging module (or you may import from this library)
51
+ Release notes can now be found in the release notes folder.
52
+
53
+ ## Changing Logging Level
54
+
55
+ As of v1.0.8, two functions, "update_console_level" and "update_file_level" have been added. To change the logging level on the fly:
56
+
57
+ >>> from logging_helper import create_logger, update_console_level
58
+ >>> logger = create_logger(console=True)
59
+ >>> update_console_level(logger, 'info')
60
+
61
+ ## CustomLogger class
62
+
63
+ As of v1.0.8, a CustomLogger class was added. This performs the same function as using create_logger, however rather than requiring a separate function to update logging levels, the class includes a "console_level" and "file_level" function that can be used. The class also utilizes threading to enable old logfile cleanup.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "logging_handler"
3
- version = "1.0.7"
3
+ version = "1.0.8"
4
4
  description = "Python Library to quickly create logging handlers"
5
5
  authors = [{name = "Thomas Dunteman", email= "git@learningtopi.com"}]
6
6
  keywords = ["logging", "syslog"]
@@ -10,8 +10,7 @@ classifiers = [
10
10
  "Topic :: System :: Logging",
11
11
  "License :: OSI Approved :: MIT License",
12
12
  "Operating System :: OS Independent",
13
- "Programming Language :: Python :: 3"
14
- ]
13
+ "Programming Language :: Python :: 3"]
15
14
  dependencies = []
16
15
 
17
16
  [project.urls]
@@ -0,0 +1,213 @@
1
+ '''
2
+ MIT License
3
+
4
+ Copyright (c) 2022 LearningToPi
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
23
+
24
+ '''
25
+ import logging
26
+ import logging.handlers
27
+ from glob import glob
28
+ from datetime import datetime, timedelta
29
+ import pathlib
30
+ import os
31
+ import threading
32
+ from time import sleep
33
+
34
+ VERSION = (1, 0, 8)
35
+
36
+ DEFAULT_LEVEL = logging.WARNING
37
+
38
+ DEBUG = 'DEBUG'
39
+ INFO = 'INFO'
40
+ WARNING = 'WARNING'
41
+ ERROR = 'ERROR'
42
+ CRITICAL = 'CRITICAL'
43
+
44
+ _log_level_number = {
45
+ 'DEBUG': logging.DEBUG,
46
+ "INFO": logging.INFO,
47
+ "WARNING": logging.WARNING,
48
+ "ERROR": logging.ERROR,
49
+ "CRITICAL": logging.CRITICAL
50
+ }
51
+
52
+
53
+ def create_logger(console_level=WARNING, log_file='', file_level=WARNING, name='', file_mode='a', console=True,
54
+ syslog=False, syslog_script_name='', log_file_vars=None, log_file_retention_days=0, propagate=False):
55
+ """ Creates a logger and returns the handle.
56
+ Log file vars should be sent as a list of dict -> [{"var": "{date}", "set": "%Y-%m-%d-%Y-%M"}]
57
+
58
+ Supported log file vars:
59
+ {date} - will be replaced with the current date using the provided strftime format
60
+
61
+ """
62
+ logger = logging.getLogger(name)
63
+ logger.handlers.clear() # Clear all existing handlers before creating new ones!
64
+ logger.setLevel(logging.DEBUG)
65
+ logger.propagate = propagate
66
+ formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
67
+
68
+ # Console
69
+ if console:
70
+ console_handler = logging.StreamHandler()
71
+ console_handler.setLevel(console_level if isinstance(console_level, int) else _log_level_number.get(str(console_level).upper(), DEFAULT_LEVEL))
72
+ console_handler.setFormatter(formatter)
73
+ logger.addHandler(console_handler)
74
+
75
+ # syslog
76
+ if syslog:
77
+ syslog_formatter = logging.Formatter(syslog_script_name + '[%(process)d]: %(levelname)s: %(message)s')
78
+ syslog_handler = logging.handlers.SysLogHandler(address='/dev/log')
79
+ syslog_handler.setLevel(file_level if isinstance(file_level, int) else _log_level_number.get(str(file_level).upper(), DEFAULT_LEVEL))
80
+ syslog_handler.setFormatter(syslog_formatter)
81
+ logger.addHandler(syslog_handler)
82
+
83
+ # file
84
+ if log_file != '':
85
+ # replace variables in the log file name
86
+ if log_file_vars is not None:
87
+ for var in log_file_vars:
88
+ if isinstance(var, dict) and 'var' in var and 'set' in var and var['var'] == "{date}":
89
+ log_file = log_file.replace(var['var'], datetime.now().strftime(var['set']))
90
+ file_handler = logging.FileHandler(log_file, mode=file_mode, encoding='utf-8', delay=False)
91
+ file_handler.setLevel(file_level if isinstance(file_level, int) else _log_level_number.get(str(file_level).upper(), DEFAULT_LEVEL))
92
+ file_handler.setFormatter(formatter)
93
+ logger.addHandler(file_handler)
94
+
95
+ # manage retention
96
+ if log_file_retention_days > 0:
97
+ # replace variables with a *
98
+ log_file_search_name = log_file
99
+ if log_file_vars is not None:
100
+ for var in log_file_vars:
101
+ log_file_search_name.replace(var['var'], '*')
102
+ old_log_files = glob(log_file_search_name)
103
+ for old_log_file in old_log_files:
104
+ # check the age and delete if needed
105
+ fname = pathlib.Path(old_log_file)
106
+ mtime = datetime.fromtimestamp(fname.stat().st_mtime)
107
+ if mtime < datetime.now() - timedelta(days=log_file_retention_days):
108
+ logger.info('Deleting old log file %s. Modified time %s, retention set to %i days.', old_log_file, mtime, log_file_retention_days)
109
+ os.remove(old_log_file)
110
+
111
+ # return the logger
112
+ return logger
113
+
114
+
115
+ def update_console_level(logger, level):
116
+ """ Updates the console log level for the provided logger. """
117
+ for handler in logger.handlers:
118
+ if isinstance(handler, logging.StreamHandler):
119
+ handler.setLevel(level if isinstance(level, int) else _log_level_number.get(str(level).upper(), DEFAULT_LEVEL))
120
+ logger.info('Console log level updated to %s', level)
121
+
122
+
123
+ def update_file_level(logger, level):
124
+ """ Updates the file log level for the provided logger. """
125
+ for handler in logger.handlers:
126
+ if isinstance(handler, logging.handlers.SysLogHandler):
127
+ handler.setLevel(level if isinstance(level, int) else _log_level_number.get(str(level).upper(), DEFAULT_LEVEL))
128
+ logger.info('File log level updated to %s', level)
129
+
130
+
131
+ class CustomLogger(logging.Logger):
132
+ """ Custom logger class that extends the standard logging.Logger. """
133
+ def __init__(self, name, console_level:str=WARNING, log_file:str|None=None, file_level:str|None=None, file_mode:str='a', console:bool=True,
134
+ syslog:bool=False, syslog_script_name:str='', log_file_vars:dict|None=None, log_file_retention_days:int=0, propagate:bool=False):
135
+ super().__init__(name, console_level if isinstance(console_level, int) else _log_level_number.get(str(console_level).upper(), DEFAULT_LEVEL))
136
+ self.handlers.clear() # Clear all existing handlers before creating new ones!
137
+ self.setLevel(logging.DEBUG)
138
+ self.propagate = propagate
139
+ formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
140
+ self.log_file_vars, self.log_file_retention_days = log_file_vars, log_file_retention_days
141
+
142
+ # Console
143
+ if console:
144
+ console_handler = logging.StreamHandler()
145
+ console_handler.setLevel(console_level if isinstance(console_level, int) else _log_level_number.get(str(console_level).upper(), DEFAULT_LEVEL))
146
+ console_handler.setFormatter(formatter)
147
+ self.addHandler(console_handler)
148
+
149
+ # syslog
150
+ if syslog:
151
+ syslog_formatter = logging.Formatter(syslog_script_name + '[%(process)d]: %(levelname)s: %(message)s')
152
+ syslog_handler = logging.handlers.SysLogHandler(address='/dev/log')
153
+ syslog_handler.setLevel(file_level if isinstance(file_level, int) else _log_level_number.get(str(file_level).upper(), DEFAULT_LEVEL))
154
+ syslog_handler.setFormatter(syslog_formatter)
155
+ self.addHandler(syslog_handler)
156
+
157
+ # file
158
+ if log_file not in (None, ''):
159
+ # replace variables in the log file name
160
+ if log_file_vars is not None:
161
+ for var in log_file_vars:
162
+ if isinstance(var, dict) and 'var' in var and 'set' in var and var['var'] == "{date}":
163
+ log_file = log_file.replace(var['var'], datetime.now().strftime(var['set']))
164
+ file_handler = logging.FileHandler(log_file, mode=file_mode, encoding='utf-8', delay=False)
165
+ file_handler.setLevel(file_level if isinstance(file_level, int) else _log_level_number.get(str(file_level).upper(), DEFAULT_LEVEL))
166
+ file_handler.setFormatter(formatter)
167
+ self.addHandler(file_handler)
168
+
169
+ # start background thread for log file cleanup if retention is set
170
+ if log_file_retention_days > 0:
171
+ self._cleanup_thread = threading.Thread(target=self._logfile_cleanup_thread, daemon=True)
172
+ self._cleanup_thread.start()
173
+
174
+ def console_level(self, level):
175
+ """ Updates the console log level for this logger. """
176
+ for handler in self.handlers:
177
+ if isinstance(handler, logging.StreamHandler):
178
+ handler.setLevel(level if isinstance(level, int) else _log_level_number.get(str(level).upper(), DEFAULT_LEVEL))
179
+ self.info('Console log level updated to %s', level)
180
+
181
+ def file_level(self, level):
182
+ """ Updates the file log level for this logger. """
183
+ for handler in self.handlers:
184
+ if isinstance(handler, logging.handlers.SysLogHandler):
185
+ handler.setLevel(level if isinstance(level, int) else _log_level_number.get(str(level).upper(), DEFAULT_LEVEL))
186
+ self.info('File log level updated to %s', level)
187
+
188
+ def _logfile_cleanup_thread(self):
189
+ """ Thread function to clean up old log files based on retention settings. """
190
+ while True:
191
+ try:
192
+ for handler in self.handlers:
193
+ if isinstance(handler, logging.FileHandler):
194
+ log_file = handler.baseFilename
195
+ # manage retention
196
+ if hasattr(self, 'log_file_retention_days') and self.log_file_retention_days > 0:
197
+ # replace variables with a *
198
+ log_file_search_name = log_file
199
+ if hasattr(self, 'log_file_vars') and self.log_file_vars is not None:
200
+ for var in self.log_file_vars:
201
+ log_file_search_name.replace(var['var'], '*')
202
+ old_log_files = glob(log_file_search_name)
203
+ for old_log_file in old_log_files:
204
+ # check the age and delete if needed
205
+ fname = pathlib.Path(old_log_file)
206
+ mtime = datetime.fromtimestamp(fname.stat().st_mtime)
207
+ if mtime < datetime.now() - timedelta(days=self.log_file_retention_days):
208
+ self.info('Deleting old log file %s. Modified time %s, retention set to %i days.', old_log_file, mtime, self.log_file_retention_days)
209
+ os.remove(old_log_file)
210
+ except Exception as e: # pylint: disable=broad-except
211
+ self.error('Error in log file cleanup thread, %s: %s', e.__class__.__name__, str(e))
212
+ finally:
213
+ sleep(300) # Sleep for 5 minutes before checking again
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
2
- Name: logging-handler
3
- Version: 1.0.7
1
+ Metadata-Version: 2.4
2
+ Name: logging_handler
3
+ Version: 1.0.8
4
4
  Summary: Python Library to quickly create logging handlers
5
5
  Author-email: Thomas Dunteman <git@learningtopi.com>
6
6
  Project-URL: Homepage, https://www.learningtopi.com/python-modules-applications/python_logging_handler/
@@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3
14
14
  Requires-Python: >=3.6
15
15
  Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
+ Dynamic: license-file
17
18
 
18
19
  # Python Logging Helper
19
20
  Python library to quickly create logging handlers. Supports logging to the console as well as to file using a syslog style output format.
@@ -65,8 +66,16 @@ In the examples above, you can create multiple loggers with different names. If
65
66
 
66
67
  ## Release Notes
67
68
 
68
- v1.0.2
69
- - Moved console level to the 1st parameter. Make it quicker and easier to create a console logger
70
- - Chaned default console level to INFO from WARNING. This is what I typically end up using anyway
71
- - Updated the home page to our new logging_handler page: https://www.learningtopi.com/python-modules-applications/python_logging_handler/
72
- - Allow passing static values DEBUG, INFO, WARNING, ERROR and CRITICAL from the logging module (or you may import from this library)
69
+ Release notes can now be found in the release notes folder.
70
+
71
+ ## Changing Logging Level
72
+
73
+ As of v1.0.8, two functions, "update_console_level" and "update_file_level" have been added. To change the logging level on the fly:
74
+
75
+ >>> from logging_helper import create_logger, update_console_level
76
+ >>> logger = create_logger(console=True)
77
+ >>> update_console_level(logger, 'info')
78
+
79
+ ## CustomLogger class
80
+
81
+ As of v1.0.8, a CustomLogger class was added. This performs the same function as using create_logger, however rather than requiring a separate function to update logging levels, the class includes a "console_level" and "file_level" function that can be used. The class also utilizes threading to enable old logfile cleanup.
@@ -1,110 +0,0 @@
1
- '''
2
- MIT License
3
-
4
- Copyright (c) 2022 LearningToPi
5
-
6
- Permission is hereby granted, free of charge, to any person obtaining a copy
7
- of this software and associated documentation files (the "Software"), to deal
8
- in the Software without restriction, including without limitation the rights
9
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- copies of the Software, and to permit persons to whom the Software is
11
- furnished to do so, subject to the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be included in all
14
- copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
- SOFTWARE.
23
-
24
- '''
25
- import logging
26
- import logging.handlers
27
- from glob import glob
28
- from datetime import datetime, timedelta
29
- import pathlib
30
- import os
31
-
32
- __VERSION__ = (1, 0, 7)
33
-
34
- DEFAULT_LEVEL = logging.WARNING
35
-
36
- DEBUG = 'DEBUG'
37
- INFO = 'INFO'
38
- WARNING = 'WARNING'
39
- ERROR = 'ERROR'
40
- CRITICAL = 'CRITICAL'
41
-
42
- _log_level_number = {
43
- 'DEBUG': logging.DEBUG,
44
- "INFO": logging.INFO,
45
- "WARNING": logging.WARNING,
46
- "ERROR": logging.ERROR,
47
- "CRITICAL": logging.CRITICAL
48
- }
49
-
50
-
51
- def create_logger(console_level=WARNING, log_file='', file_level=WARNING, name='', file_mode='a', console=True,
52
- syslog=False, syslog_script_name='', log_file_vars=None, log_file_retention_days=0, propagate=False):
53
- """ Creates a logger and returns the handle.
54
- Log file vars should be sent as a list of dict -> [{"var": "{date}", "set": "%Y-%m-%d-%Y-%M"}]
55
-
56
- Supported log file vars:
57
- {date} - will be replaced with the current date using the provided strftime format
58
-
59
- """
60
- logger = logging.getLogger(name)
61
- logger.handlers.clear() # Clear all existing handlers before creating new ones!
62
- logger.setLevel(logging.DEBUG)
63
- logger.propagate = propagate
64
- formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
65
-
66
- # Console
67
- if console:
68
- console_handler = logging.StreamHandler()
69
- console_handler.setLevel(console_level if isinstance(console_level, int) else _log_level_number.get(str(console_level).upper(), DEFAULT_LEVEL))
70
- console_handler.setFormatter(formatter)
71
- logger.addHandler(console_handler)
72
-
73
- # syslog
74
- if syslog:
75
- syslog_formatter = logging.Formatter(syslog_script_name + '[%(process)d]: %(levelname)s: %(message)s')
76
- syslog_handler = logging.handlers.SysLogHandler(address='/dev/log')
77
- syslog_handler.setLevel(file_level if isinstance(file_level, int) else _log_level_number.get(str(file_level).upper(), DEFAULT_LEVEL))
78
- syslog_handler.setFormatter(syslog_formatter)
79
- logger.addHandler(syslog_handler)
80
-
81
- # file
82
- if log_file != '':
83
- # replace variables in the log file name
84
- if log_file_vars is not None:
85
- for var in log_file_vars:
86
- if isinstance(var, dict) and 'var' in var and 'set' in var and var['var'] == "{date}":
87
- log_file = log_file.replace(var['var'], datetime.now().strftime(var['set']))
88
- file_handler = logging.FileHandler(log_file, mode=file_mode, encoding='utf-8', delay=False)
89
- file_handler.setLevel(file_level if isinstance(file_level, int) else _log_level_number.get(str(file_level).upper(), DEFAULT_LEVEL))
90
- file_handler.setFormatter(formatter)
91
- logger.addHandler(file_handler)
92
-
93
- # manage retention
94
- if log_file_retention_days > 0:
95
- # replace variables with a *
96
- log_file_search_name = log_file
97
- if log_file_vars is not None:
98
- for var in log_file_vars:
99
- log_file_search_name.replace(var['var'], '*')
100
- old_log_files = glob(log_file_search_name)
101
- for old_log_file in old_log_files:
102
- # check the age and delete if needed
103
- fname = pathlib.Path(old_log_file)
104
- mtime = datetime.fromtimestamp(fname.stat().st_mtime)
105
- if mtime < datetime.now() - timedelta(days=log_file_retention_days):
106
- logger.info('Deleting old log file %s. Modified time %s, retention set to %i days.', old_log_file, mtime, log_file_retention_days)
107
- os.remove(old_log_file)
108
-
109
- # return the logger
110
- return logger
File without changes