Coverage for python/lsst/log/log/logContinued.py : 45%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/env python
3#
4# LSST Data Management System
5# Copyright 2013 LSST Corporation.
6#
7# This product includes software developed by the
8# LSST Project (http://www.lsst.org/).
9#
10# This program is free software: you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation, either version 3 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the LSST License Statement and
21# the GNU General Public License along with this program. If not,
22# see <http://www.lsstcorp.org/LegalNotices/>.
23#
25__all__ = ["TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL",
26 "Log", "configure", "configure_prop", "getDefaultLogger", "getLogger",
27 "MDC", "MDCRemove", "MDCRegisterInit", "setLevel", "getLevel", "isEnabledFor",
28 "log", "trace", "debug", "info", "warn", "warning", "error", "fatal", "logf",
29 "tracef", "debugf", "infof", "warnf", "errorf", "fatalf", "lwpID",
30 "usePythonLogging", "doNotUsePythonLogging", "UsePythonLogging",
31 "LevelTranslator", "LogHandler"]
33import logging
34import inspect
35import os
37from lsst.utils import continueClass
39from .log import Log
41TRACE = 5000
42DEBUG = 10000
43INFO = 20000
44WARN = 30000
45ERROR = 40000
46FATAL = 50000
49@continueClass # noqa: F811 (FIXME: remove for py 3.8+)
50class Log: # noqa: F811
51 UsePythonLogging = False
52 """Forward Python `lsst.log` messages to Python `logging` package."""
54 @classmethod
55 def usePythonLogging(cls):
56 """Forward log messages to Python `logging`
58 Notes
59 -----
60 This is useful for unit testing when you want to ensure
61 that log messages are captured by the testing environment
62 as distinct from standard output.
64 This state only affects messages sent to the `lsst.log`
65 package from Python.
66 """
67 cls.UsePythonLogging = True
69 @classmethod
70 def doNotUsePythonLogging(cls):
71 """Forward log messages to LSST logging system.
73 Notes
74 -----
75 This is the default state.
76 """
77 cls.UsePythonLogging = False
79 def trace(self, fmt, *args):
80 self._log(Log.TRACE, False, fmt, *args)
82 def debug(self, fmt, *args):
83 self._log(Log.DEBUG, False, fmt, *args)
85 def info(self, fmt, *args):
86 self._log(Log.INFO, False, fmt, *args)
88 def warn(self, fmt, *args):
89 self._log(Log.WARN, False, fmt, *args)
91 def warning(self, fmt, *args):
92 self.warn(fmt, *args)
94 def error(self, fmt, *args):
95 self._log(Log.ERROR, False, fmt, *args)
97 def fatal(self, fmt, *args):
98 self._log(Log.FATAL, False, fmt, *args)
100 def tracef(self, fmt, *args, **kwargs):
101 self._log(Log.TRACE, True, fmt, *args, **kwargs)
103 def debugf(self, fmt, *args, **kwargs):
104 self._log(Log.DEBUG, True, fmt, *args, **kwargs)
106 def infof(self, fmt, *args, **kwargs):
107 self._log(Log.INFO, True, fmt, *args, **kwargs)
109 def warnf(self, fmt, *args, **kwargs):
110 self._log(Log.WARN, True, fmt, *args, **kwargs)
112 def errorf(self, fmt, *args, **kwargs):
113 self._log(Log.ERROR, True, fmt, *args, **kwargs)
115 def fatalf(self, fmt, *args, **kwargs):
116 self._log(Log.FATAL, True, fmt, *args, **kwargs)
118 def _log(self, level, use_format, fmt, *args, **kwargs):
119 if self.isEnabledFor(level):
120 frame = inspect.currentframe().f_back # calling method
121 frame = frame.f_back # original log location
122 filename = os.path.split(frame.f_code.co_filename)[1]
123 funcname = frame.f_code.co_name
124 if use_format:
125 msg = fmt.format(*args, **kwargs) if args or kwargs else fmt
126 else:
127 msg = fmt % args if args else fmt
128 if self.UsePythonLogging:
129 pylog = logging.getLogger(self.getName())
130 record = logging.LogRecord(self.getName(), LevelTranslator.lsstLog2logging(level),
131 filename, frame.f_lineno, msg, None, False, func=funcname)
132 pylog.handle(record)
133 else:
134 self.logMsg(level, filename, funcname, frame.f_lineno, msg)
136 def __reduce__(self):
137 """Implement pickle support.
138 """
139 args = (self.getName(), )
140 # method has to be module-level, not class method
141 return (getLogger, args)
143# Export static functions from Log class to module namespace
146def configure(*args):
147 Log.configure(*args)
150def configure_prop(properties):
151 Log.configure_prop(properties)
154def getDefaultLogger():
155 return Log.getDefaultLogger()
158def getLogger(loggername):
159 return Log.getLogger(loggername)
162def MDC(key, value):
163 Log.MDC(key, str(value))
166def MDCRemove(key):
167 Log.MDCRemove(key)
170def MDCRegisterInit(func):
171 Log.MDCRegisterInit(func)
174def setLevel(loggername, level):
175 Log.getLogger(loggername).setLevel(level)
178def getLevel(loggername):
179 Log.getLogger(loggername).getLevel()
182def isEnabledFor(logger, level):
183 Log.getLogger(logger).isEnabledFor(level)
186# This will cause a warning in Sphinx documentation due to confusion between
187# Log and log. https://github.com/astropy/sphinx-automodapi/issues/73 (but
188# note that this does not seem to be Mac-only).
189def log(loggername, level, fmt, *args, **kwargs):
190 Log.getLogger(loggername)._log(level, False, fmt, *args)
193def trace(fmt, *args):
194 Log.getDefaultLogger()._log(TRACE, False, fmt, *args)
197def debug(fmt, *args):
198 Log.getDefaultLogger()._log(DEBUG, False, fmt, *args)
201def info(fmt, *args):
202 Log.getDefaultLogger()._log(INFO, False, fmt, *args)
205def warn(fmt, *args):
206 Log.getDefaultLogger()._log(WARN, False, fmt, *args)
209def warning(fmt, *args):
210 warn(fmt, *args)
213def error(fmt, *args):
214 Log.getDefaultLogger()._log(ERROR, False, fmt, *args)
217def fatal(fmt, *args):
218 Log.getDefaultLogger()._log(FATAL, False, fmt, *args)
221def logf(loggername, level, fmt, *args, **kwargs):
222 Log.getLogger(loggername)._log(level, True, fmt, *args, **kwargs)
225def tracef(fmt, *args, **kwargs):
226 Log.getDefaultLogger()._log(TRACE, True, fmt, *args, **kwargs)
229def debugf(fmt, *args, **kwargs):
230 Log.getDefaultLogger()._log(DEBUG, True, fmt, *args, **kwargs)
233def infof(fmt, *args, **kwargs):
234 Log.getDefaultLogger()._log(INFO, True, fmt, *args, **kwargs)
237def warnf(fmt, *args, **kwargs):
238 Log.getDefaultLogger()._log(WARN, True, fmt, *args, **kwargs)
241def errorf(fmt, *args, **kwargs):
242 Log.getDefaultLogger()._log(ERROR, True, fmt, *args, **kwargs)
245def fatalf(fmt, *args, **kwargs):
246 Log.getDefaultLogger()._log(FATAL, True, fmt, *args, **kwargs)
249def lwpID():
250 return Log.lwpID
253# This will cause a warning in Sphinx documentation due to confusion between
254# UsePythonLogging and usePythonLogging.
255# https://github.com/astropy/sphinx-automodapi/issues/73 (but note that this
256# does not seem to be Mac-only).
257def usePythonLogging():
258 Log.usePythonLogging()
261def doNotUsePythonLogging():
262 Log.doNotUsePythonLogging()
265class UsePythonLogging:
266 """Context manager to enable Python log forwarding temporarily.
267 """
269 def __init__(self):
270 self.current = Log.UsePythonLogging
272 def __enter__(self):
273 Log.usePythonLogging()
275 def __exit__(self, exc_type, exc_value, traceback):
276 Log.UsePythonLogging = self.current
279class LevelTranslator:
280 """Helper class to translate levels between ``lsst.log`` and Python
281 `logging`.
282 """
283 @staticmethod
284 def lsstLog2logging(level):
285 """Translates from lsst.log/log4cxx levels to `logging` module levels.
287 Parameters
288 ----------
289 level : `int`
290 Logging level number used by `lsst.log`, typically one of the
291 constants defined in this module (`DEBUG`, `INFO`, etc.)
293 Returns
294 -------
295 level : `int`
296 Correspoding logging level number for Python `logging` module.
297 """
298 # Python logging levels are same as lsst.log divided by 1000,
299 # logging does not have TRACE level by default but it is OK to use
300 # that numeric level and we may even add TRACE later.
301 return level//1000
303 @staticmethod
304 def logging2lsstLog(level):
305 """Translates from standard python `logging` module levels to
306 lsst.log/log4cxx levels.
308 Parameters
309 ----------
310 level : `int`
311 Logging level number used by Python `logging`, typically one of
312 the constants defined by `logging` module (`logging.DEBUG`,
313 `logging.INFO`, etc.)
315 Returns
316 -------
317 level : `int`
318 Correspoding logging level number for `lsst.log` module.
319 """
320 return level*1000
323class LogHandler(logging.Handler):
324 """Handler for Python logging module that emits to LSST logging.
326 Parameters
327 ----------
328 level : `int`
329 Level at which to set the this handler.
331 Notes
332 -----
333 If this handler is enabled and `lsst.log` has been configured to use
334 Python `logging`, the handler will do nothing itself if any other
335 handler has been registered with the Python logger. If it does not
336 think that anything else is handling the message it will attempt to
337 send the message via a default `~logging.StreamHandler`. The safest
338 approach is to configure the logger with an additional handler
339 (possibly the ROOT logger) if `lsst.log` is to be configured to use
340 Python logging.
341 """
343 def __init__(self, level=logging.NOTSET):
344 logging.Handler.__init__(self, level=level)
345 # Format as a simple message because lsst.log will format the
346 # message a second time.
347 self.formatter = logging.Formatter(fmt="%(message)s")
349 def handle(self, record):
350 logger = Log.getLogger(record.name)
351 if logger.isEnabledFor(LevelTranslator.logging2lsstLog(record.levelno)):
352 logging.Handler.handle(self, record)
354 def emit(self, record):
355 if Log.UsePythonLogging:
356 # Do not forward this message to lsst.log since this may cause
357 # a logging loop.
359 # Work out whether any other handler is going to be invoked
360 # for this logger.
361 pylgr = logging.getLogger(record.name)
363 # If another handler is registered that is not LogHandler
364 # we ignore this request
365 if any(not isinstance(h, self.__class__) for h in pylgr.handlers):
366 return
368 # If the parent has handlers and propagation is enabled
369 # we punt as well (and if a LogHandler is involved then we will
370 # ask the same question when we get to it).
371 if pylgr.parent and pylgr.parent.hasHandlers() and pylgr.propagate:
372 return
374 # Force this message to appear somewhere.
375 # If something else should happen then the caller should add a
376 # second Handler.
377 stream = logging.StreamHandler()
378 stream.setFormatter(logging.Formatter(fmt="%(name)s %(levelname)s (fallback): %(message)s"))
379 stream.handle(record)
380 return
382 logger = Log.getLogger(record.name)
383 # Use standard formatting class to format message part of the record
384 message = self.formatter.format(record)
386 logger.logMsg(LevelTranslator.logging2lsstLog(record.levelno),
387 record.filename, record.funcName,
388 record.lineno, message)