LSST Applications  21.0.0-147-g0e635eb1+1acddb5be5,22.0.0+052faf71bd,22.0.0+1ea9a8b2b2,22.0.0+6312710a6c,22.0.0+729191ecac,22.0.0+7589c3a021,22.0.0+9f079a9461,22.0.1-1-g7d6de66+b8044ec9de,22.0.1-1-g87000a6+536b1ee016,22.0.1-1-g8e32f31+6312710a6c,22.0.1-10-gd060f87+016f7cdc03,22.0.1-12-g9c3108e+df145f6f68,22.0.1-16-g314fa6d+c825727ab8,22.0.1-19-g93a5c75+d23f2fb6d8,22.0.1-19-gb93eaa13+aab3ef7709,22.0.1-2-g8ef0a89+b8044ec9de,22.0.1-2-g92698f7+9f079a9461,22.0.1-2-ga9b0f51+052faf71bd,22.0.1-2-gac51dbf+052faf71bd,22.0.1-2-gb66926d+6312710a6c,22.0.1-2-gcb770ba+09e3807989,22.0.1-20-g32debb5+b8044ec9de,22.0.1-23-gc2439a9a+fb0756638e,22.0.1-3-g496fd5d+09117f784f,22.0.1-3-g59f966b+1e6ba2c031,22.0.1-3-g849a1b8+f8b568069f,22.0.1-3-gaaec9c0+c5c846a8b1,22.0.1-32-g5ddfab5d3+60ce4897b0,22.0.1-4-g037fbe1+64e601228d,22.0.1-4-g8623105+b8044ec9de,22.0.1-5-g096abc9+d18c45d440,22.0.1-5-g15c806e+57f5c03693,22.0.1-7-gba73697+57f5c03693,master-g6e05de7fdc+c1283a92b8,master-g72cdda8301+729191ecac,w.2021.39
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 
33 import logging
34 import inspect
35 import os
36 
37 from typing import Optional
38 from deprecated.sphinx import deprecated
39 
40 from lsst.utils import continueClass
41 
42 from .log import Log
43 
44 TRACE = 5000
45 DEBUG = 10000
46 INFO = 20000
47 WARN = 30000
48 ERROR = 40000
49 FATAL = 50000
50 
51 # For compatibility with python logging
52 CRITICAL = FATAL
53 WARNING = WARN
54 
55 
56 @continueClass # noqa: F811 (FIXME: remove for py 3.8+)
57 class 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
65  def usePythonLogging(cls):
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." " Will be removed after v25", version="v23.0", category=FutureWarning)
136  def tracef(self, fmt, *args, **kwargs):
137  self._log_log(Log.TRACE, True, fmt, *args, **kwargs)
138 
139  @deprecated(reason="f-string log messages are now deprecated to match python logging convention." " Will be removed after v25", version="v23.0", category=FutureWarning)
140  def debugf(self, fmt, *args, **kwargs):
141  self._log_log(Log.DEBUG, True, fmt, *args, **kwargs)
142 
143  @deprecated(reason="f-string log messages are now deprecated to match python logging convention." " Will be removed after v25", version="v23.0", category=FutureWarning)
144  def infof(self, fmt, *args, **kwargs):
145  self._log_log(Log.INFO, True, fmt, *args, **kwargs)
146 
147  @deprecated(reason="f-string log messages are now deprecated to match python logging convention." " Will be removed after v25", version="v23.0", category=FutureWarning)
148  def warnf(self, fmt, *args, **kwargs):
149  self._log_log(Log.WARN, True, fmt, *args, **kwargs)
150 
151  @deprecated(reason="f-string log messages are now deprecated to match python logging convention." " Will be removed after v25", version="v23.0", category=FutureWarning)
152  def errorf(self, fmt, *args, **kwargs):
153  self._log_log(Log.ERROR, True, fmt, *args, **kwargs)
154 
155  @deprecated(reason="f-string log messages are now deprecated to match python logging convention." " Will be removed after v25", version="v23.0", category=FutureWarning)
156  def fatalf(self, fmt, *args, **kwargs):
157  self._log_log(Log.FATAL, True, fmt, *args, **kwargs)
158 
159  def _log(self, level, use_format, fmt, *args, **kwargs):
160  if self.isEnabledFor(level):
161  frame = inspect.currentframe().f_back # calling method
162  frame = frame.f_back # original log location
163  filename = os.path.split(frame.f_code.co_filename)[1]
164  funcname = frame.f_code.co_name
165  if use_format:
166  msg = fmt.format(*args, **kwargs) if args or kwargs else fmt
167  else:
168  msg = fmt % args if args else fmt
169  if self.UsePythonLoggingUsePythonLoggingUsePythonLogging:
170  levelno = LevelTranslator.lsstLog2logging(level)
171  levelName = logging.getLevelName(levelno)
172 
173  pylog = logging.getLogger(self.getName())
174  record = logging.makeLogRecord(dict(name=self.getName(),
175  levelno=levelno,
176  levelname=levelName,
177  msg=msg,
178  funcName=funcname,
179  filename=filename,
180  pathname=frame.f_code.co_filename,
181  lineno=frame.f_lineno))
182  pylog.handle(record)
183  else:
184  self.logMsg(level, filename, funcname, frame.f_lineno, msg)
185 
186  def __reduce__(self):
187  """Implement pickle support.
188  """
189  args = (self.getName(), )
190  # method has to be module-level, not class method
191  return (getLogger, args)
192 
193  def __repr__(self):
194  # Match python logging style.
195  cls = type(self)
196  class_name = f"{cls.__module__}.{cls.__qualname__}"
197  prefix = "lsst.log.log.log"
198  if class_name.startswith(prefix):
199  class_name = class_name.replace(prefix, "lsst.log")
200  return f"<{class_name} '{self.name}' ({getLevelName(self.getEffectiveLevel())})>"
201 
202 
203 class MDCDict(dict):
204  """Dictionary for MDC data.
205 
206  This is internal class used for better formatting of MDC in Python logging
207  output. It behaves like `defaultdict(str)` but overrides ``__str__`` and
208  ``__repr__`` method to produce output better suited for logging records.
209  """
210  def __getitem__(self, name: str):
211  """Returns value for a given key or empty string for missing key.
212  """
213  return self.get(name, "")
214 
215  def __str__(self):
216  """Return string representation, strings are interpolated without
217  quotes.
218  """
219  items = (f"{k}={self[k]}" for k in sorted(self))
220  return "{" + ", ".join(items) + "}"
221 
222  def __repr__(self):
223  return str(self)
224 
225 
226 # Export static functions from Log class to module namespace
227 
228 
229 def configure(*args):
230  Log.configure(*args)
231 
232 
233 def configure_prop(properties):
234  Log.configure_prop(properties)
235 
236 
237 def configure_pylog_MDC(level: str, MDC_class: Optional[type] = MDCDict):
238  """Configure log4cxx to send messages to Python logging, with MDC support.
239 
240  Parameters
241  ----------
242  level : `str`
243  Name of the logging level for root log4cxx logger.
244  MDC_class : `type`, optional
245  Type of dictionary which is added to `logging.LogRecord` as an ``MDC``
246  attribute. Any dictionary or ``defaultdict``-like class can be used as
247  a type. If `None` the `logging.LogRecord` will not be augmented.
248 
249  Notes
250  -----
251  This method does two things:
252 
253  - Configures log4cxx with a given logging level and a ``PyLogAppender``
254  appender class which forwards all messages to Python `logging`.
255  - Installs a record factory for Python `logging` that adds ``MDC``
256  attribute to every `logging.LogRecord` object (instance of
257  ``MDC_class``). This will happen by default but can be disabled
258  by setting the ``MDC_class`` parameter to `None`.
259  """
260  if MDC_class is not None:
261  old_factory = logging.getLogRecordFactory()
262 
263  def record_factory(*args, **kwargs):
264  record = old_factory(*args, **kwargs)
265  record.MDC = MDC_class()
266  return record
267 
268  logging.setLogRecordFactory(record_factory)
269 
270  properties = """\
271 log4j.rootLogger = {}, PyLog
272 log4j.appender.PyLog = PyLogAppender
273 """.format(level)
274  configure_prop(properties)
275 
276 
277 def getDefaultLogger():
278  return Log.getDefaultLogger()
279 
280 
281 def getLogger(loggername):
282  return Log.getLogger(loggername)
283 
284 
285 def MDC(key, value):
286  return Log.MDC(key, str(value))
287 
288 
289 def MDCRemove(key):
290  Log.MDCRemove(key)
291 
292 
293 def MDCRegisterInit(func):
294  Log.MDCRegisterInit(func)
295 
296 
297 def setLevel(loggername, level):
298  Log.getLogger(loggername).setLevel(level)
299 
300 
301 def getLevel(loggername):
302  return Log.getLogger(loggername).getLevel()
303 
304 
305 def getEffectiveLevel(loggername):
306  return Log.getLogger(loggername).getEffectiveLevel()
307 
308 
309 def isEnabledFor(loggername, level):
310  return Log.getLogger(loggername).isEnabledFor(level)
311 
312 
313 # This will cause a warning in Sphinx documentation due to confusion between
314 # Log and log. https://github.com/astropy/sphinx-automodapi/issues/73 (but
315 # note that this does not seem to be Mac-only).
316 def log(loggername, level, fmt, *args, **kwargs):
317  Log.getLogger(loggername)._log(level, False, fmt, *args)
318 
319 
320 def trace(fmt, *args):
321  Log.getDefaultLogger()._log(TRACE, False, fmt, *args)
322 
323 
324 def debug(fmt, *args):
325  Log.getDefaultLogger()._log(DEBUG, False, fmt, *args)
326 
327 
328 def info(fmt, *args):
329  Log.getDefaultLogger()._log(INFO, False, fmt, *args)
330 
331 
332 def warn(fmt, *args):
333  Log.getDefaultLogger()._log(WARN, False, fmt, *args)
334 
335 
336 def warning(fmt, *args):
337  warn(fmt, *args)
338 
339 
340 def error(fmt, *args):
341  Log.getDefaultLogger()._log(ERROR, False, fmt, *args)
342 
343 
344 def fatal(fmt, *args):
345  Log.getDefaultLogger()._log(FATAL, False, fmt, *args)
346 
347 
348 def critical(fmt, *args):
349  fatal(fmt, *args)
350 
351 
352 def logf(loggername, level, fmt, *args, **kwargs):
353  Log.getLogger(loggername)._log(level, True, fmt, *args, **kwargs)
354 
355 
356 def tracef(fmt, *args, **kwargs):
357  Log.getDefaultLogger()._log(TRACE, True, fmt, *args, **kwargs)
358 
359 
360 def debugf(fmt, *args, **kwargs):
361  Log.getDefaultLogger()._log(DEBUG, True, fmt, *args, **kwargs)
362 
363 
364 def infof(fmt, *args, **kwargs):
365  Log.getDefaultLogger()._log(INFO, True, fmt, *args, **kwargs)
366 
367 
368 def warnf(fmt, *args, **kwargs):
369  Log.getDefaultLogger()._log(WARN, True, fmt, *args, **kwargs)
370 
371 
372 def errorf(fmt, *args, **kwargs):
373  Log.getDefaultLogger()._log(ERROR, True, fmt, *args, **kwargs)
374 
375 
376 def fatalf(fmt, *args, **kwargs):
377  Log.getDefaultLogger()._log(FATAL, True, fmt, *args, **kwargs)
378 
379 
380 def lwpID():
381  return Log.lwpID
382 
383 
384 def getLevelName(level):
385  """Return the name associated with this logging level.
386 
387  Returns "Level %d" if no name can be found.
388  """
389  names = ("DEBUG", "TRACE", "WARNING", "FATAL", "INFO", "ERROR")
390  for name in names:
391  test_level = getattr(Log, name)
392  if test_level == level:
393  return name
394  return f"Level {level}"
395 
396 
397 # This will cause a warning in Sphinx documentation due to confusion between
398 # UsePythonLogging and usePythonLogging.
399 # https://github.com/astropy/sphinx-automodapi/issues/73 (but note that this
400 # does not seem to be Mac-only).
401 def usePythonLogging():
402  Log.usePythonLogging()
403 
404 
406  Log.doNotUsePythonLogging()
407 
408 
409 class UsePythonLogging:
410  """Context manager to enable Python log forwarding temporarily.
411  """
412 
413  def __init__(self):
414  self.current = Log.UsePythonLogging
415 
416  def __enter__(self):
417  Log.usePythonLogging()
418 
419  def __exit__(self, exc_type, exc_value, traceback):
420  Log.UsePythonLogging = self.current
421 
422 
423 class LevelTranslator:
424  """Helper class to translate levels between ``lsst.log`` and Python
425  `logging`.
426  """
427  @staticmethod
428  def lsstLog2logging(level):
429  """Translates from lsst.log/log4cxx levels to `logging` module levels.
430 
431  Parameters
432  ----------
433  level : `int`
434  Logging level number used by `lsst.log`, typically one of the
435  constants defined in this module (`DEBUG`, `INFO`, etc.)
436 
437  Returns
438  -------
439  level : `int`
440  Correspoding logging level number for Python `logging` module.
441  """
442  # Python logging levels are same as lsst.log divided by 1000,
443  # logging does not have TRACE level by default but it is OK to use
444  # that numeric level and we may even add TRACE later.
445  return level//1000
446 
447  @staticmethod
448  def logging2lsstLog(level):
449  """Translates from standard python `logging` module levels to
450  lsst.log/log4cxx levels.
451 
452  Parameters
453  ----------
454  level : `int`
455  Logging level number used by Python `logging`, typically one of
456  the constants defined by `logging` module (`logging.DEBUG`,
457  `logging.INFO`, etc.)
458 
459  Returns
460  -------
461  level : `int`
462  Correspoding logging level number for `lsst.log` module.
463  """
464  return level*1000
465 
466 
467 class LogHandler(logging.Handler):
468  """Handler for Python logging module that emits to LSST logging.
469 
470  Parameters
471  ----------
472  level : `int`
473  Level at which to set the this handler.
474 
475  Notes
476  -----
477  If this handler is enabled and `lsst.log` has been configured to use
478  Python `logging`, the handler will do nothing itself if any other
479  handler has been registered with the Python logger. If it does not
480  think that anything else is handling the message it will attempt to
481  send the message via a default `~logging.StreamHandler`. The safest
482  approach is to configure the logger with an additional handler
483  (possibly the ROOT logger) if `lsst.log` is to be configured to use
484  Python logging.
485  """
486 
487  def __init__(self, level=logging.NOTSET):
488  logging.Handler.__init__(self, level=level)
489  # Format as a simple message because lsst.log will format the
490  # message a second time.
491  self.formatterformatter = logging.Formatter(fmt="%(message)s")
492 
493  def handle(self, record):
494  logger = Log.getLogger(record.name)
495  if logger.isEnabledFor(LevelTranslator.logging2lsstLog(record.levelno)):
496  logging.Handler.handle(self, record)
497 
498  def emit(self, record):
499  if Log.UsePythonLogging:
500  # Do not forward this message to lsst.log since this may cause
501  # a logging loop.
502 
503  # Work out whether any other handler is going to be invoked
504  # for this logger.
505  pylgr = logging.getLogger(record.name)
506 
507  # If another handler is registered that is not LogHandler
508  # we ignore this request
509  if any(not isinstance(h, self.__class__) for h in pylgr.handlers):
510  return
511 
512  # If the parent has handlers and propagation is enabled
513  # we punt as well (and if a LogHandler is involved then we will
514  # ask the same question when we get to it).
515  if pylgr.parent and pylgr.parent.hasHandlers() and pylgr.propagate:
516  return
517 
518  # Force this message to appear somewhere.
519  # If something else should happen then the caller should add a
520  # second Handler.
521  stream = logging.StreamHandler()
522  stream.setFormatter(logging.Formatter(fmt="%(name)s %(levelname)s (fallback): %(message)s"))
523  stream.handle(record)
524  return
525 
526  logger = Log.getLogger(record.name)
527  # Use standard formatting class to format message part of the record
528  message = self.formatterformatter.format(record)
529 
530  logger.logMsg(LevelTranslator.logging2lsstLog(record.levelno),
531  record.filename, record.funcName,
532  record.lineno, message)
533 
table::Key< int > type
Definition: Detector.cc:163
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)
bool any(CoordinateExpr< N > const &expr) noexcept
Return true if any 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)
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:174