| 1 | n/a | # Copyright 2001-2016 by Vinay Sajip. All Rights Reserved. |
|---|
| 2 | n/a | # |
|---|
| 3 | n/a | # Permission to use, copy, modify, and distribute this software and its |
|---|
| 4 | n/a | # documentation for any purpose and without fee is hereby granted, |
|---|
| 5 | n/a | # provided that the above copyright notice appear in all copies and that |
|---|
| 6 | n/a | # both that copyright notice and this permission notice appear in |
|---|
| 7 | n/a | # supporting documentation, and that the name of Vinay Sajip |
|---|
| 8 | n/a | # not be used in advertising or publicity pertaining to distribution |
|---|
| 9 | n/a | # of the software without specific, written prior permission. |
|---|
| 10 | n/a | # VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING |
|---|
| 11 | n/a | # ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL |
|---|
| 12 | n/a | # VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR |
|---|
| 13 | n/a | # ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER |
|---|
| 14 | n/a | # IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT |
|---|
| 15 | n/a | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|---|
| 16 | n/a | |
|---|
| 17 | n/a | """ |
|---|
| 18 | n/a | Logging package for Python. Based on PEP 282 and comments thereto in |
|---|
| 19 | n/a | comp.lang.python. |
|---|
| 20 | n/a | |
|---|
| 21 | n/a | Copyright (C) 2001-2016 Vinay Sajip. All Rights Reserved. |
|---|
| 22 | n/a | |
|---|
| 23 | n/a | To use, simply 'import logging' and log away! |
|---|
| 24 | n/a | """ |
|---|
| 25 | n/a | |
|---|
| 26 | n/a | import sys, os, time, io, traceback, warnings, weakref, collections |
|---|
| 27 | n/a | |
|---|
| 28 | n/a | from string import Template |
|---|
| 29 | n/a | |
|---|
| 30 | n/a | __all__ = ['BASIC_FORMAT', 'BufferingFormatter', 'CRITICAL', 'DEBUG', 'ERROR', |
|---|
| 31 | n/a | 'FATAL', 'FileHandler', 'Filter', 'Formatter', 'Handler', 'INFO', |
|---|
| 32 | n/a | 'LogRecord', 'Logger', 'LoggerAdapter', 'NOTSET', 'NullHandler', |
|---|
| 33 | n/a | 'StreamHandler', 'WARN', 'WARNING', 'addLevelName', 'basicConfig', |
|---|
| 34 | n/a | 'captureWarnings', 'critical', 'debug', 'disable', 'error', |
|---|
| 35 | n/a | 'exception', 'fatal', 'getLevelName', 'getLogger', 'getLoggerClass', |
|---|
| 36 | n/a | 'info', 'log', 'makeLogRecord', 'setLoggerClass', 'shutdown', |
|---|
| 37 | n/a | 'warn', 'warning', 'getLogRecordFactory', 'setLogRecordFactory', |
|---|
| 38 | n/a | 'lastResort', 'raiseExceptions'] |
|---|
| 39 | n/a | |
|---|
| 40 | n/a | try: |
|---|
| 41 | n/a | import threading |
|---|
| 42 | n/a | except ImportError: #pragma: no cover |
|---|
| 43 | n/a | threading = None |
|---|
| 44 | n/a | |
|---|
| 45 | n/a | __author__ = "Vinay Sajip <vinay_sajip@red-dove.com>" |
|---|
| 46 | n/a | __status__ = "production" |
|---|
| 47 | n/a | # The following module attributes are no longer updated. |
|---|
| 48 | n/a | __version__ = "0.5.1.2" |
|---|
| 49 | n/a | __date__ = "07 February 2010" |
|---|
| 50 | n/a | |
|---|
| 51 | n/a | #--------------------------------------------------------------------------- |
|---|
| 52 | n/a | # Miscellaneous module data |
|---|
| 53 | n/a | #--------------------------------------------------------------------------- |
|---|
| 54 | n/a | |
|---|
| 55 | n/a | # |
|---|
| 56 | n/a | #_startTime is used as the base when calculating the relative time of events |
|---|
| 57 | n/a | # |
|---|
| 58 | n/a | _startTime = time.time() |
|---|
| 59 | n/a | |
|---|
| 60 | n/a | # |
|---|
| 61 | n/a | #raiseExceptions is used to see if exceptions during handling should be |
|---|
| 62 | n/a | #propagated |
|---|
| 63 | n/a | # |
|---|
| 64 | n/a | raiseExceptions = True |
|---|
| 65 | n/a | |
|---|
| 66 | n/a | # |
|---|
| 67 | n/a | # If you don't want threading information in the log, set this to zero |
|---|
| 68 | n/a | # |
|---|
| 69 | n/a | logThreads = True |
|---|
| 70 | n/a | |
|---|
| 71 | n/a | # |
|---|
| 72 | n/a | # If you don't want multiprocessing information in the log, set this to zero |
|---|
| 73 | n/a | # |
|---|
| 74 | n/a | logMultiprocessing = True |
|---|
| 75 | n/a | |
|---|
| 76 | n/a | # |
|---|
| 77 | n/a | # If you don't want process information in the log, set this to zero |
|---|
| 78 | n/a | # |
|---|
| 79 | n/a | logProcesses = True |
|---|
| 80 | n/a | |
|---|
| 81 | n/a | #--------------------------------------------------------------------------- |
|---|
| 82 | n/a | # Level related stuff |
|---|
| 83 | n/a | #--------------------------------------------------------------------------- |
|---|
| 84 | n/a | # |
|---|
| 85 | n/a | # Default levels and level names, these can be replaced with any positive set |
|---|
| 86 | n/a | # of values having corresponding names. There is a pseudo-level, NOTSET, which |
|---|
| 87 | n/a | # is only really there as a lower limit for user-defined levels. Handlers and |
|---|
| 88 | n/a | # loggers are initialized with NOTSET so that they will log all messages, even |
|---|
| 89 | n/a | # at user-defined levels. |
|---|
| 90 | n/a | # |
|---|
| 91 | n/a | |
|---|
| 92 | n/a | CRITICAL = 50 |
|---|
| 93 | n/a | FATAL = CRITICAL |
|---|
| 94 | n/a | ERROR = 40 |
|---|
| 95 | n/a | WARNING = 30 |
|---|
| 96 | n/a | WARN = WARNING |
|---|
| 97 | n/a | INFO = 20 |
|---|
| 98 | n/a | DEBUG = 10 |
|---|
| 99 | n/a | NOTSET = 0 |
|---|
| 100 | n/a | |
|---|
| 101 | n/a | _levelToName = { |
|---|
| 102 | n/a | CRITICAL: 'CRITICAL', |
|---|
| 103 | n/a | ERROR: 'ERROR', |
|---|
| 104 | n/a | WARNING: 'WARNING', |
|---|
| 105 | n/a | INFO: 'INFO', |
|---|
| 106 | n/a | DEBUG: 'DEBUG', |
|---|
| 107 | n/a | NOTSET: 'NOTSET', |
|---|
| 108 | n/a | } |
|---|
| 109 | n/a | _nameToLevel = { |
|---|
| 110 | n/a | 'CRITICAL': CRITICAL, |
|---|
| 111 | n/a | 'FATAL': FATAL, |
|---|
| 112 | n/a | 'ERROR': ERROR, |
|---|
| 113 | n/a | 'WARN': WARNING, |
|---|
| 114 | n/a | 'WARNING': WARNING, |
|---|
| 115 | n/a | 'INFO': INFO, |
|---|
| 116 | n/a | 'DEBUG': DEBUG, |
|---|
| 117 | n/a | 'NOTSET': NOTSET, |
|---|
| 118 | n/a | } |
|---|
| 119 | n/a | |
|---|
| 120 | n/a | def getLevelName(level): |
|---|
| 121 | n/a | """ |
|---|
| 122 | n/a | Return the textual representation of logging level 'level'. |
|---|
| 123 | n/a | |
|---|
| 124 | n/a | If the level is one of the predefined levels (CRITICAL, ERROR, WARNING, |
|---|
| 125 | n/a | INFO, DEBUG) then you get the corresponding string. If you have |
|---|
| 126 | n/a | associated levels with names using addLevelName then the name you have |
|---|
| 127 | n/a | associated with 'level' is returned. |
|---|
| 128 | n/a | |
|---|
| 129 | n/a | If a numeric value corresponding to one of the defined levels is passed |
|---|
| 130 | n/a | in, the corresponding string representation is returned. |
|---|
| 131 | n/a | |
|---|
| 132 | n/a | Otherwise, the string "Level %s" % level is returned. |
|---|
| 133 | n/a | """ |
|---|
| 134 | n/a | # See Issues #22386, #27937 and #29220 for why it's this way |
|---|
| 135 | n/a | result = _levelToName.get(level) |
|---|
| 136 | n/a | if result is not None: |
|---|
| 137 | n/a | return result |
|---|
| 138 | n/a | result = _nameToLevel.get(level) |
|---|
| 139 | n/a | if result is not None: |
|---|
| 140 | n/a | return result |
|---|
| 141 | n/a | return "Level %s" % level |
|---|
| 142 | n/a | |
|---|
| 143 | n/a | def addLevelName(level, levelName): |
|---|
| 144 | n/a | """ |
|---|
| 145 | n/a | Associate 'levelName' with 'level'. |
|---|
| 146 | n/a | |
|---|
| 147 | n/a | This is used when converting levels to text during message formatting. |
|---|
| 148 | n/a | """ |
|---|
| 149 | n/a | _acquireLock() |
|---|
| 150 | n/a | try: #unlikely to cause an exception, but you never know... |
|---|
| 151 | n/a | _levelToName[level] = levelName |
|---|
| 152 | n/a | _nameToLevel[levelName] = level |
|---|
| 153 | n/a | finally: |
|---|
| 154 | n/a | _releaseLock() |
|---|
| 155 | n/a | |
|---|
| 156 | n/a | if hasattr(sys, '_getframe'): |
|---|
| 157 | n/a | currentframe = lambda: sys._getframe(3) |
|---|
| 158 | n/a | else: #pragma: no cover |
|---|
| 159 | n/a | def currentframe(): |
|---|
| 160 | n/a | """Return the frame object for the caller's stack frame.""" |
|---|
| 161 | n/a | try: |
|---|
| 162 | n/a | raise Exception |
|---|
| 163 | n/a | except Exception: |
|---|
| 164 | n/a | return sys.exc_info()[2].tb_frame.f_back |
|---|
| 165 | n/a | |
|---|
| 166 | n/a | # |
|---|
| 167 | n/a | # _srcfile is used when walking the stack to check when we've got the first |
|---|
| 168 | n/a | # caller stack frame, by skipping frames whose filename is that of this |
|---|
| 169 | n/a | # module's source. It therefore should contain the filename of this module's |
|---|
| 170 | n/a | # source file. |
|---|
| 171 | n/a | # |
|---|
| 172 | n/a | # Ordinarily we would use __file__ for this, but frozen modules don't always |
|---|
| 173 | n/a | # have __file__ set, for some reason (see Issue #21736). Thus, we get the |
|---|
| 174 | n/a | # filename from a handy code object from a function defined in this module. |
|---|
| 175 | n/a | # (There's no particular reason for picking addLevelName.) |
|---|
| 176 | n/a | # |
|---|
| 177 | n/a | |
|---|
| 178 | n/a | _srcfile = os.path.normcase(addLevelName.__code__.co_filename) |
|---|
| 179 | n/a | |
|---|
| 180 | n/a | # _srcfile is only used in conjunction with sys._getframe(). |
|---|
| 181 | n/a | # To provide compatibility with older versions of Python, set _srcfile |
|---|
| 182 | n/a | # to None if _getframe() is not available; this value will prevent |
|---|
| 183 | n/a | # findCaller() from being called. You can also do this if you want to avoid |
|---|
| 184 | n/a | # the overhead of fetching caller information, even when _getframe() is |
|---|
| 185 | n/a | # available. |
|---|
| 186 | n/a | #if not hasattr(sys, '_getframe'): |
|---|
| 187 | n/a | # _srcfile = None |
|---|
| 188 | n/a | |
|---|
| 189 | n/a | |
|---|
| 190 | n/a | def _checkLevel(level): |
|---|
| 191 | n/a | if isinstance(level, int): |
|---|
| 192 | n/a | rv = level |
|---|
| 193 | n/a | elif str(level) == level: |
|---|
| 194 | n/a | if level not in _nameToLevel: |
|---|
| 195 | n/a | raise ValueError("Unknown level: %r" % level) |
|---|
| 196 | n/a | rv = _nameToLevel[level] |
|---|
| 197 | n/a | else: |
|---|
| 198 | n/a | raise TypeError("Level not an integer or a valid string: %r" % level) |
|---|
| 199 | n/a | return rv |
|---|
| 200 | n/a | |
|---|
| 201 | n/a | #--------------------------------------------------------------------------- |
|---|
| 202 | n/a | # Thread-related stuff |
|---|
| 203 | n/a | #--------------------------------------------------------------------------- |
|---|
| 204 | n/a | |
|---|
| 205 | n/a | # |
|---|
| 206 | n/a | #_lock is used to serialize access to shared data structures in this module. |
|---|
| 207 | n/a | #This needs to be an RLock because fileConfig() creates and configures |
|---|
| 208 | n/a | #Handlers, and so might arbitrary user threads. Since Handler code updates the |
|---|
| 209 | n/a | #shared dictionary _handlers, it needs to acquire the lock. But if configuring, |
|---|
| 210 | n/a | #the lock would already have been acquired - so we need an RLock. |
|---|
| 211 | n/a | #The same argument applies to Loggers and Manager.loggerDict. |
|---|
| 212 | n/a | # |
|---|
| 213 | n/a | if threading: |
|---|
| 214 | n/a | _lock = threading.RLock() |
|---|
| 215 | n/a | else: #pragma: no cover |
|---|
| 216 | n/a | _lock = None |
|---|
| 217 | n/a | |
|---|
| 218 | n/a | |
|---|
| 219 | n/a | def _acquireLock(): |
|---|
| 220 | n/a | """ |
|---|
| 221 | n/a | Acquire the module-level lock for serializing access to shared data. |
|---|
| 222 | n/a | |
|---|
| 223 | n/a | This should be released with _releaseLock(). |
|---|
| 224 | n/a | """ |
|---|
| 225 | n/a | if _lock: |
|---|
| 226 | n/a | _lock.acquire() |
|---|
| 227 | n/a | |
|---|
| 228 | n/a | def _releaseLock(): |
|---|
| 229 | n/a | """ |
|---|
| 230 | n/a | Release the module-level lock acquired by calling _acquireLock(). |
|---|
| 231 | n/a | """ |
|---|
| 232 | n/a | if _lock: |
|---|
| 233 | n/a | _lock.release() |
|---|
| 234 | n/a | |
|---|
| 235 | n/a | #--------------------------------------------------------------------------- |
|---|
| 236 | n/a | # The logging record |
|---|
| 237 | n/a | #--------------------------------------------------------------------------- |
|---|
| 238 | n/a | |
|---|
| 239 | n/a | class LogRecord(object): |
|---|
| 240 | n/a | """ |
|---|
| 241 | n/a | A LogRecord instance represents an event being logged. |
|---|
| 242 | n/a | |
|---|
| 243 | n/a | LogRecord instances are created every time something is logged. They |
|---|
| 244 | n/a | contain all the information pertinent to the event being logged. The |
|---|
| 245 | n/a | main information passed in is in msg and args, which are combined |
|---|
| 246 | n/a | using str(msg) % args to create the message field of the record. The |
|---|
| 247 | n/a | record also includes information such as when the record was created, |
|---|
| 248 | n/a | the source line where the logging call was made, and any exception |
|---|
| 249 | n/a | information to be logged. |
|---|
| 250 | n/a | """ |
|---|
| 251 | n/a | def __init__(self, name, level, pathname, lineno, |
|---|
| 252 | n/a | msg, args, exc_info, func=None, sinfo=None, **kwargs): |
|---|
| 253 | n/a | """ |
|---|
| 254 | n/a | Initialize a logging record with interesting information. |
|---|
| 255 | n/a | """ |
|---|
| 256 | n/a | ct = time.time() |
|---|
| 257 | n/a | self.name = name |
|---|
| 258 | n/a | self.msg = msg |
|---|
| 259 | n/a | # |
|---|
| 260 | n/a | # The following statement allows passing of a dictionary as a sole |
|---|
| 261 | n/a | # argument, so that you can do something like |
|---|
| 262 | n/a | # logging.debug("a %(a)d b %(b)s", {'a':1, 'b':2}) |
|---|
| 263 | n/a | # Suggested by Stefan Behnel. |
|---|
| 264 | n/a | # Note that without the test for args[0], we get a problem because |
|---|
| 265 | n/a | # during formatting, we test to see if the arg is present using |
|---|
| 266 | n/a | # 'if self.args:'. If the event being logged is e.g. 'Value is %d' |
|---|
| 267 | n/a | # and if the passed arg fails 'if self.args:' then no formatting |
|---|
| 268 | n/a | # is done. For example, logger.warning('Value is %d', 0) would log |
|---|
| 269 | n/a | # 'Value is %d' instead of 'Value is 0'. |
|---|
| 270 | n/a | # For the use case of passing a dictionary, this should not be a |
|---|
| 271 | n/a | # problem. |
|---|
| 272 | n/a | # Issue #21172: a request was made to relax the isinstance check |
|---|
| 273 | n/a | # to hasattr(args[0], '__getitem__'). However, the docs on string |
|---|
| 274 | n/a | # formatting still seem to suggest a mapping object is required. |
|---|
| 275 | n/a | # Thus, while not removing the isinstance check, it does now look |
|---|
| 276 | n/a | # for collections.Mapping rather than, as before, dict. |
|---|
| 277 | n/a | if (args and len(args) == 1 and isinstance(args[0], collections.Mapping) |
|---|
| 278 | n/a | and args[0]): |
|---|
| 279 | n/a | args = args[0] |
|---|
| 280 | n/a | self.args = args |
|---|
| 281 | n/a | self.levelname = getLevelName(level) |
|---|
| 282 | n/a | self.levelno = level |
|---|
| 283 | n/a | self.pathname = pathname |
|---|
| 284 | n/a | try: |
|---|
| 285 | n/a | self.filename = os.path.basename(pathname) |
|---|
| 286 | n/a | self.module = os.path.splitext(self.filename)[0] |
|---|
| 287 | n/a | except (TypeError, ValueError, AttributeError): |
|---|
| 288 | n/a | self.filename = pathname |
|---|
| 289 | n/a | self.module = "Unknown module" |
|---|
| 290 | n/a | self.exc_info = exc_info |
|---|
| 291 | n/a | self.exc_text = None # used to cache the traceback text |
|---|
| 292 | n/a | self.stack_info = sinfo |
|---|
| 293 | n/a | self.lineno = lineno |
|---|
| 294 | n/a | self.funcName = func |
|---|
| 295 | n/a | self.created = ct |
|---|
| 296 | n/a | self.msecs = (ct - int(ct)) * 1000 |
|---|
| 297 | n/a | self.relativeCreated = (self.created - _startTime) * 1000 |
|---|
| 298 | n/a | if logThreads and threading: |
|---|
| 299 | n/a | self.thread = threading.get_ident() |
|---|
| 300 | n/a | self.threadName = threading.current_thread().name |
|---|
| 301 | n/a | else: # pragma: no cover |
|---|
| 302 | n/a | self.thread = None |
|---|
| 303 | n/a | self.threadName = None |
|---|
| 304 | n/a | if not logMultiprocessing: # pragma: no cover |
|---|
| 305 | n/a | self.processName = None |
|---|
| 306 | n/a | else: |
|---|
| 307 | n/a | self.processName = 'MainProcess' |
|---|
| 308 | n/a | mp = sys.modules.get('multiprocessing') |
|---|
| 309 | n/a | if mp is not None: |
|---|
| 310 | n/a | # Errors may occur if multiprocessing has not finished loading |
|---|
| 311 | n/a | # yet - e.g. if a custom import hook causes third-party code |
|---|
| 312 | n/a | # to run when multiprocessing calls import. See issue 8200 |
|---|
| 313 | n/a | # for an example |
|---|
| 314 | n/a | try: |
|---|
| 315 | n/a | self.processName = mp.current_process().name |
|---|
| 316 | n/a | except Exception: #pragma: no cover |
|---|
| 317 | n/a | pass |
|---|
| 318 | n/a | if logProcesses and hasattr(os, 'getpid'): |
|---|
| 319 | n/a | self.process = os.getpid() |
|---|
| 320 | n/a | else: |
|---|
| 321 | n/a | self.process = None |
|---|
| 322 | n/a | |
|---|
| 323 | n/a | def __str__(self): |
|---|
| 324 | n/a | return '<LogRecord: %s, %s, %s, %s, "%s">'%(self.name, self.levelno, |
|---|
| 325 | n/a | self.pathname, self.lineno, self.msg) |
|---|
| 326 | n/a | |
|---|
| 327 | n/a | __repr__ = __str__ |
|---|
| 328 | n/a | |
|---|
| 329 | n/a | def getMessage(self): |
|---|
| 330 | n/a | """ |
|---|
| 331 | n/a | Return the message for this LogRecord. |
|---|
| 332 | n/a | |
|---|
| 333 | n/a | Return the message for this LogRecord after merging any user-supplied |
|---|
| 334 | n/a | arguments with the message. |
|---|
| 335 | n/a | """ |
|---|
| 336 | n/a | msg = str(self.msg) |
|---|
| 337 | n/a | if self.args: |
|---|
| 338 | n/a | msg = msg % self.args |
|---|
| 339 | n/a | return msg |
|---|
| 340 | n/a | |
|---|
| 341 | n/a | # |
|---|
| 342 | n/a | # Determine which class to use when instantiating log records. |
|---|
| 343 | n/a | # |
|---|
| 344 | n/a | _logRecordFactory = LogRecord |
|---|
| 345 | n/a | |
|---|
| 346 | n/a | def setLogRecordFactory(factory): |
|---|
| 347 | n/a | """ |
|---|
| 348 | n/a | Set the factory to be used when instantiating a log record. |
|---|
| 349 | n/a | |
|---|
| 350 | n/a | :param factory: A callable which will be called to instantiate |
|---|
| 351 | n/a | a log record. |
|---|
| 352 | n/a | """ |
|---|
| 353 | n/a | global _logRecordFactory |
|---|
| 354 | n/a | _logRecordFactory = factory |
|---|
| 355 | n/a | |
|---|
| 356 | n/a | def getLogRecordFactory(): |
|---|
| 357 | n/a | """ |
|---|
| 358 | n/a | Return the factory to be used when instantiating a log record. |
|---|
| 359 | n/a | """ |
|---|
| 360 | n/a | |
|---|
| 361 | n/a | return _logRecordFactory |
|---|
| 362 | n/a | |
|---|
| 363 | n/a | def makeLogRecord(dict): |
|---|
| 364 | n/a | """ |
|---|
| 365 | n/a | Make a LogRecord whose attributes are defined by the specified dictionary, |
|---|
| 366 | n/a | This function is useful for converting a logging event received over |
|---|
| 367 | n/a | a socket connection (which is sent as a dictionary) into a LogRecord |
|---|
| 368 | n/a | instance. |
|---|
| 369 | n/a | """ |
|---|
| 370 | n/a | rv = _logRecordFactory(None, None, "", 0, "", (), None, None) |
|---|
| 371 | n/a | rv.__dict__.update(dict) |
|---|
| 372 | n/a | return rv |
|---|
| 373 | n/a | |
|---|
| 374 | n/a | #--------------------------------------------------------------------------- |
|---|
| 375 | n/a | # Formatter classes and functions |
|---|
| 376 | n/a | #--------------------------------------------------------------------------- |
|---|
| 377 | n/a | |
|---|
| 378 | n/a | class PercentStyle(object): |
|---|
| 379 | n/a | |
|---|
| 380 | n/a | default_format = '%(message)s' |
|---|
| 381 | n/a | asctime_format = '%(asctime)s' |
|---|
| 382 | n/a | asctime_search = '%(asctime)' |
|---|
| 383 | n/a | |
|---|
| 384 | n/a | def __init__(self, fmt): |
|---|
| 385 | n/a | self._fmt = fmt or self.default_format |
|---|
| 386 | n/a | |
|---|
| 387 | n/a | def usesTime(self): |
|---|
| 388 | n/a | return self._fmt.find(self.asctime_search) >= 0 |
|---|
| 389 | n/a | |
|---|
| 390 | n/a | def format(self, record): |
|---|
| 391 | n/a | return self._fmt % record.__dict__ |
|---|
| 392 | n/a | |
|---|
| 393 | n/a | class StrFormatStyle(PercentStyle): |
|---|
| 394 | n/a | default_format = '{message}' |
|---|
| 395 | n/a | asctime_format = '{asctime}' |
|---|
| 396 | n/a | asctime_search = '{asctime' |
|---|
| 397 | n/a | |
|---|
| 398 | n/a | def format(self, record): |
|---|
| 399 | n/a | return self._fmt.format(**record.__dict__) |
|---|
| 400 | n/a | |
|---|
| 401 | n/a | |
|---|
| 402 | n/a | class StringTemplateStyle(PercentStyle): |
|---|
| 403 | n/a | default_format = '${message}' |
|---|
| 404 | n/a | asctime_format = '${asctime}' |
|---|
| 405 | n/a | asctime_search = '${asctime}' |
|---|
| 406 | n/a | |
|---|
| 407 | n/a | def __init__(self, fmt): |
|---|
| 408 | n/a | self._fmt = fmt or self.default_format |
|---|
| 409 | n/a | self._tpl = Template(self._fmt) |
|---|
| 410 | n/a | |
|---|
| 411 | n/a | def usesTime(self): |
|---|
| 412 | n/a | fmt = self._fmt |
|---|
| 413 | n/a | return fmt.find('$asctime') >= 0 or fmt.find(self.asctime_format) >= 0 |
|---|
| 414 | n/a | |
|---|
| 415 | n/a | def format(self, record): |
|---|
| 416 | n/a | return self._tpl.substitute(**record.__dict__) |
|---|
| 417 | n/a | |
|---|
| 418 | n/a | BASIC_FORMAT = "%(levelname)s:%(name)s:%(message)s" |
|---|
| 419 | n/a | |
|---|
| 420 | n/a | _STYLES = { |
|---|
| 421 | n/a | '%': (PercentStyle, BASIC_FORMAT), |
|---|
| 422 | n/a | '{': (StrFormatStyle, '{levelname}:{name}:{message}'), |
|---|
| 423 | n/a | '$': (StringTemplateStyle, '${levelname}:${name}:${message}'), |
|---|
| 424 | n/a | } |
|---|
| 425 | n/a | |
|---|
| 426 | n/a | class Formatter(object): |
|---|
| 427 | n/a | """ |
|---|
| 428 | n/a | Formatter instances are used to convert a LogRecord to text. |
|---|
| 429 | n/a | |
|---|
| 430 | n/a | Formatters need to know how a LogRecord is constructed. They are |
|---|
| 431 | n/a | responsible for converting a LogRecord to (usually) a string which can |
|---|
| 432 | n/a | be interpreted by either a human or an external system. The base Formatter |
|---|
| 433 | n/a | allows a formatting string to be specified. If none is supplied, the |
|---|
| 434 | n/a | default value of "%s(message)" is used. |
|---|
| 435 | n/a | |
|---|
| 436 | n/a | The Formatter can be initialized with a format string which makes use of |
|---|
| 437 | n/a | knowledge of the LogRecord attributes - e.g. the default value mentioned |
|---|
| 438 | n/a | above makes use of the fact that the user's message and arguments are pre- |
|---|
| 439 | n/a | formatted into a LogRecord's message attribute. Currently, the useful |
|---|
| 440 | n/a | attributes in a LogRecord are described by: |
|---|
| 441 | n/a | |
|---|
| 442 | n/a | %(name)s Name of the logger (logging channel) |
|---|
| 443 | n/a | %(levelno)s Numeric logging level for the message (DEBUG, INFO, |
|---|
| 444 | n/a | WARNING, ERROR, CRITICAL) |
|---|
| 445 | n/a | %(levelname)s Text logging level for the message ("DEBUG", "INFO", |
|---|
| 446 | n/a | "WARNING", "ERROR", "CRITICAL") |
|---|
| 447 | n/a | %(pathname)s Full pathname of the source file where the logging |
|---|
| 448 | n/a | call was issued (if available) |
|---|
| 449 | n/a | %(filename)s Filename portion of pathname |
|---|
| 450 | n/a | %(module)s Module (name portion of filename) |
|---|
| 451 | n/a | %(lineno)d Source line number where the logging call was issued |
|---|
| 452 | n/a | (if available) |
|---|
| 453 | n/a | %(funcName)s Function name |
|---|
| 454 | n/a | %(created)f Time when the LogRecord was created (time.time() |
|---|
| 455 | n/a | return value) |
|---|
| 456 | n/a | %(asctime)s Textual time when the LogRecord was created |
|---|
| 457 | n/a | %(msecs)d Millisecond portion of the creation time |
|---|
| 458 | n/a | %(relativeCreated)d Time in milliseconds when the LogRecord was created, |
|---|
| 459 | n/a | relative to the time the logging module was loaded |
|---|
| 460 | n/a | (typically at application startup time) |
|---|
| 461 | n/a | %(thread)d Thread ID (if available) |
|---|
| 462 | n/a | %(threadName)s Thread name (if available) |
|---|
| 463 | n/a | %(process)d Process ID (if available) |
|---|
| 464 | n/a | %(message)s The result of record.getMessage(), computed just as |
|---|
| 465 | n/a | the record is emitted |
|---|
| 466 | n/a | """ |
|---|
| 467 | n/a | |
|---|
| 468 | n/a | converter = time.localtime |
|---|
| 469 | n/a | |
|---|
| 470 | n/a | def __init__(self, fmt=None, datefmt=None, style='%'): |
|---|
| 471 | n/a | """ |
|---|
| 472 | n/a | Initialize the formatter with specified format strings. |
|---|
| 473 | n/a | |
|---|
| 474 | n/a | Initialize the formatter either with the specified format string, or a |
|---|
| 475 | n/a | default as described above. Allow for specialized date formatting with |
|---|
| 476 | n/a | the optional datefmt argument (if omitted, you get the ISO8601 format). |
|---|
| 477 | n/a | |
|---|
| 478 | n/a | Use a style parameter of '%', '{' or '$' to specify that you want to |
|---|
| 479 | n/a | use one of %-formatting, :meth:`str.format` (``{}``) formatting or |
|---|
| 480 | n/a | :class:`string.Template` formatting in your format string. |
|---|
| 481 | n/a | |
|---|
| 482 | n/a | .. versionchanged:: 3.2 |
|---|
| 483 | n/a | Added the ``style`` parameter. |
|---|
| 484 | n/a | """ |
|---|
| 485 | n/a | if style not in _STYLES: |
|---|
| 486 | n/a | raise ValueError('Style must be one of: %s' % ','.join( |
|---|
| 487 | n/a | _STYLES.keys())) |
|---|
| 488 | n/a | self._style = _STYLES[style][0](fmt) |
|---|
| 489 | n/a | self._fmt = self._style._fmt |
|---|
| 490 | n/a | self.datefmt = datefmt |
|---|
| 491 | n/a | |
|---|
| 492 | n/a | default_time_format = '%Y-%m-%d %H:%M:%S' |
|---|
| 493 | n/a | default_msec_format = '%s,%03d' |
|---|
| 494 | n/a | |
|---|
| 495 | n/a | def formatTime(self, record, datefmt=None): |
|---|
| 496 | n/a | """ |
|---|
| 497 | n/a | Return the creation time of the specified LogRecord as formatted text. |
|---|
| 498 | n/a | |
|---|
| 499 | n/a | This method should be called from format() by a formatter which |
|---|
| 500 | n/a | wants to make use of a formatted time. This method can be overridden |
|---|
| 501 | n/a | in formatters to provide for any specific requirement, but the |
|---|
| 502 | n/a | basic behaviour is as follows: if datefmt (a string) is specified, |
|---|
| 503 | n/a | it is used with time.strftime() to format the creation time of the |
|---|
| 504 | n/a | record. Otherwise, the ISO8601 format is used. The resulting |
|---|
| 505 | n/a | string is returned. This function uses a user-configurable function |
|---|
| 506 | n/a | to convert the creation time to a tuple. By default, time.localtime() |
|---|
| 507 | n/a | is used; to change this for a particular formatter instance, set the |
|---|
| 508 | n/a | 'converter' attribute to a function with the same signature as |
|---|
| 509 | n/a | time.localtime() or time.gmtime(). To change it for all formatters, |
|---|
| 510 | n/a | for example if you want all logging times to be shown in GMT, |
|---|
| 511 | n/a | set the 'converter' attribute in the Formatter class. |
|---|
| 512 | n/a | """ |
|---|
| 513 | n/a | ct = self.converter(record.created) |
|---|
| 514 | n/a | if datefmt: |
|---|
| 515 | n/a | s = time.strftime(datefmt, ct) |
|---|
| 516 | n/a | else: |
|---|
| 517 | n/a | t = time.strftime(self.default_time_format, ct) |
|---|
| 518 | n/a | s = self.default_msec_format % (t, record.msecs) |
|---|
| 519 | n/a | return s |
|---|
| 520 | n/a | |
|---|
| 521 | n/a | def formatException(self, ei): |
|---|
| 522 | n/a | """ |
|---|
| 523 | n/a | Format and return the specified exception information as a string. |
|---|
| 524 | n/a | |
|---|
| 525 | n/a | This default implementation just uses |
|---|
| 526 | n/a | traceback.print_exception() |
|---|
| 527 | n/a | """ |
|---|
| 528 | n/a | sio = io.StringIO() |
|---|
| 529 | n/a | tb = ei[2] |
|---|
| 530 | n/a | # See issues #9427, #1553375. Commented out for now. |
|---|
| 531 | n/a | #if getattr(self, 'fullstack', False): |
|---|
| 532 | n/a | # traceback.print_stack(tb.tb_frame.f_back, file=sio) |
|---|
| 533 | n/a | traceback.print_exception(ei[0], ei[1], tb, None, sio) |
|---|
| 534 | n/a | s = sio.getvalue() |
|---|
| 535 | n/a | sio.close() |
|---|
| 536 | n/a | if s[-1:] == "\n": |
|---|
| 537 | n/a | s = s[:-1] |
|---|
| 538 | n/a | return s |
|---|
| 539 | n/a | |
|---|
| 540 | n/a | def usesTime(self): |
|---|
| 541 | n/a | """ |
|---|
| 542 | n/a | Check if the format uses the creation time of the record. |
|---|
| 543 | n/a | """ |
|---|
| 544 | n/a | return self._style.usesTime() |
|---|
| 545 | n/a | |
|---|
| 546 | n/a | def formatMessage(self, record): |
|---|
| 547 | n/a | return self._style.format(record) |
|---|
| 548 | n/a | |
|---|
| 549 | n/a | def formatStack(self, stack_info): |
|---|
| 550 | n/a | """ |
|---|
| 551 | n/a | This method is provided as an extension point for specialized |
|---|
| 552 | n/a | formatting of stack information. |
|---|
| 553 | n/a | |
|---|
| 554 | n/a | The input data is a string as returned from a call to |
|---|
| 555 | n/a | :func:`traceback.print_stack`, but with the last trailing newline |
|---|
| 556 | n/a | removed. |
|---|
| 557 | n/a | |
|---|
| 558 | n/a | The base implementation just returns the value passed in. |
|---|
| 559 | n/a | """ |
|---|
| 560 | n/a | return stack_info |
|---|
| 561 | n/a | |
|---|
| 562 | n/a | def format(self, record): |
|---|
| 563 | n/a | """ |
|---|
| 564 | n/a | Format the specified record as text. |
|---|
| 565 | n/a | |
|---|
| 566 | n/a | The record's attribute dictionary is used as the operand to a |
|---|
| 567 | n/a | string formatting operation which yields the returned string. |
|---|
| 568 | n/a | Before formatting the dictionary, a couple of preparatory steps |
|---|
| 569 | n/a | are carried out. The message attribute of the record is computed |
|---|
| 570 | n/a | using LogRecord.getMessage(). If the formatting string uses the |
|---|
| 571 | n/a | time (as determined by a call to usesTime(), formatTime() is |
|---|
| 572 | n/a | called to format the event time. If there is exception information, |
|---|
| 573 | n/a | it is formatted using formatException() and appended to the message. |
|---|
| 574 | n/a | """ |
|---|
| 575 | n/a | record.message = record.getMessage() |
|---|
| 576 | n/a | if self.usesTime(): |
|---|
| 577 | n/a | record.asctime = self.formatTime(record, self.datefmt) |
|---|
| 578 | n/a | s = self.formatMessage(record) |
|---|
| 579 | n/a | if record.exc_info: |
|---|
| 580 | n/a | # Cache the traceback text to avoid converting it multiple times |
|---|
| 581 | n/a | # (it's constant anyway) |
|---|
| 582 | n/a | if not record.exc_text: |
|---|
| 583 | n/a | record.exc_text = self.formatException(record.exc_info) |
|---|
| 584 | n/a | if record.exc_text: |
|---|
| 585 | n/a | if s[-1:] != "\n": |
|---|
| 586 | n/a | s = s + "\n" |
|---|
| 587 | n/a | s = s + record.exc_text |
|---|
| 588 | n/a | if record.stack_info: |
|---|
| 589 | n/a | if s[-1:] != "\n": |
|---|
| 590 | n/a | s = s + "\n" |
|---|
| 591 | n/a | s = s + self.formatStack(record.stack_info) |
|---|
| 592 | n/a | return s |
|---|
| 593 | n/a | |
|---|
| 594 | n/a | # |
|---|
| 595 | n/a | # The default formatter to use when no other is specified |
|---|
| 596 | n/a | # |
|---|
| 597 | n/a | _defaultFormatter = Formatter() |
|---|
| 598 | n/a | |
|---|
| 599 | n/a | class BufferingFormatter(object): |
|---|
| 600 | n/a | """ |
|---|
| 601 | n/a | A formatter suitable for formatting a number of records. |
|---|
| 602 | n/a | """ |
|---|
| 603 | n/a | def __init__(self, linefmt=None): |
|---|
| 604 | n/a | """ |
|---|
| 605 | n/a | Optionally specify a formatter which will be used to format each |
|---|
| 606 | n/a | individual record. |
|---|
| 607 | n/a | """ |
|---|
| 608 | n/a | if linefmt: |
|---|
| 609 | n/a | self.linefmt = linefmt |
|---|
| 610 | n/a | else: |
|---|
| 611 | n/a | self.linefmt = _defaultFormatter |
|---|
| 612 | n/a | |
|---|
| 613 | n/a | def formatHeader(self, records): |
|---|
| 614 | n/a | """ |
|---|
| 615 | n/a | Return the header string for the specified records. |
|---|
| 616 | n/a | """ |
|---|
| 617 | n/a | return "" |
|---|
| 618 | n/a | |
|---|
| 619 | n/a | def formatFooter(self, records): |
|---|
| 620 | n/a | """ |
|---|
| 621 | n/a | Return the footer string for the specified records. |
|---|
| 622 | n/a | """ |
|---|
| 623 | n/a | return "" |
|---|
| 624 | n/a | |
|---|
| 625 | n/a | def format(self, records): |
|---|
| 626 | n/a | """ |
|---|
| 627 | n/a | Format the specified records and return the result as a string. |
|---|
| 628 | n/a | """ |
|---|
| 629 | n/a | rv = "" |
|---|
| 630 | n/a | if len(records) > 0: |
|---|
| 631 | n/a | rv = rv + self.formatHeader(records) |
|---|
| 632 | n/a | for record in records: |
|---|
| 633 | n/a | rv = rv + self.linefmt.format(record) |
|---|
| 634 | n/a | rv = rv + self.formatFooter(records) |
|---|
| 635 | n/a | return rv |
|---|
| 636 | n/a | |
|---|
| 637 | n/a | #--------------------------------------------------------------------------- |
|---|
| 638 | n/a | # Filter classes and functions |
|---|
| 639 | n/a | #--------------------------------------------------------------------------- |
|---|
| 640 | n/a | |
|---|
| 641 | n/a | class Filter(object): |
|---|
| 642 | n/a | """ |
|---|
| 643 | n/a | Filter instances are used to perform arbitrary filtering of LogRecords. |
|---|
| 644 | n/a | |
|---|
| 645 | n/a | Loggers and Handlers can optionally use Filter instances to filter |
|---|
| 646 | n/a | records as desired. The base filter class only allows events which are |
|---|
| 647 | n/a | below a certain point in the logger hierarchy. For example, a filter |
|---|
| 648 | n/a | initialized with "A.B" will allow events logged by loggers "A.B", |
|---|
| 649 | n/a | "A.B.C", "A.B.C.D", "A.B.D" etc. but not "A.BB", "B.A.B" etc. If |
|---|
| 650 | n/a | initialized with the empty string, all events are passed. |
|---|
| 651 | n/a | """ |
|---|
| 652 | n/a | def __init__(self, name=''): |
|---|
| 653 | n/a | """ |
|---|
| 654 | n/a | Initialize a filter. |
|---|
| 655 | n/a | |
|---|
| 656 | n/a | Initialize with the name of the logger which, together with its |
|---|
| 657 | n/a | children, will have its events allowed through the filter. If no |
|---|
| 658 | n/a | name is specified, allow every event. |
|---|
| 659 | n/a | """ |
|---|
| 660 | n/a | self.name = name |
|---|
| 661 | n/a | self.nlen = len(name) |
|---|
| 662 | n/a | |
|---|
| 663 | n/a | def filter(self, record): |
|---|
| 664 | n/a | """ |
|---|
| 665 | n/a | Determine if the specified record is to be logged. |
|---|
| 666 | n/a | |
|---|
| 667 | n/a | Is the specified record to be logged? Returns 0 for no, nonzero for |
|---|
| 668 | n/a | yes. If deemed appropriate, the record may be modified in-place. |
|---|
| 669 | n/a | """ |
|---|
| 670 | n/a | if self.nlen == 0: |
|---|
| 671 | n/a | return True |
|---|
| 672 | n/a | elif self.name == record.name: |
|---|
| 673 | n/a | return True |
|---|
| 674 | n/a | elif record.name.find(self.name, 0, self.nlen) != 0: |
|---|
| 675 | n/a | return False |
|---|
| 676 | n/a | return (record.name[self.nlen] == ".") |
|---|
| 677 | n/a | |
|---|
| 678 | n/a | class Filterer(object): |
|---|
| 679 | n/a | """ |
|---|
| 680 | n/a | A base class for loggers and handlers which allows them to share |
|---|
| 681 | n/a | common code. |
|---|
| 682 | n/a | """ |
|---|
| 683 | n/a | def __init__(self): |
|---|
| 684 | n/a | """ |
|---|
| 685 | n/a | Initialize the list of filters to be an empty list. |
|---|
| 686 | n/a | """ |
|---|
| 687 | n/a | self.filters = [] |
|---|
| 688 | n/a | |
|---|
| 689 | n/a | def addFilter(self, filter): |
|---|
| 690 | n/a | """ |
|---|
| 691 | n/a | Add the specified filter to this handler. |
|---|
| 692 | n/a | """ |
|---|
| 693 | n/a | if not (filter in self.filters): |
|---|
| 694 | n/a | self.filters.append(filter) |
|---|
| 695 | n/a | |
|---|
| 696 | n/a | def removeFilter(self, filter): |
|---|
| 697 | n/a | """ |
|---|
| 698 | n/a | Remove the specified filter from this handler. |
|---|
| 699 | n/a | """ |
|---|
| 700 | n/a | if filter in self.filters: |
|---|
| 701 | n/a | self.filters.remove(filter) |
|---|
| 702 | n/a | |
|---|
| 703 | n/a | def filter(self, record): |
|---|
| 704 | n/a | """ |
|---|
| 705 | n/a | Determine if a record is loggable by consulting all the filters. |
|---|
| 706 | n/a | |
|---|
| 707 | n/a | The default is to allow the record to be logged; any filter can veto |
|---|
| 708 | n/a | this and the record is then dropped. Returns a zero value if a record |
|---|
| 709 | n/a | is to be dropped, else non-zero. |
|---|
| 710 | n/a | |
|---|
| 711 | n/a | .. versionchanged:: 3.2 |
|---|
| 712 | n/a | |
|---|
| 713 | n/a | Allow filters to be just callables. |
|---|
| 714 | n/a | """ |
|---|
| 715 | n/a | rv = True |
|---|
| 716 | n/a | for f in self.filters: |
|---|
| 717 | n/a | if hasattr(f, 'filter'): |
|---|
| 718 | n/a | result = f.filter(record) |
|---|
| 719 | n/a | else: |
|---|
| 720 | n/a | result = f(record) # assume callable - will raise if not |
|---|
| 721 | n/a | if not result: |
|---|
| 722 | n/a | rv = False |
|---|
| 723 | n/a | break |
|---|
| 724 | n/a | return rv |
|---|
| 725 | n/a | |
|---|
| 726 | n/a | #--------------------------------------------------------------------------- |
|---|
| 727 | n/a | # Handler classes and functions |
|---|
| 728 | n/a | #--------------------------------------------------------------------------- |
|---|
| 729 | n/a | |
|---|
| 730 | n/a | _handlers = weakref.WeakValueDictionary() #map of handler names to handlers |
|---|
| 731 | n/a | _handlerList = [] # added to allow handlers to be removed in reverse of order initialized |
|---|
| 732 | n/a | |
|---|
| 733 | n/a | def _removeHandlerRef(wr): |
|---|
| 734 | n/a | """ |
|---|
| 735 | n/a | Remove a handler reference from the internal cleanup list. |
|---|
| 736 | n/a | """ |
|---|
| 737 | n/a | # This function can be called during module teardown, when globals are |
|---|
| 738 | n/a | # set to None. It can also be called from another thread. So we need to |
|---|
| 739 | n/a | # pre-emptively grab the necessary globals and check if they're None, |
|---|
| 740 | n/a | # to prevent race conditions and failures during interpreter shutdown. |
|---|
| 741 | n/a | acquire, release, handlers = _acquireLock, _releaseLock, _handlerList |
|---|
| 742 | n/a | if acquire and release and handlers: |
|---|
| 743 | n/a | acquire() |
|---|
| 744 | n/a | try: |
|---|
| 745 | n/a | if wr in handlers: |
|---|
| 746 | n/a | handlers.remove(wr) |
|---|
| 747 | n/a | finally: |
|---|
| 748 | n/a | release() |
|---|
| 749 | n/a | |
|---|
| 750 | n/a | def _addHandlerRef(handler): |
|---|
| 751 | n/a | """ |
|---|
| 752 | n/a | Add a handler to the internal cleanup list using a weak reference. |
|---|
| 753 | n/a | """ |
|---|
| 754 | n/a | _acquireLock() |
|---|
| 755 | n/a | try: |
|---|
| 756 | n/a | _handlerList.append(weakref.ref(handler, _removeHandlerRef)) |
|---|
| 757 | n/a | finally: |
|---|
| 758 | n/a | _releaseLock() |
|---|
| 759 | n/a | |
|---|
| 760 | n/a | class Handler(Filterer): |
|---|
| 761 | n/a | """ |
|---|
| 762 | n/a | Handler instances dispatch logging events to specific destinations. |
|---|
| 763 | n/a | |
|---|
| 764 | n/a | The base handler class. Acts as a placeholder which defines the Handler |
|---|
| 765 | n/a | interface. Handlers can optionally use Formatter instances to format |
|---|
| 766 | n/a | records as desired. By default, no formatter is specified; in this case, |
|---|
| 767 | n/a | the 'raw' message as determined by record.message is logged. |
|---|
| 768 | n/a | """ |
|---|
| 769 | n/a | def __init__(self, level=NOTSET): |
|---|
| 770 | n/a | """ |
|---|
| 771 | n/a | Initializes the instance - basically setting the formatter to None |
|---|
| 772 | n/a | and the filter list to empty. |
|---|
| 773 | n/a | """ |
|---|
| 774 | n/a | Filterer.__init__(self) |
|---|
| 775 | n/a | self._name = None |
|---|
| 776 | n/a | self.level = _checkLevel(level) |
|---|
| 777 | n/a | self.formatter = None |
|---|
| 778 | n/a | # Add the handler to the global _handlerList (for cleanup on shutdown) |
|---|
| 779 | n/a | _addHandlerRef(self) |
|---|
| 780 | n/a | self.createLock() |
|---|
| 781 | n/a | |
|---|
| 782 | n/a | def get_name(self): |
|---|
| 783 | n/a | return self._name |
|---|
| 784 | n/a | |
|---|
| 785 | n/a | def set_name(self, name): |
|---|
| 786 | n/a | _acquireLock() |
|---|
| 787 | n/a | try: |
|---|
| 788 | n/a | if self._name in _handlers: |
|---|
| 789 | n/a | del _handlers[self._name] |
|---|
| 790 | n/a | self._name = name |
|---|
| 791 | n/a | if name: |
|---|
| 792 | n/a | _handlers[name] = self |
|---|
| 793 | n/a | finally: |
|---|
| 794 | n/a | _releaseLock() |
|---|
| 795 | n/a | |
|---|
| 796 | n/a | name = property(get_name, set_name) |
|---|
| 797 | n/a | |
|---|
| 798 | n/a | def createLock(self): |
|---|
| 799 | n/a | """ |
|---|
| 800 | n/a | Acquire a thread lock for serializing access to the underlying I/O. |
|---|
| 801 | n/a | """ |
|---|
| 802 | n/a | if threading: |
|---|
| 803 | n/a | self.lock = threading.RLock() |
|---|
| 804 | n/a | else: #pragma: no cover |
|---|
| 805 | n/a | self.lock = None |
|---|
| 806 | n/a | |
|---|
| 807 | n/a | def acquire(self): |
|---|
| 808 | n/a | """ |
|---|
| 809 | n/a | Acquire the I/O thread lock. |
|---|
| 810 | n/a | """ |
|---|
| 811 | n/a | if self.lock: |
|---|
| 812 | n/a | self.lock.acquire() |
|---|
| 813 | n/a | |
|---|
| 814 | n/a | def release(self): |
|---|
| 815 | n/a | """ |
|---|
| 816 | n/a | Release the I/O thread lock. |
|---|
| 817 | n/a | """ |
|---|
| 818 | n/a | if self.lock: |
|---|
| 819 | n/a | self.lock.release() |
|---|
| 820 | n/a | |
|---|
| 821 | n/a | def setLevel(self, level): |
|---|
| 822 | n/a | """ |
|---|
| 823 | n/a | Set the logging level of this handler. level must be an int or a str. |
|---|
| 824 | n/a | """ |
|---|
| 825 | n/a | self.level = _checkLevel(level) |
|---|
| 826 | n/a | |
|---|
| 827 | n/a | def format(self, record): |
|---|
| 828 | n/a | """ |
|---|
| 829 | n/a | Format the specified record. |
|---|
| 830 | n/a | |
|---|
| 831 | n/a | If a formatter is set, use it. Otherwise, use the default formatter |
|---|
| 832 | n/a | for the module. |
|---|
| 833 | n/a | """ |
|---|
| 834 | n/a | if self.formatter: |
|---|
| 835 | n/a | fmt = self.formatter |
|---|
| 836 | n/a | else: |
|---|
| 837 | n/a | fmt = _defaultFormatter |
|---|
| 838 | n/a | return fmt.format(record) |
|---|
| 839 | n/a | |
|---|
| 840 | n/a | def emit(self, record): |
|---|
| 841 | n/a | """ |
|---|
| 842 | n/a | Do whatever it takes to actually log the specified logging record. |
|---|
| 843 | n/a | |
|---|
| 844 | n/a | This version is intended to be implemented by subclasses and so |
|---|
| 845 | n/a | raises a NotImplementedError. |
|---|
| 846 | n/a | """ |
|---|
| 847 | n/a | raise NotImplementedError('emit must be implemented ' |
|---|
| 848 | n/a | 'by Handler subclasses') |
|---|
| 849 | n/a | |
|---|
| 850 | n/a | def handle(self, record): |
|---|
| 851 | n/a | """ |
|---|
| 852 | n/a | Conditionally emit the specified logging record. |
|---|
| 853 | n/a | |
|---|
| 854 | n/a | Emission depends on filters which may have been added to the handler. |
|---|
| 855 | n/a | Wrap the actual emission of the record with acquisition/release of |
|---|
| 856 | n/a | the I/O thread lock. Returns whether the filter passed the record for |
|---|
| 857 | n/a | emission. |
|---|
| 858 | n/a | """ |
|---|
| 859 | n/a | rv = self.filter(record) |
|---|
| 860 | n/a | if rv: |
|---|
| 861 | n/a | self.acquire() |
|---|
| 862 | n/a | try: |
|---|
| 863 | n/a | self.emit(record) |
|---|
| 864 | n/a | finally: |
|---|
| 865 | n/a | self.release() |
|---|
| 866 | n/a | return rv |
|---|
| 867 | n/a | |
|---|
| 868 | n/a | def setFormatter(self, fmt): |
|---|
| 869 | n/a | """ |
|---|
| 870 | n/a | Set the formatter for this handler. |
|---|
| 871 | n/a | """ |
|---|
| 872 | n/a | self.formatter = fmt |
|---|
| 873 | n/a | |
|---|
| 874 | n/a | def flush(self): |
|---|
| 875 | n/a | """ |
|---|
| 876 | n/a | Ensure all logging output has been flushed. |
|---|
| 877 | n/a | |
|---|
| 878 | n/a | This version does nothing and is intended to be implemented by |
|---|
| 879 | n/a | subclasses. |
|---|
| 880 | n/a | """ |
|---|
| 881 | n/a | pass |
|---|
| 882 | n/a | |
|---|
| 883 | n/a | def close(self): |
|---|
| 884 | n/a | """ |
|---|
| 885 | n/a | Tidy up any resources used by the handler. |
|---|
| 886 | n/a | |
|---|
| 887 | n/a | This version removes the handler from an internal map of handlers, |
|---|
| 888 | n/a | _handlers, which is used for handler lookup by name. Subclasses |
|---|
| 889 | n/a | should ensure that this gets called from overridden close() |
|---|
| 890 | n/a | methods. |
|---|
| 891 | n/a | """ |
|---|
| 892 | n/a | #get the module data lock, as we're updating a shared structure. |
|---|
| 893 | n/a | _acquireLock() |
|---|
| 894 | n/a | try: #unlikely to raise an exception, but you never know... |
|---|
| 895 | n/a | if self._name and self._name in _handlers: |
|---|
| 896 | n/a | del _handlers[self._name] |
|---|
| 897 | n/a | finally: |
|---|
| 898 | n/a | _releaseLock() |
|---|
| 899 | n/a | |
|---|
| 900 | n/a | def handleError(self, record): |
|---|
| 901 | n/a | """ |
|---|
| 902 | n/a | Handle errors which occur during an emit() call. |
|---|
| 903 | n/a | |
|---|
| 904 | n/a | This method should be called from handlers when an exception is |
|---|
| 905 | n/a | encountered during an emit() call. If raiseExceptions is false, |
|---|
| 906 | n/a | exceptions get silently ignored. This is what is mostly wanted |
|---|
| 907 | n/a | for a logging system - most users will not care about errors in |
|---|
| 908 | n/a | the logging system, they are more interested in application errors. |
|---|
| 909 | n/a | You could, however, replace this with a custom handler if you wish. |
|---|
| 910 | n/a | The record which was being processed is passed in to this method. |
|---|
| 911 | n/a | """ |
|---|
| 912 | n/a | if raiseExceptions and sys.stderr: # see issue 13807 |
|---|
| 913 | n/a | t, v, tb = sys.exc_info() |
|---|
| 914 | n/a | try: |
|---|
| 915 | n/a | sys.stderr.write('--- Logging error ---\n') |
|---|
| 916 | n/a | traceback.print_exception(t, v, tb, None, sys.stderr) |
|---|
| 917 | n/a | sys.stderr.write('Call stack:\n') |
|---|
| 918 | n/a | # Walk the stack frame up until we're out of logging, |
|---|
| 919 | n/a | # so as to print the calling context. |
|---|
| 920 | n/a | frame = tb.tb_frame |
|---|
| 921 | n/a | while (frame and os.path.dirname(frame.f_code.co_filename) == |
|---|
| 922 | n/a | __path__[0]): |
|---|
| 923 | n/a | frame = frame.f_back |
|---|
| 924 | n/a | if frame: |
|---|
| 925 | n/a | traceback.print_stack(frame, file=sys.stderr) |
|---|
| 926 | n/a | else: |
|---|
| 927 | n/a | # couldn't find the right stack frame, for some reason |
|---|
| 928 | n/a | sys.stderr.write('Logged from file %s, line %s\n' % ( |
|---|
| 929 | n/a | record.filename, record.lineno)) |
|---|
| 930 | n/a | # Issue 18671: output logging message and arguments |
|---|
| 931 | n/a | try: |
|---|
| 932 | n/a | sys.stderr.write('Message: %r\n' |
|---|
| 933 | n/a | 'Arguments: %s\n' % (record.msg, |
|---|
| 934 | n/a | record.args)) |
|---|
| 935 | n/a | except Exception: |
|---|
| 936 | n/a | sys.stderr.write('Unable to print the message and arguments' |
|---|
| 937 | n/a | ' - possible formatting error.\nUse the' |
|---|
| 938 | n/a | ' traceback above to help find the error.\n' |
|---|
| 939 | n/a | ) |
|---|
| 940 | n/a | except OSError: #pragma: no cover |
|---|
| 941 | n/a | pass # see issue 5971 |
|---|
| 942 | n/a | finally: |
|---|
| 943 | n/a | del t, v, tb |
|---|
| 944 | n/a | |
|---|
| 945 | n/a | def __repr__(self): |
|---|
| 946 | n/a | level = getLevelName(self.level) |
|---|
| 947 | n/a | return '<%s (%s)>' % (self.__class__.__name__, level) |
|---|
| 948 | n/a | |
|---|
| 949 | n/a | class StreamHandler(Handler): |
|---|
| 950 | n/a | """ |
|---|
| 951 | n/a | A handler class which writes logging records, appropriately formatted, |
|---|
| 952 | n/a | to a stream. Note that this class does not close the stream, as |
|---|
| 953 | n/a | sys.stdout or sys.stderr may be used. |
|---|
| 954 | n/a | """ |
|---|
| 955 | n/a | |
|---|
| 956 | n/a | terminator = '\n' |
|---|
| 957 | n/a | |
|---|
| 958 | n/a | def __init__(self, stream=None): |
|---|
| 959 | n/a | """ |
|---|
| 960 | n/a | Initialize the handler. |
|---|
| 961 | n/a | |
|---|
| 962 | n/a | If stream is not specified, sys.stderr is used. |
|---|
| 963 | n/a | """ |
|---|
| 964 | n/a | Handler.__init__(self) |
|---|
| 965 | n/a | if stream is None: |
|---|
| 966 | n/a | stream = sys.stderr |
|---|
| 967 | n/a | self.stream = stream |
|---|
| 968 | n/a | |
|---|
| 969 | n/a | def flush(self): |
|---|
| 970 | n/a | """ |
|---|
| 971 | n/a | Flushes the stream. |
|---|
| 972 | n/a | """ |
|---|
| 973 | n/a | self.acquire() |
|---|
| 974 | n/a | try: |
|---|
| 975 | n/a | if self.stream and hasattr(self.stream, "flush"): |
|---|
| 976 | n/a | self.stream.flush() |
|---|
| 977 | n/a | finally: |
|---|
| 978 | n/a | self.release() |
|---|
| 979 | n/a | |
|---|
| 980 | n/a | def emit(self, record): |
|---|
| 981 | n/a | """ |
|---|
| 982 | n/a | Emit a record. |
|---|
| 983 | n/a | |
|---|
| 984 | n/a | If a formatter is specified, it is used to format the record. |
|---|
| 985 | n/a | The record is then written to the stream with a trailing newline. If |
|---|
| 986 | n/a | exception information is present, it is formatted using |
|---|
| 987 | n/a | traceback.print_exception and appended to the stream. If the stream |
|---|
| 988 | n/a | has an 'encoding' attribute, it is used to determine how to do the |
|---|
| 989 | n/a | output to the stream. |
|---|
| 990 | n/a | """ |
|---|
| 991 | n/a | try: |
|---|
| 992 | n/a | msg = self.format(record) |
|---|
| 993 | n/a | stream = self.stream |
|---|
| 994 | n/a | stream.write(msg) |
|---|
| 995 | n/a | stream.write(self.terminator) |
|---|
| 996 | n/a | self.flush() |
|---|
| 997 | n/a | except Exception: |
|---|
| 998 | n/a | self.handleError(record) |
|---|
| 999 | n/a | |
|---|
| 1000 | n/a | def __repr__(self): |
|---|
| 1001 | n/a | level = getLevelName(self.level) |
|---|
| 1002 | n/a | name = getattr(self.stream, 'name', '') |
|---|
| 1003 | n/a | if name: |
|---|
| 1004 | n/a | name += ' ' |
|---|
| 1005 | n/a | return '<%s %s(%s)>' % (self.__class__.__name__, name, level) |
|---|
| 1006 | n/a | |
|---|
| 1007 | n/a | |
|---|
| 1008 | n/a | class FileHandler(StreamHandler): |
|---|
| 1009 | n/a | """ |
|---|
| 1010 | n/a | A handler class which writes formatted logging records to disk files. |
|---|
| 1011 | n/a | """ |
|---|
| 1012 | n/a | def __init__(self, filename, mode='a', encoding=None, delay=False): |
|---|
| 1013 | n/a | """ |
|---|
| 1014 | n/a | Open the specified file and use it as the stream for logging. |
|---|
| 1015 | n/a | """ |
|---|
| 1016 | n/a | # Issue #27493: add support for Path objects to be passed in |
|---|
| 1017 | n/a | filename = os.fspath(filename) |
|---|
| 1018 | n/a | #keep the absolute path, otherwise derived classes which use this |
|---|
| 1019 | n/a | #may come a cropper when the current directory changes |
|---|
| 1020 | n/a | self.baseFilename = os.path.abspath(filename) |
|---|
| 1021 | n/a | self.mode = mode |
|---|
| 1022 | n/a | self.encoding = encoding |
|---|
| 1023 | n/a | self.delay = delay |
|---|
| 1024 | n/a | if delay: |
|---|
| 1025 | n/a | #We don't open the stream, but we still need to call the |
|---|
| 1026 | n/a | #Handler constructor to set level, formatter, lock etc. |
|---|
| 1027 | n/a | Handler.__init__(self) |
|---|
| 1028 | n/a | self.stream = None |
|---|
| 1029 | n/a | else: |
|---|
| 1030 | n/a | StreamHandler.__init__(self, self._open()) |
|---|
| 1031 | n/a | |
|---|
| 1032 | n/a | def close(self): |
|---|
| 1033 | n/a | """ |
|---|
| 1034 | n/a | Closes the stream. |
|---|
| 1035 | n/a | """ |
|---|
| 1036 | n/a | self.acquire() |
|---|
| 1037 | n/a | try: |
|---|
| 1038 | n/a | try: |
|---|
| 1039 | n/a | if self.stream: |
|---|
| 1040 | n/a | try: |
|---|
| 1041 | n/a | self.flush() |
|---|
| 1042 | n/a | finally: |
|---|
| 1043 | n/a | stream = self.stream |
|---|
| 1044 | n/a | self.stream = None |
|---|
| 1045 | n/a | if hasattr(stream, "close"): |
|---|
| 1046 | n/a | stream.close() |
|---|
| 1047 | n/a | finally: |
|---|
| 1048 | n/a | # Issue #19523: call unconditionally to |
|---|
| 1049 | n/a | # prevent a handler leak when delay is set |
|---|
| 1050 | n/a | StreamHandler.close(self) |
|---|
| 1051 | n/a | finally: |
|---|
| 1052 | n/a | self.release() |
|---|
| 1053 | n/a | |
|---|
| 1054 | n/a | def _open(self): |
|---|
| 1055 | n/a | """ |
|---|
| 1056 | n/a | Open the current base file with the (original) mode and encoding. |
|---|
| 1057 | n/a | Return the resulting stream. |
|---|
| 1058 | n/a | """ |
|---|
| 1059 | n/a | return open(self.baseFilename, self.mode, encoding=self.encoding) |
|---|
| 1060 | n/a | |
|---|
| 1061 | n/a | def emit(self, record): |
|---|
| 1062 | n/a | """ |
|---|
| 1063 | n/a | Emit a record. |
|---|
| 1064 | n/a | |
|---|
| 1065 | n/a | If the stream was not opened because 'delay' was specified in the |
|---|
| 1066 | n/a | constructor, open it before calling the superclass's emit. |
|---|
| 1067 | n/a | """ |
|---|
| 1068 | n/a | if self.stream is None: |
|---|
| 1069 | n/a | self.stream = self._open() |
|---|
| 1070 | n/a | StreamHandler.emit(self, record) |
|---|
| 1071 | n/a | |
|---|
| 1072 | n/a | def __repr__(self): |
|---|
| 1073 | n/a | level = getLevelName(self.level) |
|---|
| 1074 | n/a | return '<%s %s (%s)>' % (self.__class__.__name__, self.baseFilename, level) |
|---|
| 1075 | n/a | |
|---|
| 1076 | n/a | |
|---|
| 1077 | n/a | class _StderrHandler(StreamHandler): |
|---|
| 1078 | n/a | """ |
|---|
| 1079 | n/a | This class is like a StreamHandler using sys.stderr, but always uses |
|---|
| 1080 | n/a | whatever sys.stderr is currently set to rather than the value of |
|---|
| 1081 | n/a | sys.stderr at handler construction time. |
|---|
| 1082 | n/a | """ |
|---|
| 1083 | n/a | def __init__(self, level=NOTSET): |
|---|
| 1084 | n/a | """ |
|---|
| 1085 | n/a | Initialize the handler. |
|---|
| 1086 | n/a | """ |
|---|
| 1087 | n/a | Handler.__init__(self, level) |
|---|
| 1088 | n/a | |
|---|
| 1089 | n/a | @property |
|---|
| 1090 | n/a | def stream(self): |
|---|
| 1091 | n/a | return sys.stderr |
|---|
| 1092 | n/a | |
|---|
| 1093 | n/a | |
|---|
| 1094 | n/a | _defaultLastResort = _StderrHandler(WARNING) |
|---|
| 1095 | n/a | lastResort = _defaultLastResort |
|---|
| 1096 | n/a | |
|---|
| 1097 | n/a | #--------------------------------------------------------------------------- |
|---|
| 1098 | n/a | # Manager classes and functions |
|---|
| 1099 | n/a | #--------------------------------------------------------------------------- |
|---|
| 1100 | n/a | |
|---|
| 1101 | n/a | class PlaceHolder(object): |
|---|
| 1102 | n/a | """ |
|---|
| 1103 | n/a | PlaceHolder instances are used in the Manager logger hierarchy to take |
|---|
| 1104 | n/a | the place of nodes for which no loggers have been defined. This class is |
|---|
| 1105 | n/a | intended for internal use only and not as part of the public API. |
|---|
| 1106 | n/a | """ |
|---|
| 1107 | n/a | def __init__(self, alogger): |
|---|
| 1108 | n/a | """ |
|---|
| 1109 | n/a | Initialize with the specified logger being a child of this placeholder. |
|---|
| 1110 | n/a | """ |
|---|
| 1111 | n/a | self.loggerMap = { alogger : None } |
|---|
| 1112 | n/a | |
|---|
| 1113 | n/a | def append(self, alogger): |
|---|
| 1114 | n/a | """ |
|---|
| 1115 | n/a | Add the specified logger as a child of this placeholder. |
|---|
| 1116 | n/a | """ |
|---|
| 1117 | n/a | if alogger not in self.loggerMap: |
|---|
| 1118 | n/a | self.loggerMap[alogger] = None |
|---|
| 1119 | n/a | |
|---|
| 1120 | n/a | # |
|---|
| 1121 | n/a | # Determine which class to use when instantiating loggers. |
|---|
| 1122 | n/a | # |
|---|
| 1123 | n/a | |
|---|
| 1124 | n/a | def setLoggerClass(klass): |
|---|
| 1125 | n/a | """ |
|---|
| 1126 | n/a | Set the class to be used when instantiating a logger. The class should |
|---|
| 1127 | n/a | define __init__() such that only a name argument is required, and the |
|---|
| 1128 | n/a | __init__() should call Logger.__init__() |
|---|
| 1129 | n/a | """ |
|---|
| 1130 | n/a | if klass != Logger: |
|---|
| 1131 | n/a | if not issubclass(klass, Logger): |
|---|
| 1132 | n/a | raise TypeError("logger not derived from logging.Logger: " |
|---|
| 1133 | n/a | + klass.__name__) |
|---|
| 1134 | n/a | global _loggerClass |
|---|
| 1135 | n/a | _loggerClass = klass |
|---|
| 1136 | n/a | |
|---|
| 1137 | n/a | def getLoggerClass(): |
|---|
| 1138 | n/a | """ |
|---|
| 1139 | n/a | Return the class to be used when instantiating a logger. |
|---|
| 1140 | n/a | """ |
|---|
| 1141 | n/a | return _loggerClass |
|---|
| 1142 | n/a | |
|---|
| 1143 | n/a | class Manager(object): |
|---|
| 1144 | n/a | """ |
|---|
| 1145 | n/a | There is [under normal circumstances] just one Manager instance, which |
|---|
| 1146 | n/a | holds the hierarchy of loggers. |
|---|
| 1147 | n/a | """ |
|---|
| 1148 | n/a | def __init__(self, rootnode): |
|---|
| 1149 | n/a | """ |
|---|
| 1150 | n/a | Initialize the manager with the root node of the logger hierarchy. |
|---|
| 1151 | n/a | """ |
|---|
| 1152 | n/a | self.root = rootnode |
|---|
| 1153 | n/a | self.disable = 0 |
|---|
| 1154 | n/a | self.emittedNoHandlerWarning = False |
|---|
| 1155 | n/a | self.loggerDict = {} |
|---|
| 1156 | n/a | self.loggerClass = None |
|---|
| 1157 | n/a | self.logRecordFactory = None |
|---|
| 1158 | n/a | |
|---|
| 1159 | n/a | def getLogger(self, name): |
|---|
| 1160 | n/a | """ |
|---|
| 1161 | n/a | Get a logger with the specified name (channel name), creating it |
|---|
| 1162 | n/a | if it doesn't yet exist. This name is a dot-separated hierarchical |
|---|
| 1163 | n/a | name, such as "a", "a.b", "a.b.c" or similar. |
|---|
| 1164 | n/a | |
|---|
| 1165 | n/a | If a PlaceHolder existed for the specified name [i.e. the logger |
|---|
| 1166 | n/a | didn't exist but a child of it did], replace it with the created |
|---|
| 1167 | n/a | logger and fix up the parent/child references which pointed to the |
|---|
| 1168 | n/a | placeholder to now point to the logger. |
|---|
| 1169 | n/a | """ |
|---|
| 1170 | n/a | rv = None |
|---|
| 1171 | n/a | if not isinstance(name, str): |
|---|
| 1172 | n/a | raise TypeError('A logger name must be a string') |
|---|
| 1173 | n/a | _acquireLock() |
|---|
| 1174 | n/a | try: |
|---|
| 1175 | n/a | if name in self.loggerDict: |
|---|
| 1176 | n/a | rv = self.loggerDict[name] |
|---|
| 1177 | n/a | if isinstance(rv, PlaceHolder): |
|---|
| 1178 | n/a | ph = rv |
|---|
| 1179 | n/a | rv = (self.loggerClass or _loggerClass)(name) |
|---|
| 1180 | n/a | rv.manager = self |
|---|
| 1181 | n/a | self.loggerDict[name] = rv |
|---|
| 1182 | n/a | self._fixupChildren(ph, rv) |
|---|
| 1183 | n/a | self._fixupParents(rv) |
|---|
| 1184 | n/a | else: |
|---|
| 1185 | n/a | rv = (self.loggerClass or _loggerClass)(name) |
|---|
| 1186 | n/a | rv.manager = self |
|---|
| 1187 | n/a | self.loggerDict[name] = rv |
|---|
| 1188 | n/a | self._fixupParents(rv) |
|---|
| 1189 | n/a | finally: |
|---|
| 1190 | n/a | _releaseLock() |
|---|
| 1191 | n/a | return rv |
|---|
| 1192 | n/a | |
|---|
| 1193 | n/a | def setLoggerClass(self, klass): |
|---|
| 1194 | n/a | """ |
|---|
| 1195 | n/a | Set the class to be used when instantiating a logger with this Manager. |
|---|
| 1196 | n/a | """ |
|---|
| 1197 | n/a | if klass != Logger: |
|---|
| 1198 | n/a | if not issubclass(klass, Logger): |
|---|
| 1199 | n/a | raise TypeError("logger not derived from logging.Logger: " |
|---|
| 1200 | n/a | + klass.__name__) |
|---|
| 1201 | n/a | self.loggerClass = klass |
|---|
| 1202 | n/a | |
|---|
| 1203 | n/a | def setLogRecordFactory(self, factory): |
|---|
| 1204 | n/a | """ |
|---|
| 1205 | n/a | Set the factory to be used when instantiating a log record with this |
|---|
| 1206 | n/a | Manager. |
|---|
| 1207 | n/a | """ |
|---|
| 1208 | n/a | self.logRecordFactory = factory |
|---|
| 1209 | n/a | |
|---|
| 1210 | n/a | def _fixupParents(self, alogger): |
|---|
| 1211 | n/a | """ |
|---|
| 1212 | n/a | Ensure that there are either loggers or placeholders all the way |
|---|
| 1213 | n/a | from the specified logger to the root of the logger hierarchy. |
|---|
| 1214 | n/a | """ |
|---|
| 1215 | n/a | name = alogger.name |
|---|
| 1216 | n/a | i = name.rfind(".") |
|---|
| 1217 | n/a | rv = None |
|---|
| 1218 | n/a | while (i > 0) and not rv: |
|---|
| 1219 | n/a | substr = name[:i] |
|---|
| 1220 | n/a | if substr not in self.loggerDict: |
|---|
| 1221 | n/a | self.loggerDict[substr] = PlaceHolder(alogger) |
|---|
| 1222 | n/a | else: |
|---|
| 1223 | n/a | obj = self.loggerDict[substr] |
|---|
| 1224 | n/a | if isinstance(obj, Logger): |
|---|
| 1225 | n/a | rv = obj |
|---|
| 1226 | n/a | else: |
|---|
| 1227 | n/a | assert isinstance(obj, PlaceHolder) |
|---|
| 1228 | n/a | obj.append(alogger) |
|---|
| 1229 | n/a | i = name.rfind(".", 0, i - 1) |
|---|
| 1230 | n/a | if not rv: |
|---|
| 1231 | n/a | rv = self.root |
|---|
| 1232 | n/a | alogger.parent = rv |
|---|
| 1233 | n/a | |
|---|
| 1234 | n/a | def _fixupChildren(self, ph, alogger): |
|---|
| 1235 | n/a | """ |
|---|
| 1236 | n/a | Ensure that children of the placeholder ph are connected to the |
|---|
| 1237 | n/a | specified logger. |
|---|
| 1238 | n/a | """ |
|---|
| 1239 | n/a | name = alogger.name |
|---|
| 1240 | n/a | namelen = len(name) |
|---|
| 1241 | n/a | for c in ph.loggerMap.keys(): |
|---|
| 1242 | n/a | #The if means ... if not c.parent.name.startswith(nm) |
|---|
| 1243 | n/a | if c.parent.name[:namelen] != name: |
|---|
| 1244 | n/a | alogger.parent = c.parent |
|---|
| 1245 | n/a | c.parent = alogger |
|---|
| 1246 | n/a | |
|---|
| 1247 | n/a | #--------------------------------------------------------------------------- |
|---|
| 1248 | n/a | # Logger classes and functions |
|---|
| 1249 | n/a | #--------------------------------------------------------------------------- |
|---|
| 1250 | n/a | |
|---|
| 1251 | n/a | class Logger(Filterer): |
|---|
| 1252 | n/a | """ |
|---|
| 1253 | n/a | Instances of the Logger class represent a single logging channel. A |
|---|
| 1254 | n/a | "logging channel" indicates an area of an application. Exactly how an |
|---|
| 1255 | n/a | "area" is defined is up to the application developer. Since an |
|---|
| 1256 | n/a | application can have any number of areas, logging channels are identified |
|---|
| 1257 | n/a | by a unique string. Application areas can be nested (e.g. an area |
|---|
| 1258 | n/a | of "input processing" might include sub-areas "read CSV files", "read |
|---|
| 1259 | n/a | XLS files" and "read Gnumeric files"). To cater for this natural nesting, |
|---|
| 1260 | n/a | channel names are organized into a namespace hierarchy where levels are |
|---|
| 1261 | n/a | separated by periods, much like the Java or Python package namespace. So |
|---|
| 1262 | n/a | in the instance given above, channel names might be "input" for the upper |
|---|
| 1263 | n/a | level, and "input.csv", "input.xls" and "input.gnu" for the sub-levels. |
|---|
| 1264 | n/a | There is no arbitrary limit to the depth of nesting. |
|---|
| 1265 | n/a | """ |
|---|
| 1266 | n/a | def __init__(self, name, level=NOTSET): |
|---|
| 1267 | n/a | """ |
|---|
| 1268 | n/a | Initialize the logger with a name and an optional level. |
|---|
| 1269 | n/a | """ |
|---|
| 1270 | n/a | Filterer.__init__(self) |
|---|
| 1271 | n/a | self.name = name |
|---|
| 1272 | n/a | self.level = _checkLevel(level) |
|---|
| 1273 | n/a | self.parent = None |
|---|
| 1274 | n/a | self.propagate = True |
|---|
| 1275 | n/a | self.handlers = [] |
|---|
| 1276 | n/a | self.disabled = False |
|---|
| 1277 | n/a | |
|---|
| 1278 | n/a | def setLevel(self, level): |
|---|
| 1279 | n/a | """ |
|---|
| 1280 | n/a | Set the logging level of this logger. level must be an int or a str. |
|---|
| 1281 | n/a | """ |
|---|
| 1282 | n/a | self.level = _checkLevel(level) |
|---|
| 1283 | n/a | |
|---|
| 1284 | n/a | def debug(self, msg, *args, **kwargs): |
|---|
| 1285 | n/a | """ |
|---|
| 1286 | n/a | Log 'msg % args' with severity 'DEBUG'. |
|---|
| 1287 | n/a | |
|---|
| 1288 | n/a | To pass exception information, use the keyword argument exc_info with |
|---|
| 1289 | n/a | a true value, e.g. |
|---|
| 1290 | n/a | |
|---|
| 1291 | n/a | logger.debug("Houston, we have a %s", "thorny problem", exc_info=1) |
|---|
| 1292 | n/a | """ |
|---|
| 1293 | n/a | if self.isEnabledFor(DEBUG): |
|---|
| 1294 | n/a | self._log(DEBUG, msg, args, **kwargs) |
|---|
| 1295 | n/a | |
|---|
| 1296 | n/a | def info(self, msg, *args, **kwargs): |
|---|
| 1297 | n/a | """ |
|---|
| 1298 | n/a | Log 'msg % args' with severity 'INFO'. |
|---|
| 1299 | n/a | |
|---|
| 1300 | n/a | To pass exception information, use the keyword argument exc_info with |
|---|
| 1301 | n/a | a true value, e.g. |
|---|
| 1302 | n/a | |
|---|
| 1303 | n/a | logger.info("Houston, we have a %s", "interesting problem", exc_info=1) |
|---|
| 1304 | n/a | """ |
|---|
| 1305 | n/a | if self.isEnabledFor(INFO): |
|---|
| 1306 | n/a | self._log(INFO, msg, args, **kwargs) |
|---|
| 1307 | n/a | |
|---|
| 1308 | n/a | def warning(self, msg, *args, **kwargs): |
|---|
| 1309 | n/a | """ |
|---|
| 1310 | n/a | Log 'msg % args' with severity 'WARNING'. |
|---|
| 1311 | n/a | |
|---|
| 1312 | n/a | To pass exception information, use the keyword argument exc_info with |
|---|
| 1313 | n/a | a true value, e.g. |
|---|
| 1314 | n/a | |
|---|
| 1315 | n/a | logger.warning("Houston, we have a %s", "bit of a problem", exc_info=1) |
|---|
| 1316 | n/a | """ |
|---|
| 1317 | n/a | if self.isEnabledFor(WARNING): |
|---|
| 1318 | n/a | self._log(WARNING, msg, args, **kwargs) |
|---|
| 1319 | n/a | |
|---|
| 1320 | n/a | def warn(self, msg, *args, **kwargs): |
|---|
| 1321 | n/a | warnings.warn("The 'warn' method is deprecated, " |
|---|
| 1322 | n/a | "use 'warning' instead", DeprecationWarning, 2) |
|---|
| 1323 | n/a | self.warning(msg, *args, **kwargs) |
|---|
| 1324 | n/a | |
|---|
| 1325 | n/a | def error(self, msg, *args, **kwargs): |
|---|
| 1326 | n/a | """ |
|---|
| 1327 | n/a | Log 'msg % args' with severity 'ERROR'. |
|---|
| 1328 | n/a | |
|---|
| 1329 | n/a | To pass exception information, use the keyword argument exc_info with |
|---|
| 1330 | n/a | a true value, e.g. |
|---|
| 1331 | n/a | |
|---|
| 1332 | n/a | logger.error("Houston, we have a %s", "major problem", exc_info=1) |
|---|
| 1333 | n/a | """ |
|---|
| 1334 | n/a | if self.isEnabledFor(ERROR): |
|---|
| 1335 | n/a | self._log(ERROR, msg, args, **kwargs) |
|---|
| 1336 | n/a | |
|---|
| 1337 | n/a | def exception(self, msg, *args, exc_info=True, **kwargs): |
|---|
| 1338 | n/a | """ |
|---|
| 1339 | n/a | Convenience method for logging an ERROR with exception information. |
|---|
| 1340 | n/a | """ |
|---|
| 1341 | n/a | self.error(msg, *args, exc_info=exc_info, **kwargs) |
|---|
| 1342 | n/a | |
|---|
| 1343 | n/a | def critical(self, msg, *args, **kwargs): |
|---|
| 1344 | n/a | """ |
|---|
| 1345 | n/a | Log 'msg % args' with severity 'CRITICAL'. |
|---|
| 1346 | n/a | |
|---|
| 1347 | n/a | To pass exception information, use the keyword argument exc_info with |
|---|
| 1348 | n/a | a true value, e.g. |
|---|
| 1349 | n/a | |
|---|
| 1350 | n/a | logger.critical("Houston, we have a %s", "major disaster", exc_info=1) |
|---|
| 1351 | n/a | """ |
|---|
| 1352 | n/a | if self.isEnabledFor(CRITICAL): |
|---|
| 1353 | n/a | self._log(CRITICAL, msg, args, **kwargs) |
|---|
| 1354 | n/a | |
|---|
| 1355 | n/a | fatal = critical |
|---|
| 1356 | n/a | |
|---|
| 1357 | n/a | def log(self, level, msg, *args, **kwargs): |
|---|
| 1358 | n/a | """ |
|---|
| 1359 | n/a | Log 'msg % args' with the integer severity 'level'. |
|---|
| 1360 | n/a | |
|---|
| 1361 | n/a | To pass exception information, use the keyword argument exc_info with |
|---|
| 1362 | n/a | a true value, e.g. |
|---|
| 1363 | n/a | |
|---|
| 1364 | n/a | logger.log(level, "We have a %s", "mysterious problem", exc_info=1) |
|---|
| 1365 | n/a | """ |
|---|
| 1366 | n/a | if not isinstance(level, int): |
|---|
| 1367 | n/a | if raiseExceptions: |
|---|
| 1368 | n/a | raise TypeError("level must be an integer") |
|---|
| 1369 | n/a | else: |
|---|
| 1370 | n/a | return |
|---|
| 1371 | n/a | if self.isEnabledFor(level): |
|---|
| 1372 | n/a | self._log(level, msg, args, **kwargs) |
|---|
| 1373 | n/a | |
|---|
| 1374 | n/a | def findCaller(self, stack_info=False): |
|---|
| 1375 | n/a | """ |
|---|
| 1376 | n/a | Find the stack frame of the caller so that we can note the source |
|---|
| 1377 | n/a | file name, line number and function name. |
|---|
| 1378 | n/a | """ |
|---|
| 1379 | n/a | f = currentframe() |
|---|
| 1380 | n/a | #On some versions of IronPython, currentframe() returns None if |
|---|
| 1381 | n/a | #IronPython isn't run with -X:Frames. |
|---|
| 1382 | n/a | if f is not None: |
|---|
| 1383 | n/a | f = f.f_back |
|---|
| 1384 | n/a | rv = "(unknown file)", 0, "(unknown function)", None |
|---|
| 1385 | n/a | while hasattr(f, "f_code"): |
|---|
| 1386 | n/a | co = f.f_code |
|---|
| 1387 | n/a | filename = os.path.normcase(co.co_filename) |
|---|
| 1388 | n/a | if filename == _srcfile: |
|---|
| 1389 | n/a | f = f.f_back |
|---|
| 1390 | n/a | continue |
|---|
| 1391 | n/a | sinfo = None |
|---|
| 1392 | n/a | if stack_info: |
|---|
| 1393 | n/a | sio = io.StringIO() |
|---|
| 1394 | n/a | sio.write('Stack (most recent call last):\n') |
|---|
| 1395 | n/a | traceback.print_stack(f, file=sio) |
|---|
| 1396 | n/a | sinfo = sio.getvalue() |
|---|
| 1397 | n/a | if sinfo[-1] == '\n': |
|---|
| 1398 | n/a | sinfo = sinfo[:-1] |
|---|
| 1399 | n/a | sio.close() |
|---|
| 1400 | n/a | rv = (co.co_filename, f.f_lineno, co.co_name, sinfo) |
|---|
| 1401 | n/a | break |
|---|
| 1402 | n/a | return rv |
|---|
| 1403 | n/a | |
|---|
| 1404 | n/a | def makeRecord(self, name, level, fn, lno, msg, args, exc_info, |
|---|
| 1405 | n/a | func=None, extra=None, sinfo=None): |
|---|
| 1406 | n/a | """ |
|---|
| 1407 | n/a | A factory method which can be overridden in subclasses to create |
|---|
| 1408 | n/a | specialized LogRecords. |
|---|
| 1409 | n/a | """ |
|---|
| 1410 | n/a | rv = _logRecordFactory(name, level, fn, lno, msg, args, exc_info, func, |
|---|
| 1411 | n/a | sinfo) |
|---|
| 1412 | n/a | if extra is not None: |
|---|
| 1413 | n/a | for key in extra: |
|---|
| 1414 | n/a | if (key in ["message", "asctime"]) or (key in rv.__dict__): |
|---|
| 1415 | n/a | raise KeyError("Attempt to overwrite %r in LogRecord" % key) |
|---|
| 1416 | n/a | rv.__dict__[key] = extra[key] |
|---|
| 1417 | n/a | return rv |
|---|
| 1418 | n/a | |
|---|
| 1419 | n/a | def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False): |
|---|
| 1420 | n/a | """ |
|---|
| 1421 | n/a | Low-level logging routine which creates a LogRecord and then calls |
|---|
| 1422 | n/a | all the handlers of this logger to handle the record. |
|---|
| 1423 | n/a | """ |
|---|
| 1424 | n/a | sinfo = None |
|---|
| 1425 | n/a | if _srcfile: |
|---|
| 1426 | n/a | #IronPython doesn't track Python frames, so findCaller raises an |
|---|
| 1427 | n/a | #exception on some versions of IronPython. We trap it here so that |
|---|
| 1428 | n/a | #IronPython can use logging. |
|---|
| 1429 | n/a | try: |
|---|
| 1430 | n/a | fn, lno, func, sinfo = self.findCaller(stack_info) |
|---|
| 1431 | n/a | except ValueError: # pragma: no cover |
|---|
| 1432 | n/a | fn, lno, func = "(unknown file)", 0, "(unknown function)" |
|---|
| 1433 | n/a | else: # pragma: no cover |
|---|
| 1434 | n/a | fn, lno, func = "(unknown file)", 0, "(unknown function)" |
|---|
| 1435 | n/a | if exc_info: |
|---|
| 1436 | n/a | if isinstance(exc_info, BaseException): |
|---|
| 1437 | n/a | exc_info = (type(exc_info), exc_info, exc_info.__traceback__) |
|---|
| 1438 | n/a | elif not isinstance(exc_info, tuple): |
|---|
| 1439 | n/a | exc_info = sys.exc_info() |
|---|
| 1440 | n/a | record = self.makeRecord(self.name, level, fn, lno, msg, args, |
|---|
| 1441 | n/a | exc_info, func, extra, sinfo) |
|---|
| 1442 | n/a | self.handle(record) |
|---|
| 1443 | n/a | |
|---|
| 1444 | n/a | def handle(self, record): |
|---|
| 1445 | n/a | """ |
|---|
| 1446 | n/a | Call the handlers for the specified record. |
|---|
| 1447 | n/a | |
|---|
| 1448 | n/a | This method is used for unpickled records received from a socket, as |
|---|
| 1449 | n/a | well as those created locally. Logger-level filtering is applied. |
|---|
| 1450 | n/a | """ |
|---|
| 1451 | n/a | if (not self.disabled) and self.filter(record): |
|---|
| 1452 | n/a | self.callHandlers(record) |
|---|
| 1453 | n/a | |
|---|
| 1454 | n/a | def addHandler(self, hdlr): |
|---|
| 1455 | n/a | """ |
|---|
| 1456 | n/a | Add the specified handler to this logger. |
|---|
| 1457 | n/a | """ |
|---|
| 1458 | n/a | _acquireLock() |
|---|
| 1459 | n/a | try: |
|---|
| 1460 | n/a | if not (hdlr in self.handlers): |
|---|
| 1461 | n/a | self.handlers.append(hdlr) |
|---|
| 1462 | n/a | finally: |
|---|
| 1463 | n/a | _releaseLock() |
|---|
| 1464 | n/a | |
|---|
| 1465 | n/a | def removeHandler(self, hdlr): |
|---|
| 1466 | n/a | """ |
|---|
| 1467 | n/a | Remove the specified handler from this logger. |
|---|
| 1468 | n/a | """ |
|---|
| 1469 | n/a | _acquireLock() |
|---|
| 1470 | n/a | try: |
|---|
| 1471 | n/a | if hdlr in self.handlers: |
|---|
| 1472 | n/a | self.handlers.remove(hdlr) |
|---|
| 1473 | n/a | finally: |
|---|
| 1474 | n/a | _releaseLock() |
|---|
| 1475 | n/a | |
|---|
| 1476 | n/a | def hasHandlers(self): |
|---|
| 1477 | n/a | """ |
|---|
| 1478 | n/a | See if this logger has any handlers configured. |
|---|
| 1479 | n/a | |
|---|
| 1480 | n/a | Loop through all handlers for this logger and its parents in the |
|---|
| 1481 | n/a | logger hierarchy. Return True if a handler was found, else False. |
|---|
| 1482 | n/a | Stop searching up the hierarchy whenever a logger with the "propagate" |
|---|
| 1483 | n/a | attribute set to zero is found - that will be the last logger which |
|---|
| 1484 | n/a | is checked for the existence of handlers. |
|---|
| 1485 | n/a | """ |
|---|
| 1486 | n/a | c = self |
|---|
| 1487 | n/a | rv = False |
|---|
| 1488 | n/a | while c: |
|---|
| 1489 | n/a | if c.handlers: |
|---|
| 1490 | n/a | rv = True |
|---|
| 1491 | n/a | break |
|---|
| 1492 | n/a | if not c.propagate: |
|---|
| 1493 | n/a | break |
|---|
| 1494 | n/a | else: |
|---|
| 1495 | n/a | c = c.parent |
|---|
| 1496 | n/a | return rv |
|---|
| 1497 | n/a | |
|---|
| 1498 | n/a | def callHandlers(self, record): |
|---|
| 1499 | n/a | """ |
|---|
| 1500 | n/a | Pass a record to all relevant handlers. |
|---|
| 1501 | n/a | |
|---|
| 1502 | n/a | Loop through all handlers for this logger and its parents in the |
|---|
| 1503 | n/a | logger hierarchy. If no handler was found, output a one-off error |
|---|
| 1504 | n/a | message to sys.stderr. Stop searching up the hierarchy whenever a |
|---|
| 1505 | n/a | logger with the "propagate" attribute set to zero is found - that |
|---|
| 1506 | n/a | will be the last logger whose handlers are called. |
|---|
| 1507 | n/a | """ |
|---|
| 1508 | n/a | c = self |
|---|
| 1509 | n/a | found = 0 |
|---|
| 1510 | n/a | while c: |
|---|
| 1511 | n/a | for hdlr in c.handlers: |
|---|
| 1512 | n/a | found = found + 1 |
|---|
| 1513 | n/a | if record.levelno >= hdlr.level: |
|---|
| 1514 | n/a | hdlr.handle(record) |
|---|
| 1515 | n/a | if not c.propagate: |
|---|
| 1516 | n/a | c = None #break out |
|---|
| 1517 | n/a | else: |
|---|
| 1518 | n/a | c = c.parent |
|---|
| 1519 | n/a | if (found == 0): |
|---|
| 1520 | n/a | if lastResort: |
|---|
| 1521 | n/a | if record.levelno >= lastResort.level: |
|---|
| 1522 | n/a | lastResort.handle(record) |
|---|
| 1523 | n/a | elif raiseExceptions and not self.manager.emittedNoHandlerWarning: |
|---|
| 1524 | n/a | sys.stderr.write("No handlers could be found for logger" |
|---|
| 1525 | n/a | " \"%s\"\n" % self.name) |
|---|
| 1526 | n/a | self.manager.emittedNoHandlerWarning = True |
|---|
| 1527 | n/a | |
|---|
| 1528 | n/a | def getEffectiveLevel(self): |
|---|
| 1529 | n/a | """ |
|---|
| 1530 | n/a | Get the effective level for this logger. |
|---|
| 1531 | n/a | |
|---|
| 1532 | n/a | Loop through this logger and its parents in the logger hierarchy, |
|---|
| 1533 | n/a | looking for a non-zero logging level. Return the first one found. |
|---|
| 1534 | n/a | """ |
|---|
| 1535 | n/a | logger = self |
|---|
| 1536 | n/a | while logger: |
|---|
| 1537 | n/a | if logger.level: |
|---|
| 1538 | n/a | return logger.level |
|---|
| 1539 | n/a | logger = logger.parent |
|---|
| 1540 | n/a | return NOTSET |
|---|
| 1541 | n/a | |
|---|
| 1542 | n/a | def isEnabledFor(self, level): |
|---|
| 1543 | n/a | """ |
|---|
| 1544 | n/a | Is this logger enabled for level 'level'? |
|---|
| 1545 | n/a | """ |
|---|
| 1546 | n/a | if self.manager.disable >= level: |
|---|
| 1547 | n/a | return False |
|---|
| 1548 | n/a | return level >= self.getEffectiveLevel() |
|---|
| 1549 | n/a | |
|---|
| 1550 | n/a | def getChild(self, suffix): |
|---|
| 1551 | n/a | """ |
|---|
| 1552 | n/a | Get a logger which is a descendant to this one. |
|---|
| 1553 | n/a | |
|---|
| 1554 | n/a | This is a convenience method, such that |
|---|
| 1555 | n/a | |
|---|
| 1556 | n/a | logging.getLogger('abc').getChild('def.ghi') |
|---|
| 1557 | n/a | |
|---|
| 1558 | n/a | is the same as |
|---|
| 1559 | n/a | |
|---|
| 1560 | n/a | logging.getLogger('abc.def.ghi') |
|---|
| 1561 | n/a | |
|---|
| 1562 | n/a | It's useful, for example, when the parent logger is named using |
|---|
| 1563 | n/a | __name__ rather than a literal string. |
|---|
| 1564 | n/a | """ |
|---|
| 1565 | n/a | if self.root is not self: |
|---|
| 1566 | n/a | suffix = '.'.join((self.name, suffix)) |
|---|
| 1567 | n/a | return self.manager.getLogger(suffix) |
|---|
| 1568 | n/a | |
|---|
| 1569 | n/a | def __repr__(self): |
|---|
| 1570 | n/a | level = getLevelName(self.getEffectiveLevel()) |
|---|
| 1571 | n/a | return '<%s %s (%s)>' % (self.__class__.__name__, self.name, level) |
|---|
| 1572 | n/a | |
|---|
| 1573 | n/a | |
|---|
| 1574 | n/a | class RootLogger(Logger): |
|---|
| 1575 | n/a | """ |
|---|
| 1576 | n/a | A root logger is not that different to any other logger, except that |
|---|
| 1577 | n/a | it must have a logging level and there is only one instance of it in |
|---|
| 1578 | n/a | the hierarchy. |
|---|
| 1579 | n/a | """ |
|---|
| 1580 | n/a | def __init__(self, level): |
|---|
| 1581 | n/a | """ |
|---|
| 1582 | n/a | Initialize the logger with the name "root". |
|---|
| 1583 | n/a | """ |
|---|
| 1584 | n/a | Logger.__init__(self, "root", level) |
|---|
| 1585 | n/a | |
|---|
| 1586 | n/a | _loggerClass = Logger |
|---|
| 1587 | n/a | |
|---|
| 1588 | n/a | class LoggerAdapter(object): |
|---|
| 1589 | n/a | """ |
|---|
| 1590 | n/a | An adapter for loggers which makes it easier to specify contextual |
|---|
| 1591 | n/a | information in logging output. |
|---|
| 1592 | n/a | """ |
|---|
| 1593 | n/a | |
|---|
| 1594 | n/a | def __init__(self, logger, extra): |
|---|
| 1595 | n/a | """ |
|---|
| 1596 | n/a | Initialize the adapter with a logger and a dict-like object which |
|---|
| 1597 | n/a | provides contextual information. This constructor signature allows |
|---|
| 1598 | n/a | easy stacking of LoggerAdapters, if so desired. |
|---|
| 1599 | n/a | |
|---|
| 1600 | n/a | You can effectively pass keyword arguments as shown in the |
|---|
| 1601 | n/a | following example: |
|---|
| 1602 | n/a | |
|---|
| 1603 | n/a | adapter = LoggerAdapter(someLogger, dict(p1=v1, p2="v2")) |
|---|
| 1604 | n/a | """ |
|---|
| 1605 | n/a | self.logger = logger |
|---|
| 1606 | n/a | self.extra = extra |
|---|
| 1607 | n/a | |
|---|
| 1608 | n/a | def process(self, msg, kwargs): |
|---|
| 1609 | n/a | """ |
|---|
| 1610 | n/a | Process the logging message and keyword arguments passed in to |
|---|
| 1611 | n/a | a logging call to insert contextual information. You can either |
|---|
| 1612 | n/a | manipulate the message itself, the keyword args or both. Return |
|---|
| 1613 | n/a | the message and kwargs modified (or not) to suit your needs. |
|---|
| 1614 | n/a | |
|---|
| 1615 | n/a | Normally, you'll only need to override this one method in a |
|---|
| 1616 | n/a | LoggerAdapter subclass for your specific needs. |
|---|
| 1617 | n/a | """ |
|---|
| 1618 | n/a | kwargs["extra"] = self.extra |
|---|
| 1619 | n/a | return msg, kwargs |
|---|
| 1620 | n/a | |
|---|
| 1621 | n/a | # |
|---|
| 1622 | n/a | # Boilerplate convenience methods |
|---|
| 1623 | n/a | # |
|---|
| 1624 | n/a | def debug(self, msg, *args, **kwargs): |
|---|
| 1625 | n/a | """ |
|---|
| 1626 | n/a | Delegate a debug call to the underlying logger. |
|---|
| 1627 | n/a | """ |
|---|
| 1628 | n/a | self.log(DEBUG, msg, *args, **kwargs) |
|---|
| 1629 | n/a | |
|---|
| 1630 | n/a | def info(self, msg, *args, **kwargs): |
|---|
| 1631 | n/a | """ |
|---|
| 1632 | n/a | Delegate an info call to the underlying logger. |
|---|
| 1633 | n/a | """ |
|---|
| 1634 | n/a | self.log(INFO, msg, *args, **kwargs) |
|---|
| 1635 | n/a | |
|---|
| 1636 | n/a | def warning(self, msg, *args, **kwargs): |
|---|
| 1637 | n/a | """ |
|---|
| 1638 | n/a | Delegate a warning call to the underlying logger. |
|---|
| 1639 | n/a | """ |
|---|
| 1640 | n/a | self.log(WARNING, msg, *args, **kwargs) |
|---|
| 1641 | n/a | |
|---|
| 1642 | n/a | def warn(self, msg, *args, **kwargs): |
|---|
| 1643 | n/a | warnings.warn("The 'warn' method is deprecated, " |
|---|
| 1644 | n/a | "use 'warning' instead", DeprecationWarning, 2) |
|---|
| 1645 | n/a | self.warning(msg, *args, **kwargs) |
|---|
| 1646 | n/a | |
|---|
| 1647 | n/a | def error(self, msg, *args, **kwargs): |
|---|
| 1648 | n/a | """ |
|---|
| 1649 | n/a | Delegate an error call to the underlying logger. |
|---|
| 1650 | n/a | """ |
|---|
| 1651 | n/a | self.log(ERROR, msg, *args, **kwargs) |
|---|
| 1652 | n/a | |
|---|
| 1653 | n/a | def exception(self, msg, *args, exc_info=True, **kwargs): |
|---|
| 1654 | n/a | """ |
|---|
| 1655 | n/a | Delegate an exception call to the underlying logger. |
|---|
| 1656 | n/a | """ |
|---|
| 1657 | n/a | self.log(ERROR, msg, *args, exc_info=exc_info, **kwargs) |
|---|
| 1658 | n/a | |
|---|
| 1659 | n/a | def critical(self, msg, *args, **kwargs): |
|---|
| 1660 | n/a | """ |
|---|
| 1661 | n/a | Delegate a critical call to the underlying logger. |
|---|
| 1662 | n/a | """ |
|---|
| 1663 | n/a | self.log(CRITICAL, msg, *args, **kwargs) |
|---|
| 1664 | n/a | |
|---|
| 1665 | n/a | def log(self, level, msg, *args, **kwargs): |
|---|
| 1666 | n/a | """ |
|---|
| 1667 | n/a | Delegate a log call to the underlying logger, after adding |
|---|
| 1668 | n/a | contextual information from this adapter instance. |
|---|
| 1669 | n/a | """ |
|---|
| 1670 | n/a | if self.isEnabledFor(level): |
|---|
| 1671 | n/a | msg, kwargs = self.process(msg, kwargs) |
|---|
| 1672 | n/a | self.logger._log(level, msg, args, **kwargs) |
|---|
| 1673 | n/a | |
|---|
| 1674 | n/a | def isEnabledFor(self, level): |
|---|
| 1675 | n/a | """ |
|---|
| 1676 | n/a | Is this logger enabled for level 'level'? |
|---|
| 1677 | n/a | """ |
|---|
| 1678 | n/a | if self.logger.manager.disable >= level: |
|---|
| 1679 | n/a | return False |
|---|
| 1680 | n/a | return level >= self.getEffectiveLevel() |
|---|
| 1681 | n/a | |
|---|
| 1682 | n/a | def setLevel(self, level): |
|---|
| 1683 | n/a | """ |
|---|
| 1684 | n/a | Set the specified level on the underlying logger. |
|---|
| 1685 | n/a | """ |
|---|
| 1686 | n/a | self.logger.setLevel(level) |
|---|
| 1687 | n/a | |
|---|
| 1688 | n/a | def getEffectiveLevel(self): |
|---|
| 1689 | n/a | """ |
|---|
| 1690 | n/a | Get the effective level for the underlying logger. |
|---|
| 1691 | n/a | """ |
|---|
| 1692 | n/a | return self.logger.getEffectiveLevel() |
|---|
| 1693 | n/a | |
|---|
| 1694 | n/a | def hasHandlers(self): |
|---|
| 1695 | n/a | """ |
|---|
| 1696 | n/a | See if the underlying logger has any handlers. |
|---|
| 1697 | n/a | """ |
|---|
| 1698 | n/a | return self.logger.hasHandlers() |
|---|
| 1699 | n/a | |
|---|
| 1700 | n/a | def __repr__(self): |
|---|
| 1701 | n/a | logger = self.logger |
|---|
| 1702 | n/a | level = getLevelName(logger.getEffectiveLevel()) |
|---|
| 1703 | n/a | return '<%s %s (%s)>' % (self.__class__.__name__, logger.name, level) |
|---|
| 1704 | n/a | |
|---|
| 1705 | n/a | root = RootLogger(WARNING) |
|---|
| 1706 | n/a | Logger.root = root |
|---|
| 1707 | n/a | Logger.manager = Manager(Logger.root) |
|---|
| 1708 | n/a | |
|---|
| 1709 | n/a | #--------------------------------------------------------------------------- |
|---|
| 1710 | n/a | # Configuration classes and functions |
|---|
| 1711 | n/a | #--------------------------------------------------------------------------- |
|---|
| 1712 | n/a | |
|---|
| 1713 | n/a | def basicConfig(**kwargs): |
|---|
| 1714 | n/a | """ |
|---|
| 1715 | n/a | Do basic configuration for the logging system. |
|---|
| 1716 | n/a | |
|---|
| 1717 | n/a | This function does nothing if the root logger already has handlers |
|---|
| 1718 | n/a | configured. It is a convenience method intended for use by simple scripts |
|---|
| 1719 | n/a | to do one-shot configuration of the logging package. |
|---|
| 1720 | n/a | |
|---|
| 1721 | n/a | The default behaviour is to create a StreamHandler which writes to |
|---|
| 1722 | n/a | sys.stderr, set a formatter using the BASIC_FORMAT format string, and |
|---|
| 1723 | n/a | add the handler to the root logger. |
|---|
| 1724 | n/a | |
|---|
| 1725 | n/a | A number of optional keyword arguments may be specified, which can alter |
|---|
| 1726 | n/a | the default behaviour. |
|---|
| 1727 | n/a | |
|---|
| 1728 | n/a | filename Specifies that a FileHandler be created, using the specified |
|---|
| 1729 | n/a | filename, rather than a StreamHandler. |
|---|
| 1730 | n/a | filemode Specifies the mode to open the file, if filename is specified |
|---|
| 1731 | n/a | (if filemode is unspecified, it defaults to 'a'). |
|---|
| 1732 | n/a | format Use the specified format string for the handler. |
|---|
| 1733 | n/a | datefmt Use the specified date/time format. |
|---|
| 1734 | n/a | style If a format string is specified, use this to specify the |
|---|
| 1735 | n/a | type of format string (possible values '%', '{', '$', for |
|---|
| 1736 | n/a | %-formatting, :meth:`str.format` and :class:`string.Template` |
|---|
| 1737 | n/a | - defaults to '%'). |
|---|
| 1738 | n/a | level Set the root logger level to the specified level. |
|---|
| 1739 | n/a | stream Use the specified stream to initialize the StreamHandler. Note |
|---|
| 1740 | n/a | that this argument is incompatible with 'filename' - if both |
|---|
| 1741 | n/a | are present, 'stream' is ignored. |
|---|
| 1742 | n/a | handlers If specified, this should be an iterable of already created |
|---|
| 1743 | n/a | handlers, which will be added to the root handler. Any handler |
|---|
| 1744 | n/a | in the list which does not have a formatter assigned will be |
|---|
| 1745 | n/a | assigned the formatter created in this function. |
|---|
| 1746 | n/a | |
|---|
| 1747 | n/a | Note that you could specify a stream created using open(filename, mode) |
|---|
| 1748 | n/a | rather than passing the filename and mode in. However, it should be |
|---|
| 1749 | n/a | remembered that StreamHandler does not close its stream (since it may be |
|---|
| 1750 | n/a | using sys.stdout or sys.stderr), whereas FileHandler closes its stream |
|---|
| 1751 | n/a | when the handler is closed. |
|---|
| 1752 | n/a | |
|---|
| 1753 | n/a | .. versionchanged:: 3.2 |
|---|
| 1754 | n/a | Added the ``style`` parameter. |
|---|
| 1755 | n/a | |
|---|
| 1756 | n/a | .. versionchanged:: 3.3 |
|---|
| 1757 | n/a | Added the ``handlers`` parameter. A ``ValueError`` is now thrown for |
|---|
| 1758 | n/a | incompatible arguments (e.g. ``handlers`` specified together with |
|---|
| 1759 | n/a | ``filename``/``filemode``, or ``filename``/``filemode`` specified |
|---|
| 1760 | n/a | together with ``stream``, or ``handlers`` specified together with |
|---|
| 1761 | n/a | ``stream``. |
|---|
| 1762 | n/a | """ |
|---|
| 1763 | n/a | # Add thread safety in case someone mistakenly calls |
|---|
| 1764 | n/a | # basicConfig() from multiple threads |
|---|
| 1765 | n/a | _acquireLock() |
|---|
| 1766 | n/a | try: |
|---|
| 1767 | n/a | if len(root.handlers) == 0: |
|---|
| 1768 | n/a | handlers = kwargs.pop("handlers", None) |
|---|
| 1769 | n/a | if handlers is None: |
|---|
| 1770 | n/a | if "stream" in kwargs and "filename" in kwargs: |
|---|
| 1771 | n/a | raise ValueError("'stream' and 'filename' should not be " |
|---|
| 1772 | n/a | "specified together") |
|---|
| 1773 | n/a | else: |
|---|
| 1774 | n/a | if "stream" in kwargs or "filename" in kwargs: |
|---|
| 1775 | n/a | raise ValueError("'stream' or 'filename' should not be " |
|---|
| 1776 | n/a | "specified together with 'handlers'") |
|---|
| 1777 | n/a | if handlers is None: |
|---|
| 1778 | n/a | filename = kwargs.pop("filename", None) |
|---|
| 1779 | n/a | mode = kwargs.pop("filemode", 'a') |
|---|
| 1780 | n/a | if filename: |
|---|
| 1781 | n/a | h = FileHandler(filename, mode) |
|---|
| 1782 | n/a | else: |
|---|
| 1783 | n/a | stream = kwargs.pop("stream", None) |
|---|
| 1784 | n/a | h = StreamHandler(stream) |
|---|
| 1785 | n/a | handlers = [h] |
|---|
| 1786 | n/a | dfs = kwargs.pop("datefmt", None) |
|---|
| 1787 | n/a | style = kwargs.pop("style", '%') |
|---|
| 1788 | n/a | if style not in _STYLES: |
|---|
| 1789 | n/a | raise ValueError('Style must be one of: %s' % ','.join( |
|---|
| 1790 | n/a | _STYLES.keys())) |
|---|
| 1791 | n/a | fs = kwargs.pop("format", _STYLES[style][1]) |
|---|
| 1792 | n/a | fmt = Formatter(fs, dfs, style) |
|---|
| 1793 | n/a | for h in handlers: |
|---|
| 1794 | n/a | if h.formatter is None: |
|---|
| 1795 | n/a | h.setFormatter(fmt) |
|---|
| 1796 | n/a | root.addHandler(h) |
|---|
| 1797 | n/a | level = kwargs.pop("level", None) |
|---|
| 1798 | n/a | if level is not None: |
|---|
| 1799 | n/a | root.setLevel(level) |
|---|
| 1800 | n/a | if kwargs: |
|---|
| 1801 | n/a | keys = ', '.join(kwargs.keys()) |
|---|
| 1802 | n/a | raise ValueError('Unrecognised argument(s): %s' % keys) |
|---|
| 1803 | n/a | finally: |
|---|
| 1804 | n/a | _releaseLock() |
|---|
| 1805 | n/a | |
|---|
| 1806 | n/a | #--------------------------------------------------------------------------- |
|---|
| 1807 | n/a | # Utility functions at module level. |
|---|
| 1808 | n/a | # Basically delegate everything to the root logger. |
|---|
| 1809 | n/a | #--------------------------------------------------------------------------- |
|---|
| 1810 | n/a | |
|---|
| 1811 | n/a | def getLogger(name=None): |
|---|
| 1812 | n/a | """ |
|---|
| 1813 | n/a | Return a logger with the specified name, creating it if necessary. |
|---|
| 1814 | n/a | |
|---|
| 1815 | n/a | If no name is specified, return the root logger. |
|---|
| 1816 | n/a | """ |
|---|
| 1817 | n/a | if name: |
|---|
| 1818 | n/a | return Logger.manager.getLogger(name) |
|---|
| 1819 | n/a | else: |
|---|
| 1820 | n/a | return root |
|---|
| 1821 | n/a | |
|---|
| 1822 | n/a | def critical(msg, *args, **kwargs): |
|---|
| 1823 | n/a | """ |
|---|
| 1824 | n/a | Log a message with severity 'CRITICAL' on the root logger. If the logger |
|---|
| 1825 | n/a | has no handlers, call basicConfig() to add a console handler with a |
|---|
| 1826 | n/a | pre-defined format. |
|---|
| 1827 | n/a | """ |
|---|
| 1828 | n/a | if len(root.handlers) == 0: |
|---|
| 1829 | n/a | basicConfig() |
|---|
| 1830 | n/a | root.critical(msg, *args, **kwargs) |
|---|
| 1831 | n/a | |
|---|
| 1832 | n/a | fatal = critical |
|---|
| 1833 | n/a | |
|---|
| 1834 | n/a | def error(msg, *args, **kwargs): |
|---|
| 1835 | n/a | """ |
|---|
| 1836 | n/a | Log a message with severity 'ERROR' on the root logger. If the logger has |
|---|
| 1837 | n/a | no handlers, call basicConfig() to add a console handler with a pre-defined |
|---|
| 1838 | n/a | format. |
|---|
| 1839 | n/a | """ |
|---|
| 1840 | n/a | if len(root.handlers) == 0: |
|---|
| 1841 | n/a | basicConfig() |
|---|
| 1842 | n/a | root.error(msg, *args, **kwargs) |
|---|
| 1843 | n/a | |
|---|
| 1844 | n/a | def exception(msg, *args, exc_info=True, **kwargs): |
|---|
| 1845 | n/a | """ |
|---|
| 1846 | n/a | Log a message with severity 'ERROR' on the root logger, with exception |
|---|
| 1847 | n/a | information. If the logger has no handlers, basicConfig() is called to add |
|---|
| 1848 | n/a | a console handler with a pre-defined format. |
|---|
| 1849 | n/a | """ |
|---|
| 1850 | n/a | error(msg, *args, exc_info=exc_info, **kwargs) |
|---|
| 1851 | n/a | |
|---|
| 1852 | n/a | def warning(msg, *args, **kwargs): |
|---|
| 1853 | n/a | """ |
|---|
| 1854 | n/a | Log a message with severity 'WARNING' on the root logger. If the logger has |
|---|
| 1855 | n/a | no handlers, call basicConfig() to add a console handler with a pre-defined |
|---|
| 1856 | n/a | format. |
|---|
| 1857 | n/a | """ |
|---|
| 1858 | n/a | if len(root.handlers) == 0: |
|---|
| 1859 | n/a | basicConfig() |
|---|
| 1860 | n/a | root.warning(msg, *args, **kwargs) |
|---|
| 1861 | n/a | |
|---|
| 1862 | n/a | def warn(msg, *args, **kwargs): |
|---|
| 1863 | n/a | warnings.warn("The 'warn' function is deprecated, " |
|---|
| 1864 | n/a | "use 'warning' instead", DeprecationWarning, 2) |
|---|
| 1865 | n/a | warning(msg, *args, **kwargs) |
|---|
| 1866 | n/a | |
|---|
| 1867 | n/a | def info(msg, *args, **kwargs): |
|---|
| 1868 | n/a | """ |
|---|
| 1869 | n/a | Log a message with severity 'INFO' on the root logger. If the logger has |
|---|
| 1870 | n/a | no handlers, call basicConfig() to add a console handler with a pre-defined |
|---|
| 1871 | n/a | format. |
|---|
| 1872 | n/a | """ |
|---|
| 1873 | n/a | if len(root.handlers) == 0: |
|---|
| 1874 | n/a | basicConfig() |
|---|
| 1875 | n/a | root.info(msg, *args, **kwargs) |
|---|
| 1876 | n/a | |
|---|
| 1877 | n/a | def debug(msg, *args, **kwargs): |
|---|
| 1878 | n/a | """ |
|---|
| 1879 | n/a | Log a message with severity 'DEBUG' on the root logger. If the logger has |
|---|
| 1880 | n/a | no handlers, call basicConfig() to add a console handler with a pre-defined |
|---|
| 1881 | n/a | format. |
|---|
| 1882 | n/a | """ |
|---|
| 1883 | n/a | if len(root.handlers) == 0: |
|---|
| 1884 | n/a | basicConfig() |
|---|
| 1885 | n/a | root.debug(msg, *args, **kwargs) |
|---|
| 1886 | n/a | |
|---|
| 1887 | n/a | def log(level, msg, *args, **kwargs): |
|---|
| 1888 | n/a | """ |
|---|
| 1889 | n/a | Log 'msg % args' with the integer severity 'level' on the root logger. If |
|---|
| 1890 | n/a | the logger has no handlers, call basicConfig() to add a console handler |
|---|
| 1891 | n/a | with a pre-defined format. |
|---|
| 1892 | n/a | """ |
|---|
| 1893 | n/a | if len(root.handlers) == 0: |
|---|
| 1894 | n/a | basicConfig() |
|---|
| 1895 | n/a | root.log(level, msg, *args, **kwargs) |
|---|
| 1896 | n/a | |
|---|
| 1897 | n/a | def disable(level=CRITICAL): |
|---|
| 1898 | n/a | """ |
|---|
| 1899 | n/a | Disable all logging calls of severity 'level' and below. |
|---|
| 1900 | n/a | """ |
|---|
| 1901 | n/a | root.manager.disable = level |
|---|
| 1902 | n/a | |
|---|
| 1903 | n/a | def shutdown(handlerList=_handlerList): |
|---|
| 1904 | n/a | """ |
|---|
| 1905 | n/a | Perform any cleanup actions in the logging system (e.g. flushing |
|---|
| 1906 | n/a | buffers). |
|---|
| 1907 | n/a | |
|---|
| 1908 | n/a | Should be called at application exit. |
|---|
| 1909 | n/a | """ |
|---|
| 1910 | n/a | for wr in reversed(handlerList[:]): |
|---|
| 1911 | n/a | #errors might occur, for example, if files are locked |
|---|
| 1912 | n/a | #we just ignore them if raiseExceptions is not set |
|---|
| 1913 | n/a | try: |
|---|
| 1914 | n/a | h = wr() |
|---|
| 1915 | n/a | if h: |
|---|
| 1916 | n/a | try: |
|---|
| 1917 | n/a | h.acquire() |
|---|
| 1918 | n/a | h.flush() |
|---|
| 1919 | n/a | h.close() |
|---|
| 1920 | n/a | except (OSError, ValueError): |
|---|
| 1921 | n/a | # Ignore errors which might be caused |
|---|
| 1922 | n/a | # because handlers have been closed but |
|---|
| 1923 | n/a | # references to them are still around at |
|---|
| 1924 | n/a | # application exit. |
|---|
| 1925 | n/a | pass |
|---|
| 1926 | n/a | finally: |
|---|
| 1927 | n/a | h.release() |
|---|
| 1928 | n/a | except: # ignore everything, as we're shutting down |
|---|
| 1929 | n/a | if raiseExceptions: |
|---|
| 1930 | n/a | raise |
|---|
| 1931 | n/a | #else, swallow |
|---|
| 1932 | n/a | |
|---|
| 1933 | n/a | #Let's try and shutdown automatically on application exit... |
|---|
| 1934 | n/a | import atexit |
|---|
| 1935 | n/a | atexit.register(shutdown) |
|---|
| 1936 | n/a | |
|---|
| 1937 | n/a | # Null handler |
|---|
| 1938 | n/a | |
|---|
| 1939 | n/a | class NullHandler(Handler): |
|---|
| 1940 | n/a | """ |
|---|
| 1941 | n/a | This handler does nothing. It's intended to be used to avoid the |
|---|
| 1942 | n/a | "No handlers could be found for logger XXX" one-off warning. This is |
|---|
| 1943 | n/a | important for library code, which may contain code to log events. If a user |
|---|
| 1944 | n/a | of the library does not configure logging, the one-off warning might be |
|---|
| 1945 | n/a | produced; to avoid this, the library developer simply needs to instantiate |
|---|
| 1946 | n/a | a NullHandler and add it to the top-level logger of the library module or |
|---|
| 1947 | n/a | package. |
|---|
| 1948 | n/a | """ |
|---|
| 1949 | n/a | def handle(self, record): |
|---|
| 1950 | n/a | """Stub.""" |
|---|
| 1951 | n/a | |
|---|
| 1952 | n/a | def emit(self, record): |
|---|
| 1953 | n/a | """Stub.""" |
|---|
| 1954 | n/a | |
|---|
| 1955 | n/a | def createLock(self): |
|---|
| 1956 | n/a | self.lock = None |
|---|
| 1957 | n/a | |
|---|
| 1958 | n/a | # Warnings integration |
|---|
| 1959 | n/a | |
|---|
| 1960 | n/a | _warnings_showwarning = None |
|---|
| 1961 | n/a | |
|---|
| 1962 | n/a | def _showwarning(message, category, filename, lineno, file=None, line=None): |
|---|
| 1963 | n/a | """ |
|---|
| 1964 | n/a | Implementation of showwarnings which redirects to logging, which will first |
|---|
| 1965 | n/a | check to see if the file parameter is None. If a file is specified, it will |
|---|
| 1966 | n/a | delegate to the original warnings implementation of showwarning. Otherwise, |
|---|
| 1967 | n/a | it will call warnings.formatwarning and will log the resulting string to a |
|---|
| 1968 | n/a | warnings logger named "py.warnings" with level logging.WARNING. |
|---|
| 1969 | n/a | """ |
|---|
| 1970 | n/a | if file is not None: |
|---|
| 1971 | n/a | if _warnings_showwarning is not None: |
|---|
| 1972 | n/a | _warnings_showwarning(message, category, filename, lineno, file, line) |
|---|
| 1973 | n/a | else: |
|---|
| 1974 | n/a | s = warnings.formatwarning(message, category, filename, lineno, line) |
|---|
| 1975 | n/a | logger = getLogger("py.warnings") |
|---|
| 1976 | n/a | if not logger.handlers: |
|---|
| 1977 | n/a | logger.addHandler(NullHandler()) |
|---|
| 1978 | n/a | logger.warning("%s", s) |
|---|
| 1979 | n/a | |
|---|
| 1980 | n/a | def captureWarnings(capture): |
|---|
| 1981 | n/a | """ |
|---|
| 1982 | n/a | If capture is true, redirect all warnings to the logging package. |
|---|
| 1983 | n/a | If capture is False, ensure that warnings are not redirected to logging |
|---|
| 1984 | n/a | but to their original destinations. |
|---|
| 1985 | n/a | """ |
|---|
| 1986 | n/a | global _warnings_showwarning |
|---|
| 1987 | n/a | if capture: |
|---|
| 1988 | n/a | if _warnings_showwarning is None: |
|---|
| 1989 | n/a | _warnings_showwarning = warnings.showwarning |
|---|
| 1990 | n/a | warnings.showwarning = _showwarning |
|---|
| 1991 | n/a | else: |
|---|
| 1992 | n/a | if _warnings_showwarning is not None: |
|---|
| 1993 | n/a | warnings.showwarning = _warnings_showwarning |
|---|
| 1994 | n/a | _warnings_showwarning = None |
|---|