1""" 2 salt._logging.handlers 3 ~~~~~~~~~~~~~~~~~~~~~~ 4 5 Salt's logging handlers 6""" 7 8 9import copy 10import logging 11import logging.handlers 12import queue 13import sys 14from collections import deque 15 16from salt._logging.mixins import ExcInfoOnLogLevelFormatMixin, NewStyleClassMixin 17 18# from salt.utils.versions import warn_until_date 19 20log = logging.getLogger(__name__) 21 22 23class TemporaryLoggingHandler(logging.NullHandler): 24 """ 25 This logging handler will store all the log records up to its maximum 26 queue size at which stage the first messages stored will be dropped. 27 28 Should only be used as a temporary logging handler, while the logging 29 system is not fully configured. 30 31 Once configured, pass any logging handlers that should have received the 32 initial log messages to the function 33 :func:`TemporaryLoggingHandler.sync_with_handlers` and all stored log 34 records will be dispatched to the provided handlers. 35 36 .. versionadded:: 0.17.0 37 """ 38 39 def __init__(self, level=logging.NOTSET, max_queue_size=10000): 40 # warn_until_date( 41 # '20220101', 42 # 'Please stop using \'{name}.TemporaryLoggingHandler\'. ' 43 # '\'{name}.TemporaryLoggingHandler\' will go away after ' 44 # '{{date}}.'.format(name=__name__) 45 # ) 46 self.__max_queue_size = max_queue_size 47 super().__init__(level=level) 48 self.__messages = deque(maxlen=max_queue_size) 49 50 def handle(self, record): 51 self.acquire() 52 self.__messages.append(record) 53 self.release() 54 55 def sync_with_handlers(self, handlers=()): 56 """ 57 Sync the stored log records to the provided log handlers. 58 """ 59 if not handlers: 60 return 61 62 while self.__messages: 63 record = self.__messages.popleft() 64 for handler in handlers: 65 if handler.level > record.levelno: 66 # If the handler's level is higher than the log record one, 67 # it should not handle the log record 68 continue 69 handler.handle(record) 70 71 72class StreamHandler( 73 ExcInfoOnLogLevelFormatMixin, logging.StreamHandler, NewStyleClassMixin 74): 75 """ 76 Stream handler which properly handles exc_info on a per handler basis 77 """ 78 79 80class FileHandler( 81 ExcInfoOnLogLevelFormatMixin, logging.FileHandler, NewStyleClassMixin 82): 83 """ 84 File handler which properly handles exc_info on a per handler basis 85 """ 86 87 88class SysLogHandler( 89 ExcInfoOnLogLevelFormatMixin, logging.handlers.SysLogHandler, NewStyleClassMixin 90): 91 """ 92 Syslog handler which properly handles exc_info on a per handler basis 93 """ 94 95 def handleError(self, record): 96 """ 97 Override the default error handling mechanism for py3 98 Deal with syslog os errors when the log file does not exist 99 """ 100 handled = False 101 if sys.stderr and sys.version_info >= (3, 5, 4): 102 exc_type, exc, exc_traceback = sys.exc_info() 103 try: 104 if exc_type.__name__ in "FileNotFoundError": 105 sys.stderr.write( 106 "[WARNING ] The log_file does not exist. Logging not " 107 "setup correctly or syslog service not started.\n" 108 ) 109 handled = True 110 finally: 111 # 'del' recommended. See documentation of 112 # 'sys.exc_info()' for details. 113 del exc_type, exc, exc_traceback 114 115 if not handled: 116 super().handleError(record) 117 118 119class RotatingFileHandler( 120 ExcInfoOnLogLevelFormatMixin, 121 logging.handlers.RotatingFileHandler, 122 NewStyleClassMixin, 123): 124 """ 125 Rotating file handler which properly handles exc_info on a per handler basis 126 """ 127 128 def handleError(self, record): 129 """ 130 Override the default error handling mechanism 131 132 Deal with log file rotation errors due to log file in use 133 more softly. 134 """ 135 handled = False 136 137 # Can't use "salt.utils.platform.is_windows()" in this file 138 if ( 139 sys.platform.startswith("win") and logging.raiseExceptions and sys.stderr 140 ): # see Python issue 13807 141 exc_type, exc, exc_traceback = sys.exc_info() 142 try: 143 # PermissionError is used since Python 3.3. 144 # OSError is used for previous versions of Python. 145 if ( 146 exc_type.__name__ in ("PermissionError", "OSError") 147 and exc.winerror == 32 148 ): 149 if self.level <= logging.WARNING: 150 sys.stderr.write( 151 '[WARNING ] Unable to rotate the log file "{}" ' 152 "because it is in use\n".format(self.baseFilename) 153 ) 154 handled = True 155 finally: 156 # 'del' recommended. See documentation of 157 # 'sys.exc_info()' for details. 158 del exc_type, exc, exc_traceback 159 160 if not handled: 161 super().handleError(record) 162 163 164class WatchedFileHandler( 165 ExcInfoOnLogLevelFormatMixin, 166 logging.handlers.WatchedFileHandler, 167 NewStyleClassMixin, 168): 169 """ 170 Watched file handler which properly handles exc_info on a per handler basis 171 """ 172 173 174if sys.version_info < (3, 2): 175 176 class QueueHandler( 177 ExcInfoOnLogLevelFormatMixin, logging.Handler, NewStyleClassMixin 178 ): 179 """ 180 This handler sends events to a queue. Typically, it would be used together 181 with a multiprocessing Queue to centralise logging to file in one process 182 (in a multi-process application), so as to avoid file write contention 183 between processes. 184 185 This code is new in Python 3.2, but this class can be copy pasted into 186 user code for use with earlier Python versions. 187 """ 188 189 def __init__(self, queue): 190 """ 191 Initialise an instance, using the passed queue. 192 """ 193 # warn_until_date( 194 # '20220101', 195 # 'Please stop using \'{name}.QueueHandler\' and instead ' 196 # 'use \'logging.handlers.QueueHandler\'. ' 197 # '\'{name}.QueueHandler\' will go away after ' 198 # '{{date}}.'.format(name=__name__) 199 # ) 200 logging.Handler.__init__(self) 201 self.queue = queue 202 203 def enqueue(self, record): 204 """ 205 Enqueue a record. 206 207 The base implementation uses put_nowait. You may want to override 208 this method if you want to use blocking, timeouts or custom queue 209 implementations. 210 """ 211 try: 212 self.queue.put_nowait(record) 213 except queue.Full: 214 sys.stderr.write( 215 "[WARNING ] Message queue is full, " 216 'unable to write "{}" to log'.format(record) 217 ) 218 219 def prepare(self, record): 220 """ 221 Prepares a record for queuing. The object returned by this method is 222 enqueued. 223 The base implementation formats the record to merge the message 224 and arguments, and removes unpickleable items from the record 225 in-place. 226 You might want to override this method if you want to convert 227 the record to a dict or JSON string, or send a modified copy 228 of the record while leaving the original intact. 229 """ 230 # The format operation gets traceback text into record.exc_text 231 # (if there's exception data), and also returns the formatted 232 # message. We can then use this to replace the original 233 # msg + args, as these might be unpickleable. We also zap the 234 # exc_info and exc_text attributes, as they are no longer 235 # needed and, if not None, will typically not be pickleable. 236 msg = self.format(record) 237 # bpo-35726: make copy of record to avoid affecting other handlers in the chain. 238 record = copy.copy(record) 239 record.message = msg 240 record.msg = msg 241 record.args = None 242 record.exc_info = None 243 record.exc_text = None 244 return record 245 246 def emit(self, record): 247 """ 248 Emit a record. 249 250 Writes the LogRecord to the queue, preparing it for pickling first. 251 """ 252 try: 253 self.enqueue(self.prepare(record)) 254 except Exception: # pylint: disable=broad-except 255 self.handleError(record) 256 257 258elif sys.version_info < (3, 7): 259 # On python versions lower than 3.7, we sill subclass and overwrite prepare to include the fix for: 260 # https://bugs.python.org/issue35726 261 class QueueHandler( 262 ExcInfoOnLogLevelFormatMixin, logging.handlers.QueueHandler 263 ): # pylint: disable=no-member,inconsistent-mro 264 def __init__(self, queue): # pylint: disable=useless-super-delegation 265 super().__init__(queue) 266 # warn_until_date( 267 # '20220101', 268 # 'Please stop using \'{name}.QueueHandler\' and instead ' 269 # 'use \'logging.handlers.QueueHandler\'. ' 270 # '\'{name}.QueueHandler\' will go away after ' 271 # '{{date}}.'.format(name=__name__) 272 # ) 273 274 def enqueue(self, record): 275 """ 276 Enqueue a record. 277 278 The base implementation uses put_nowait. You may want to override 279 this method if you want to use blocking, timeouts or custom queue 280 implementations. 281 """ 282 try: 283 self.queue.put_nowait(record) 284 except queue.Full: 285 sys.stderr.write( 286 "[WARNING ] Message queue is full, " 287 'unable to write "{}" to log.\n'.format(record) 288 ) 289 290 def prepare(self, record): 291 """ 292 Prepares a record for queuing. The object returned by this method is 293 enqueued. 294 The base implementation formats the record to merge the message 295 and arguments, and removes unpickleable items from the record 296 in-place. 297 You might want to override this method if you want to convert 298 the record to a dict or JSON string, or send a modified copy 299 of the record while leaving the original intact. 300 """ 301 # The format operation gets traceback text into record.exc_text 302 # (if there's exception data), and also returns the formatted 303 # message. We can then use this to replace the original 304 # msg + args, as these might be unpickleable. We also zap the 305 # exc_info and exc_text attributes, as they are no longer 306 # needed and, if not None, will typically not be pickleable. 307 msg = self.format(record) 308 # bpo-35726: make copy of record to avoid affecting other handlers in the chain. 309 record = copy.copy(record) 310 record.message = msg 311 record.msg = msg 312 record.args = None 313 record.exc_info = None 314 record.exc_text = None 315 return record 316 317 318else: 319 320 class QueueHandler( 321 ExcInfoOnLogLevelFormatMixin, logging.handlers.QueueHandler 322 ): # pylint: disable=no-member,inconsistent-mro 323 def __init__(self, queue): # pylint: disable=useless-super-delegation 324 super().__init__(queue) 325 # warn_until_date( 326 # '20220101', 327 # 'Please stop using \'{name}.QueueHandler\' and instead ' 328 # 'use \'logging.handlers.QueueHandler\'. ' 329 # '\'{name}.QueueHandler\' will go away after ' 330 # '{{date}}.'.format(name=__name__) 331 # ) 332 333 def enqueue(self, record): 334 """ 335 Enqueue a record. 336 337 The base implementation uses put_nowait. You may want to override 338 this method if you want to use blocking, timeouts or custom queue 339 implementations. 340 """ 341 try: 342 self.queue.put_nowait(record) 343 except queue.Full: 344 sys.stderr.write( 345 "[WARNING ] Message queue is full, " 346 'unable to write "{}" to log.\n'.format(record) 347 ) 348