LSST Applications g0b6bd0c080+a72a5dd7e6,g1182afd7b4+2a019aa3bb,g17e5ecfddb+2b8207f7de,g1d67935e3f+06cf436103,g38293774b4+ac198e9f13,g396055baef+6a2097e274,g3b44f30a73+6611e0205b,g480783c3b1+98f8679e14,g48ccf36440+89c08d0516,g4b93dc025c+98f8679e14,g5c4744a4d9+a302e8c7f0,g613e996a0d+e1c447f2e0,g6c8d09e9e7+25247a063c,g7271f0639c+98f8679e14,g7a9cd813b8+124095ede6,g9d27549199+a302e8c7f0,ga1cf026fa3+ac198e9f13,ga32aa97882+7403ac30ac,ga786bb30fb+7a139211af,gaa63f70f4e+9994eb9896,gabf319e997+ade567573c,gba47b54d5d+94dc90c3ea,gbec6a3398f+06cf436103,gc6308e37c7+07dd123edb,gc655b1545f+ade567573c,gcc9029db3c+ab229f5caf,gd01420fc67+06cf436103,gd877ba84e5+06cf436103,gdb4cecd868+6f279b5b48,ge2d134c3d5+cc4dbb2e3f,ge448b5faa6+86d1ceac1d,gecc7e12556+98f8679e14,gf3ee170dca+25247a063c,gf4ac96e456+ade567573c,gf9f5ea5b4d+ac198e9f13,gff490e6085+8c2580be5c,w.2022.27
LSST Data Management Base Package
logContinued.py
Go to the documentation of this file.
1#!/usr/bin/env python
2
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#
24
25__all__ = ["TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "CRITICAL", "WARNING",
26 "Log", "configure", "configure_prop", "configure_pylog_MDC", "getDefaultLogger",
27 "getLogger", "MDC", "MDCDict", "MDCRemove", "MDCRegisterInit", "setLevel",
28 "getLevel", "isEnabledFor", "log", "trace", "debug", "info", "warn", "warning",
29 "error", "fatal", "critical", "logf", "tracef", "debugf", "infof", "warnf", "errorf", "fatalf",
30 "lwpID", "usePythonLogging", "doNotUsePythonLogging", "UsePythonLogging",
31 "LevelTranslator", "LogHandler", "getEffectiveLevel", "getLevelName"]
32
33import logging
34import inspect
35import os
36
37from typing import Optional
38from deprecated.sphinx import deprecated
39
40from lsst.utils import continueClass
41
42from .log import Log
43
44TRACE = 5000
45DEBUG = 10000
46INFO = 20000
47WARN = 30000
48ERROR = 40000
49FATAL = 50000
50
51# For compatibility with python logging
52CRITICAL = FATAL
53WARNING = WARN
54
55
56@continueClass # noqa: F811 (FIXME: remove for py 3.8+)
57class Log: # noqa: F811
58 UsePythonLogging = False
59 """Forward Python `lsst.log` messages to Python `logging` package."""
60
61 CRITICAL = CRITICAL
62 WARNING = WARNING
63
64 @classmethod
66 """Forward log messages to Python `logging`
67
68 Notes
69 -----
70 This is useful for unit testing when you want to ensure
71 that log messages are captured by the testing environment
72 as distinct from standard output.
73
74 This state only affects messages sent to the `lsst.log`
75 package from Python.
76 """
77 cls.UsePythonLoggingUsePythonLoggingUsePythonLogging = True
78
79 @classmethod
81 """Forward log messages to LSST logging system.
82
83 Notes
84 -----
85 This is the default state.
86 """
87 cls.UsePythonLoggingUsePythonLoggingUsePythonLogging = False
88
89 @property
90 def name(self):
91 return self.getName()
92
93 @property
94 def level(self):
95 return self.getLevel()
96
97 @property
98 def parent(self):
99 """Returns the parent logger, or None if this is the root logger."""
100 if not self.namename:
101 return None
102 parent_name = self.namename.rpartition(".")[0]
103 if not parent_name:
104 return self.getDefaultLogger()
105 return self.getLogger(parent_name)
106
107 def trace(self, fmt, *args):
108 self._log_log(Log.TRACE, False, fmt, *args)
109
110 def debug(self, fmt, *args):
111 self._log_log(Log.DEBUG, False, fmt, *args)
112
113 def info(self, fmt, *args):
114 self._log_log(Log.INFO, False, fmt, *args)
115
116 def warn(self, fmt, *args):
117 self._log_log(Log.WARN, False, fmt, *args)
118
119 def warning(self, fmt, *args):
120 # Do not call warn() because that will result in an incorrect
121 # line number in the log.
122 self._log_log(Log.WARN, False, fmt, *args)
123
124 def error(self, fmt, *args):
125 self._log_log(Log.ERROR, False, fmt, *args)
126
127 def fatal(self, fmt, *args):
128 self._log_log(Log.FATAL, False, fmt, *args)
129
130 def critical(self, fmt, *args):
131 # Do not call fatal() because that will result in an incorrect
132 # line number in the log.
133 self._log_log(Log.FATAL, False, fmt, *args)
134
135 @deprecated(reason="f-string log messages are now deprecated to match python logging convention."
136 " Will be removed after v25",
137 version="v23.0", category=FutureWarning)
138 def tracef(self, fmt, *args, **kwargs):
139 self._log_log(Log.TRACE, True, fmt, *args, **kwargs)
140
141 @deprecated(reason="f-string log messages are now deprecated to match python logging convention."
142 " Will be removed after v25",
143 version="v23.0", category=FutureWarning)
144 def debugf(self, fmt, *args, **kwargs):
145 self._log_log(Log.DEBUG, True, fmt, *args, **kwargs)
146
147 @deprecated(reason="f-string log messages are now deprecated to match python logging convention."
148 " Will be removed after v25",
149 version="v23.0", category=FutureWarning)
150 def infof(self, fmt, *args, **kwargs):
151 self._log_log(Log.INFO, True, fmt, *args, **kwargs)
152
153 @deprecated(reason="f-string log messages are now deprecated to match python logging convention."
154 " Will be removed after v25",
155 version="v23.0", category=FutureWarning)
156 def warnf(self, fmt, *args, **kwargs):
157 self._log_log(Log.WARN, True, fmt, *args, **kwargs)
158
159 @deprecated(reason="f-string log messages are now deprecated to match python logging convention."
160 " Will be removed after v25",
161 version="v23.0", category=FutureWarning)
162 def errorf(self, fmt, *args, **kwargs):
163 self._log_log(Log.ERROR, True, fmt, *args, **kwargs)
164
165 @deprecated(reason="f-string log messages are now deprecated to match python logging convention."
166 " Will be removed after v25",
167 version="v23.0", category=FutureWarning)
168 def fatalf(self, fmt, *args, **kwargs):
169 self._log_log(Log.FATAL, True, fmt, *args, **kwargs)
170
171 def _log(self, level, use_format, fmt, *args, **kwargs):
172 if self.isEnabledFor(level):
173 frame = inspect.currentframe().f_back # calling method
174 frame = frame.f_back # original log location
175 filename = os.path.split(frame.f_code.co_filename)[1]
176 funcname = frame.f_code.co_name
177 if use_format:
178 msg = fmt.format(*args, **kwargs) if args or kwargs else fmt
179 else:
180 msg = fmt % args if args else fmt
181 if self.UsePythonLoggingUsePythonLoggingUsePythonLogging:
182 levelno = LevelTranslator.lsstLog2logging(level)
183 levelName = logging.getLevelName(levelno)
184
185 pylog = logging.getLogger(self.getName())
186 record = logging.makeLogRecord(dict(name=self.getName(),
187 levelno=levelno,
188 levelname=levelName,
189 msg=msg,
190 funcName=funcname,
191 filename=filename,
192 pathname=frame.f_code.co_filename,
193 lineno=frame.f_lineno))
194 pylog.handle(record)
195 else:
196 self.logMsg(level, filename, funcname, frame.f_lineno, msg)
197
198 def __reduce__(self):
199 """Implement pickle support.
200 """
201 args = (self.getName(), )
202 # method has to be module-level, not class method
203 return (getLogger, args)
204
205 def __repr__(self):
206 # Match python logging style.
207 cls = type(self)
208 class_name = f"{cls.__module__}.{cls.__qualname__}"
209 prefix = "lsst.log.log.log"
210 if class_name.startswith(prefix):
211 class_name = class_name.replace(prefix, "lsst.log")
212 return f"<{class_name} '{self.name}' ({getLevelName(self.getEffectiveLevel())})>"
213
214
215class MDCDict(dict):
216 """Dictionary for MDC data.
217
218 This is internal class used for better formatting of MDC in Python logging
219 output. It behaves like `defaultdict(str)` but overrides ``__str__`` and
220 ``__repr__`` method to produce output better suited for logging records.
221 """
222 def __getitem__(self, name: str):
223 """Returns value for a given key or empty string for missing key.
224 """
225 return self.get(name, "")
226
227 def __str__(self):
228 """Return string representation, strings are interpolated without
229 quotes.
230 """
231 items = (f"{k}={self[k]}" for k in sorted(self))
232 return "{" + ", ".join(items) + "}"
233
234 def __repr__(self):
235 return str(self)
236
237
238# Export static functions from Log class to module namespace
239
240
241def configure(*args):
242 Log.configure(*args)
243
244
245def configure_prop(properties):
246 Log.configure_prop(properties)
247
248
249def configure_pylog_MDC(level: str, MDC_class: Optional[type] = MDCDict):
250 """Configure log4cxx to send messages to Python logging, with MDC support.
251
252 Parameters
253 ----------
254 level : `str`
255 Name of the logging level for root log4cxx logger.
256 MDC_class : `type`, optional
257 Type of dictionary which is added to `logging.LogRecord` as an ``MDC``
258 attribute. Any dictionary or ``defaultdict``-like class can be used as
259 a type. If `None` the `logging.LogRecord` will not be augmented.
260
261 Notes
262 -----
263 This method does two things:
264
265 - Configures log4cxx with a given logging level and a ``PyLogAppender``
266 appender class which forwards all messages to Python `logging`.
267 - Installs a record factory for Python `logging` that adds ``MDC``
268 attribute to every `logging.LogRecord` object (instance of
269 ``MDC_class``). This will happen by default but can be disabled
270 by setting the ``MDC_class`` parameter to `None`.
271 """
272 if MDC_class is not None:
273 old_factory = logging.getLogRecordFactory()
274
275 def record_factory(*args, **kwargs):
276 record = old_factory(*args, **kwargs)
277 record.MDC = MDC_class()
278 return record
279
280 logging.setLogRecordFactory(record_factory)
281
282 properties = """\
283log4j.rootLogger = {}, PyLog
284log4j.appender.PyLog = PyLogAppender
285""".format(level)
286 configure_prop(properties)
287
288
290 return Log.getDefaultLogger()
291
292
293def getLogger(loggername):
294 return Log.getLogger(loggername)
295
296
297def MDC(key, value):
298 return Log.MDC(key, str(value))
299
300
301def MDCRemove(key):
302 Log.MDCRemove(key)
303
304
306 Log.MDCRegisterInit(func)
307
308
309def setLevel(loggername, level):
310 Log.getLogger(loggername).setLevel(level)
311
312
313def getLevel(loggername):
314 return Log.getLogger(loggername).getLevel()
315
316
317def getEffectiveLevel(loggername):
318 return Log.getLogger(loggername).getEffectiveLevel()
319
320
321def isEnabledFor(loggername, level):
322 return Log.getLogger(loggername).isEnabledFor(level)
323
324
325# This will cause a warning in Sphinx documentation due to confusion between
326# Log and log. https://github.com/astropy/sphinx-automodapi/issues/73 (but
327# note that this does not seem to be Mac-only).
328def log(loggername, level, fmt, *args, **kwargs):
329 Log.getLogger(loggername)._log(level, False, fmt, *args)
330
331
332def trace(fmt, *args):
333 Log.getDefaultLogger()._log(TRACE, False, fmt, *args)
334
335
336def debug(fmt, *args):
337 Log.getDefaultLogger()._log(DEBUG, False, fmt, *args)
338
339
340def info(fmt, *args):
341 Log.getDefaultLogger()._log(INFO, False, fmt, *args)
342
343
344def warn(fmt, *args):
345 Log.getDefaultLogger()._log(WARN, False, fmt, *args)
346
347
348def warning(fmt, *args):
349 warn(fmt, *args)
350
351
352def error(fmt, *args):
353 Log.getDefaultLogger()._log(ERROR, False, fmt, *args)
354
355
356def fatal(fmt, *args):
357 Log.getDefaultLogger()._log(FATAL, False, fmt, *args)
358
359
360def critical(fmt, *args):
361 fatal(fmt, *args)
362
363
364def logf(loggername, level, fmt, *args, **kwargs):
365 Log.getLogger(loggername)._log(level, True, fmt, *args, **kwargs)
366
367
368def tracef(fmt, *args, **kwargs):
369 Log.getDefaultLogger()._log(TRACE, True, fmt, *args, **kwargs)
370
371
372def debugf(fmt, *args, **kwargs):
373 Log.getDefaultLogger()._log(DEBUG, True, fmt, *args, **kwargs)
374
375
376def infof(fmt, *args, **kwargs):
377 Log.getDefaultLogger()._log(INFO, True, fmt, *args, **kwargs)
378
379
380def warnf(fmt, *args, **kwargs):
381 Log.getDefaultLogger()._log(WARN, True, fmt, *args, **kwargs)
382
383
384def errorf(fmt, *args, **kwargs):
385 Log.getDefaultLogger()._log(ERROR, True, fmt, *args, **kwargs)
386
387
388def fatalf(fmt, *args, **kwargs):
389 Log.getDefaultLogger()._log(FATAL, True, fmt, *args, **kwargs)
390
391
392def lwpID():
393 return Log.lwpID
394
395
396def getLevelName(level):
397 """Return the name associated with this logging level.
398
399 Returns "Level %d" if no name can be found.
400 """
401 names = ("DEBUG", "TRACE", "WARNING", "FATAL", "INFO", "ERROR")
402 for name in names:
403 test_level = getattr(Log, name)
404 if test_level == level:
405 return name
406 return f"Level {level}"
407
408
409# This will cause a warning in Sphinx documentation due to confusion between
410# UsePythonLogging and usePythonLogging.
411# https://github.com/astropy/sphinx-automodapi/issues/73 (but note that this
412# does not seem to be Mac-only).
414 Log.usePythonLogging()
415
416
418 Log.doNotUsePythonLogging()
419
420
422 """Context manager to enable Python log forwarding temporarily.
423 """
424
425 def __init__(self):
426 self.currentcurrent = Log.UsePythonLogging
427
428 def __enter__(self):
429 Log.usePythonLogging()
430
431 def __exit__(self, exc_type, exc_value, traceback):
432 Log.UsePythonLogging = self.currentcurrent
433
434
436 """Helper class to translate levels between ``lsst.log`` and Python
437 `logging`.
438 """
439 @staticmethod
440 def lsstLog2logging(level):
441 """Translates from lsst.log/log4cxx levels to `logging` module levels.
442
443 Parameters
444 ----------
445 level : `int`
446 Logging level number used by `lsst.log`, typically one of the
447 constants defined in this module (`DEBUG`, `INFO`, etc.)
448
449 Returns
450 -------
451 level : `int`
452 Correspoding logging level number for Python `logging` module.
453 """
454 # Python logging levels are same as lsst.log divided by 1000,
455 # logging does not have TRACE level by default but it is OK to use
456 # that numeric level and we may even add TRACE later.
457 return level//1000
458
459 @staticmethod
460 def logging2lsstLog(level):
461 """Translates from standard python `logging` module levels to
462 lsst.log/log4cxx levels.
463
464 Parameters
465 ----------
466 level : `int`
467 Logging level number used by Python `logging`, typically one of
468 the constants defined by `logging` module (`logging.DEBUG`,
469 `logging.INFO`, etc.)
470
471 Returns
472 -------
473 level : `int`
474 Correspoding logging level number for `lsst.log` module.
475 """
476 return level*1000
477
478
479class LogHandler(logging.Handler):
480 """Handler for Python logging module that emits to LSST logging.
481
482 Parameters
483 ----------
484 level : `int`
485 Level at which to set the this handler.
486
487 Notes
488 -----
489 If this handler is enabled and `lsst.log` has been configured to use
490 Python `logging`, the handler will do nothing itself if any other
491 handler has been registered with the Python logger. If it does not
492 think that anything else is handling the message it will attempt to
493 send the message via a default `~logging.StreamHandler`. The safest
494 approach is to configure the logger with an additional handler
495 (possibly the ROOT logger) if `lsst.log` is to be configured to use
496 Python logging.
497 """
498
499 def __init__(self, level=logging.NOTSET):
500 logging.Handler.__init__(self, level=level)
501 # Format as a simple message because lsst.log will format the
502 # message a second time.
503 self.formatterformatter = logging.Formatter(fmt="%(message)s")
504
505 def handle(self, record):
506 logger = Log.getLogger(record.name)
507 if logger.isEnabledFor(LevelTranslator.logging2lsstLog(record.levelno)):
508 logging.Handler.handle(self, record)
509
510 def emit(self, record):
511 if Log.UsePythonLogging:
512 # Do not forward this message to lsst.log since this may cause
513 # a logging loop.
514
515 # Work out whether any other handler is going to be invoked
516 # for this logger.
517 pylgr = logging.getLogger(record.name)
518
519 # If another handler is registered that is not LogHandler
520 # we ignore this request
521 if any(not isinstance(h, self.__class__) for h in pylgr.handlers):
522 return
523
524 # If the parent has handlers and propagation is enabled
525 # we punt as well (and if a LogHandler is involved then we will
526 # ask the same question when we get to it).
527 if pylgr.parent and pylgr.parent.hasHandlers() and pylgr.propagate:
528 return
529
530 # Force this message to appear somewhere.
531 # If something else should happen then the caller should add a
532 # second Handler.
533 stream = logging.StreamHandler()
534 stream.setFormatter(logging.Formatter(fmt="%(name)s %(levelname)s (fallback): %(message)s"))
535 stream.handle(record)
536 return
537
538 logger = Log.getLogger(record.name)
539 # Use standard formatting class to format message part of the record
540 message = self.formatterformatter.format(record)
541
542 logger.logMsg(LevelTranslator.logging2lsstLog(record.levelno),
543 record.filename, record.funcName,
544 record.lineno, message)
table::Key< int > type
Definition: Detector.cc:163
table::Key< int > to
def __init__(self, level=logging.NOTSET)
def fatal(self, fmt, *args)
def trace(self, fmt, *args)
def debugf(self, fmt, *args, **kwargs)
def warn(self, fmt, *args)
def tracef(self, fmt, *args, **kwargs)
def error(self, fmt, *args)
def warning(self, fmt, *args)
def infof(self, fmt, *args, **kwargs)
def critical(self, fmt, *args)
def _log(self, level, use_format, fmt, *args, **kwargs)
def errorf(self, fmt, *args, **kwargs)
def debug(self, fmt, *args)
def info(self, fmt, *args)
def fatalf(self, fmt, *args, **kwargs)
def warnf(self, fmt, *args, **kwargs)
def __exit__(self, exc_type, exc_value, traceback)
bool any(CoordinateExpr< N > const &expr) noexcept
Return true if any elements are true.
bool all(CoordinateExpr< N > const &expr) noexcept
Return true if all elements are true.
def isEnabledFor(loggername, level)
def setLevel(loggername, level)
def getLogger(loggername)
def fatalf(fmt, *args, **kwargs)
def tracef(fmt, *args, **kwargs)
def logf(loggername, level, fmt, *args, **kwargs)
def debugf(fmt, *args, **kwargs)
def configure_pylog_MDC(str level, Optional[type] MDC_class=MDCDict)
def infof(fmt, *args, **kwargs)
def log(loggername, level, fmt, *args, **kwargs)
def warnf(fmt, *args, **kwargs)
def errorf(fmt, *args, **kwargs)
def getLevel(loggername)
def configure_prop(properties)
def getEffectiveLevel(loggername)
def critical(fmt, *args)
Definition: Log.h:717
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:174