Mercurial > repos > shellac > sam_consensus_v3
comparison env/lib/python3.9/site-packages/coloredlogs/syslog.py @ 0:4f3585e2f14b draft default tip
"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author | shellac |
---|---|
date | Mon, 22 Mar 2021 18:12:50 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4f3585e2f14b |
---|---|
1 # Easy to use system logging for Python's logging module. | |
2 # | |
3 # Author: Peter Odding <peter@peterodding.com> | |
4 # Last Change: December 10, 2020 | |
5 # URL: https://coloredlogs.readthedocs.io | |
6 | |
7 """ | |
8 Easy to use UNIX system logging for Python's :mod:`logging` module. | |
9 | |
10 Admittedly system logging has little to do with colored terminal output, however: | |
11 | |
12 - The `coloredlogs` package is my attempt to do Python logging right and system | |
13 logging is an important part of that equation. | |
14 | |
15 - I've seen a surprising number of quirks and mistakes in system logging done | |
16 in Python, for example including ``%(asctime)s`` in a format string (the | |
17 system logging daemon is responsible for adding timestamps and thus you end | |
18 up with duplicate timestamps that make the logs awful to read :-). | |
19 | |
20 - The ``%(programname)s`` filter originated in my system logging code and I | |
21 wanted it in `coloredlogs` so the step to include this module wasn't that big. | |
22 | |
23 - As a bonus this Python module now has a test suite and proper documentation. | |
24 | |
25 So there :-P. Go take a look at :func:`enable_system_logging()`. | |
26 """ | |
27 | |
28 # Standard library modules. | |
29 import logging | |
30 import logging.handlers | |
31 import os | |
32 import socket | |
33 import sys | |
34 | |
35 # External dependencies. | |
36 from humanfriendly import coerce_boolean | |
37 from humanfriendly.compat import on_macos, on_windows | |
38 | |
39 # Modules included in our package. | |
40 from coloredlogs import ( | |
41 DEFAULT_LOG_LEVEL, | |
42 ProgramNameFilter, | |
43 adjust_level, | |
44 find_program_name, | |
45 level_to_number, | |
46 replace_handler, | |
47 ) | |
48 | |
49 LOG_DEVICE_MACOSX = '/var/run/syslog' | |
50 """The pathname of the log device on Mac OS X (a string).""" | |
51 | |
52 LOG_DEVICE_UNIX = '/dev/log' | |
53 """The pathname of the log device on Linux and most other UNIX systems (a string).""" | |
54 | |
55 DEFAULT_LOG_FORMAT = '%(programname)s[%(process)d]: %(levelname)s %(message)s' | |
56 """ | |
57 The default format for log messages sent to the system log (a string). | |
58 | |
59 The ``%(programname)s`` format requires :class:`~coloredlogs.ProgramNameFilter` | |
60 but :func:`enable_system_logging()` takes care of this for you. | |
61 | |
62 The ``name[pid]:`` construct (specifically the colon) in the format allows | |
63 rsyslogd_ to extract the ``$programname`` from each log message, which in turn | |
64 allows configuration files in ``/etc/rsyslog.d/*.conf`` to filter these log | |
65 messages to a separate log file (if the need arises). | |
66 | |
67 .. _rsyslogd: https://en.wikipedia.org/wiki/Rsyslog | |
68 """ | |
69 | |
70 # Initialize a logger for this module. | |
71 logger = logging.getLogger(__name__) | |
72 | |
73 | |
74 class SystemLogging(object): | |
75 | |
76 """Context manager to enable system logging.""" | |
77 | |
78 def __init__(self, *args, **kw): | |
79 """ | |
80 Initialize a :class:`SystemLogging` object. | |
81 | |
82 :param args: Positional arguments to :func:`enable_system_logging()`. | |
83 :param kw: Keyword arguments to :func:`enable_system_logging()`. | |
84 """ | |
85 self.args = args | |
86 self.kw = kw | |
87 self.handler = None | |
88 | |
89 def __enter__(self): | |
90 """Enable system logging when entering the context.""" | |
91 if self.handler is None: | |
92 self.handler = enable_system_logging(*self.args, **self.kw) | |
93 return self.handler | |
94 | |
95 def __exit__(self, exc_type=None, exc_value=None, traceback=None): | |
96 """ | |
97 Disable system logging when leaving the context. | |
98 | |
99 .. note:: If an exception is being handled when we leave the context a | |
100 warning message including traceback is logged *before* system | |
101 logging is disabled. | |
102 """ | |
103 if self.handler is not None: | |
104 if exc_type is not None: | |
105 logger.warning("Disabling system logging due to unhandled exception!", exc_info=True) | |
106 (self.kw.get('logger') or logging.getLogger()).removeHandler(self.handler) | |
107 self.handler = None | |
108 | |
109 | |
110 def enable_system_logging(programname=None, fmt=None, logger=None, reconfigure=True, **kw): | |
111 """ | |
112 Redirect :mod:`logging` messages to the system log (e.g. ``/var/log/syslog``). | |
113 | |
114 :param programname: The program name to embed in log messages (a string, defaults | |
115 to the result of :func:`~coloredlogs.find_program_name()`). | |
116 :param fmt: The log format for system log messages (a string, defaults to | |
117 :data:`DEFAULT_LOG_FORMAT`). | |
118 :param logger: The logger to which the :class:`~logging.handlers.SysLogHandler` | |
119 should be connected (defaults to the root logger). | |
120 :param level: The logging level for the :class:`~logging.handlers.SysLogHandler` | |
121 (defaults to :data:`.DEFAULT_LOG_LEVEL`). This value is coerced | |
122 using :func:`~coloredlogs.level_to_number()`. | |
123 :param reconfigure: If :data:`True` (the default) multiple calls to | |
124 :func:`enable_system_logging()` will each override | |
125 the previous configuration. | |
126 :param kw: Refer to :func:`connect_to_syslog()`. | |
127 :returns: A :class:`~logging.handlers.SysLogHandler` object or | |
128 :data:`None`. If an existing handler is found and `reconfigure` | |
129 is :data:`False` the existing handler object is returned. If the | |
130 connection to the system logging daemon fails :data:`None` is | |
131 returned. | |
132 | |
133 As of release 15.0 this function uses :func:`is_syslog_supported()` to | |
134 check whether system logging is supported and appropriate before it's | |
135 enabled. | |
136 | |
137 .. note:: When the logger's effective level is too restrictive it is | |
138 relaxed (refer to `notes about log levels`_ for details). | |
139 """ | |
140 # Check whether system logging is supported / appropriate. | |
141 if not is_syslog_supported(): | |
142 return None | |
143 # Provide defaults for omitted arguments. | |
144 programname = programname or find_program_name() | |
145 logger = logger or logging.getLogger() | |
146 fmt = fmt or DEFAULT_LOG_FORMAT | |
147 level = level_to_number(kw.get('level', DEFAULT_LOG_LEVEL)) | |
148 # Check whether system logging is already enabled. | |
149 handler, logger = replace_handler(logger, match_syslog_handler, reconfigure) | |
150 # Make sure reconfiguration is allowed or not relevant. | |
151 if not (handler and not reconfigure): | |
152 # Create a system logging handler. | |
153 handler = connect_to_syslog(**kw) | |
154 # Make sure the handler was successfully created. | |
155 if handler: | |
156 # Enable the use of %(programname)s. | |
157 ProgramNameFilter.install(handler=handler, fmt=fmt, programname=programname) | |
158 # Connect the formatter, handler and logger. | |
159 handler.setFormatter(logging.Formatter(fmt)) | |
160 logger.addHandler(handler) | |
161 # Adjust the level of the selected logger. | |
162 adjust_level(logger, level) | |
163 return handler | |
164 | |
165 | |
166 def connect_to_syslog(address=None, facility=None, level=None): | |
167 """ | |
168 Create a :class:`~logging.handlers.SysLogHandler`. | |
169 | |
170 :param address: The device file or network address of the system logging | |
171 daemon (a string or tuple, defaults to the result of | |
172 :func:`find_syslog_address()`). | |
173 :param facility: Refer to :class:`~logging.handlers.SysLogHandler`. | |
174 Defaults to ``LOG_USER``. | |
175 :param level: The logging level for the :class:`~logging.handlers.SysLogHandler` | |
176 (defaults to :data:`.DEFAULT_LOG_LEVEL`). This value is coerced | |
177 using :func:`~coloredlogs.level_to_number()`. | |
178 :returns: A :class:`~logging.handlers.SysLogHandler` object or :data:`None` (if the | |
179 system logging daemon is unavailable). | |
180 | |
181 The process of connecting to the system logging daemon goes as follows: | |
182 | |
183 - The following two socket types are tried (in decreasing preference): | |
184 | |
185 1. :data:`~socket.SOCK_RAW` avoids truncation of log messages but may | |
186 not be supported. | |
187 2. :data:`~socket.SOCK_STREAM` (TCP) supports longer messages than the | |
188 default (which is UDP). | |
189 """ | |
190 if not address: | |
191 address = find_syslog_address() | |
192 if facility is None: | |
193 facility = logging.handlers.SysLogHandler.LOG_USER | |
194 if level is None: | |
195 level = DEFAULT_LOG_LEVEL | |
196 for socktype in socket.SOCK_RAW, socket.SOCK_STREAM, None: | |
197 kw = dict(facility=facility, address=address) | |
198 if socktype is not None: | |
199 kw['socktype'] = socktype | |
200 try: | |
201 handler = logging.handlers.SysLogHandler(**kw) | |
202 except IOError: | |
203 # IOError is a superclass of socket.error which can be raised if the system | |
204 # logging daemon is unavailable. | |
205 pass | |
206 else: | |
207 handler.setLevel(level_to_number(level)) | |
208 return handler | |
209 | |
210 | |
211 def find_syslog_address(): | |
212 """ | |
213 Find the most suitable destination for system log messages. | |
214 | |
215 :returns: The pathname of a log device (a string) or an address/port tuple as | |
216 supported by :class:`~logging.handlers.SysLogHandler`. | |
217 | |
218 On Mac OS X this prefers :data:`LOG_DEVICE_MACOSX`, after that :data:`LOG_DEVICE_UNIX` | |
219 is checked for existence. If both of these device files don't exist the default used | |
220 by :class:`~logging.handlers.SysLogHandler` is returned. | |
221 """ | |
222 if sys.platform == 'darwin' and os.path.exists(LOG_DEVICE_MACOSX): | |
223 return LOG_DEVICE_MACOSX | |
224 elif os.path.exists(LOG_DEVICE_UNIX): | |
225 return LOG_DEVICE_UNIX | |
226 else: | |
227 return 'localhost', logging.handlers.SYSLOG_UDP_PORT | |
228 | |
229 | |
230 def is_syslog_supported(): | |
231 """ | |
232 Determine whether system logging is supported. | |
233 | |
234 :returns: | |
235 | |
236 :data:`True` if system logging is supported and can be enabled, | |
237 :data:`False` if system logging is not supported or there are good | |
238 reasons for not enabling it. | |
239 | |
240 The decision making process here is as follows: | |
241 | |
242 Override | |
243 If the environment variable ``$COLOREDLOGS_SYSLOG`` is set it is evaluated | |
244 using :func:`~humanfriendly.coerce_boolean()` and the resulting value | |
245 overrides the platform detection discussed below, this allows users to | |
246 override the decision making process if they disagree / know better. | |
247 | |
248 Linux / UNIX | |
249 On systems that are not Windows or MacOS (see below) we assume UNIX which | |
250 means either syslog is available or sending a bunch of UDP packets to | |
251 nowhere won't hurt anyone... | |
252 | |
253 Microsoft Windows | |
254 Over the years I've had multiple reports of :pypi:`coloredlogs` spewing | |
255 extremely verbose errno 10057 warning messages to the console (once for | |
256 each log message I suppose) so I now assume it a default that | |
257 "syslog-style system logging" is not generally available on Windows. | |
258 | |
259 Apple MacOS | |
260 There's cPython issue `#38780`_ which seems to result in a fatal exception | |
261 when the Python interpreter shuts down. This is (way) worse than not | |
262 having system logging enabled. The error message mentioned in `#38780`_ | |
263 has actually been following me around for years now, see for example: | |
264 | |
265 - https://github.com/xolox/python-rotate-backups/issues/9 mentions Docker | |
266 images implying Linux, so not strictly the same as `#38780`_. | |
267 | |
268 - https://github.com/xolox/python-npm-accel/issues/4 is definitely related | |
269 to `#38780`_ and is what eventually prompted me to add the | |
270 :func:`is_syslog_supported()` logic. | |
271 | |
272 .. _#38780: https://bugs.python.org/issue38780 | |
273 """ | |
274 override = os.environ.get("COLOREDLOGS_SYSLOG") | |
275 if override is not None: | |
276 return coerce_boolean(override) | |
277 else: | |
278 return not (on_windows() or on_macos()) | |
279 | |
280 | |
281 def match_syslog_handler(handler): | |
282 """ | |
283 Identify system logging handlers. | |
284 | |
285 :param handler: The :class:`~logging.Handler` class to check. | |
286 :returns: :data:`True` if the handler is a | |
287 :class:`~logging.handlers.SysLogHandler`, | |
288 :data:`False` otherwise. | |
289 | |
290 This function can be used as a callback for :func:`.find_handler()`. | |
291 """ | |
292 return isinstance(handler, logging.handlers.SysLogHandler) |