Coverage for python/lsst/pipe/base/task_logging.py: 59%
Shortcuts 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
Shortcuts 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# This file is part of task_base.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
22__all__ = ("TRACE", "VERBOSE", "getTaskLogger", "TaskLogAdapter")
24import logging
25from logging import LoggerAdapter
26from deprecated.sphinx import deprecated
27from contextlib import contextmanager
29# log level for trace (verbose debug).
30TRACE = 5
31logging.addLevelName(TRACE, "TRACE")
33# Verbose logging is midway between INFO and DEBUG.
34VERBOSE = (logging.INFO + logging.DEBUG) // 2
35logging.addLevelName(VERBOSE, "VERBOSE")
38class _F:
39 """
40 Format, supporting str.format() syntax
41 see using-custom-message-objects in logging-cookbook.html
42 """
43 def __init__(self, fmt, /, *args, **kwargs):
44 self.fmt = fmt
45 self.args = args
46 self.kwargs = kwargs
48 def __str__(self):
49 return self.fmt.format(*self.args, **self.kwargs)
52class TaskLogAdapter(LoggerAdapter):
53 """A special logging adapter to provide log features for `Task`.
55 Expected to be instantiated initially by a call to `getLogger()`.
57 This class provides enhancements over `logging.Logger` that include:
59 * Methods for issuing trace and verbose level log messages.
60 * Provision of a context manager to temporarily change the log level.
61 * Attachment of logging level constants to the class to make it easier
62 for a Task writer to access a specific log level without having to
63 know the underlying logger class.
64 """
66 # Store logging constants in the class for convenience. This is not
67 # something supported by Python loggers but can simplify some
68 # logic if the logger is available.
69 CRITICAL = logging.CRITICAL
70 ERROR = logging.ERROR
71 DEBUG = logging.DEBUG
72 INFO = logging.INFO
73 WARNING = logging.WARNING
75 # Python supports these but prefers they are not used.
76 FATAL = logging.FATAL
77 WARN = logging.WARN
79 # These are specific to Tasks
80 TRACE = TRACE
81 VERBOSE = VERBOSE
83 @contextmanager
84 def temporary_log_level(self, level):
85 """A context manager that temporarily sets the level of this logger.
87 Parameters
88 ----------
89 level : `int`
90 The new temporary log level.
91 """
92 old = self.level
93 self.setLevel(level)
94 try:
95 yield
96 finally:
97 self.setLevel(old)
99 @property
100 def level(self):
101 """Current level of this logger (``int``)."""
102 return self.logger.level
104 def getChild(self, name):
105 """Get the named child logger.
107 Parameters
108 ----------
109 name : `str`
110 Name of the child relative to this logger.
112 Returns
113 -------
114 child : `TaskLogAdapter`
115 The child logger.
116 """
117 return getTaskLogger(name=name, logger=self.logger)
119 @deprecated(reason="Use Python Logger compatible isEnabledFor Will be removed after v23.",
120 version="v23", category=FutureWarning)
121 def isDebugEnabled(self):
122 return self.isEnabledFor(self.DEBUG)
124 @deprecated(reason="Use Python Logger compatible 'name' attribute. Will be removed after v23.",
125 version="v23", category=FutureWarning)
126 def getName(self):
127 return self.name
129 @deprecated(reason="Use Python Logger compatible .level property. Will be removed after v23.",
130 version="v23", category=FutureWarning)
131 def getLevel(self):
132 return self.logger.level
134 def fatal(self, msg, *args, **kwargs):
135 # Python does not provide this method in LoggerAdapter but does
136 # not formally deprecated it in favor of critical() either.
137 # Provide it without deprecation message for consistency with Python.
138 # stacklevel=5 accounts for the forwarding of LoggerAdapter.
139 return self.critical(msg, *args, **kwargs, stacklevel=4)
141 def verbose(self, fmt, *args, **kwargs):
142 """Issue a VERBOSE level log message.
144 Arguments are as for `logging.info`.
145 ``VERBOSE`` is between ``DEBUG`` and ``INFO``.
146 """
147 # There is no other way to achieve this other than a special logger
148 # method.
149 # Stacklevel is passed in so that the correct line is reported
150 # in the log record and not this line. 3 is this method,
151 # 2 is the level from `self.log` and 1 is the log infrastructure
152 # itself.
153 self.log(VERBOSE, fmt, *args, stacklevel=3, **kwargs)
155 def trace(self, fmt, *args):
156 """Issue a TRACE level log message.
158 Arguments are as for `logging.info`.
159 ``TRACE`` is lower than ``DEBUG``.
160 """
161 # There is no other way to achieve this other than a special logger
162 # method. For stacklevel discussion see `verbose()`.
163 self.log(TRACE, fmt, *args, stacklevel=3)
165 @deprecated(reason="Use Python Logger compatible method. Will be removed after v23.",
166 version="v23", category=FutureWarning)
167 def tracef(self, fmt, *args, **kwargs):
168 # Stacklevel is 4 to account for the deprecation wrapper
169 self.log(TRACE, _F(fmt, *args, **kwargs), stacklevel=4)
171 @deprecated(reason="Use Python Logger compatible method. Will be removed after v23.",
172 version="v23", category=FutureWarning)
173 def debugf(self, fmt, *args, **kwargs):
174 self.log(logging.DEBUG, _F(fmt, *args, **kwargs), stacklevel=4)
176 @deprecated(reason="Use Python Logger compatible method. Will be removed after v23.",
177 version="v23", category=FutureWarning)
178 def infof(self, fmt, *args, **kwargs):
179 self.log(logging.INFO, _F(fmt, *args, **kwargs), stacklevel=4)
181 @deprecated(reason="Use Python Logger compatible method. Will be removed after v23.",
182 version="v23", category=FutureWarning)
183 def warnf(self, fmt, *args, **kwargs):
184 self.log(logging.WARNING, _F(fmt, *args, **kwargs), stacklevel=4)
186 @deprecated(reason="Use Python Logger compatible method. Will be removed after v23.",
187 version="v23", category=FutureWarning)
188 def errorf(self, fmt, *args, **kwargs):
189 self.log(logging.ERROR, _F(fmt, *args, **kwargs), stacklevel=4)
191 @deprecated(reason="Use Python Logger compatible method. Will be removed after v23.",
192 version="v23", category=FutureWarning)
193 def fatalf(self, fmt, *args, **kwargs):
194 self.log(logging.CRITICAL, _F(fmt, *args, **kwargs), stacklevel=4)
196 def setLevel(self, level):
197 """Set the level for the logger, trapping lsst.log values.
199 Parameters
200 ----------
201 level : `int`
202 The level to use. If the level looks too big to be a Python
203 logging level it is assumed to be a lsst.log level.
204 """
205 if level > logging.CRITICAL:
206 self.logger.warning("Attempting to set level to %d -- looks like an lsst.log level so scaling it"
207 " accordingly.", level)
208 level //= 1000
210 self.logger.setLevel(level)
212 @property
213 def handlers(self):
214 """Log handlers associated with this logger."""
215 return self.logger.handlers
217 def addHandler(self, handler):
218 """Add a handler to this logger.
220 The handler is forwarded to the underlying logger.
221 """
222 self.logger.addHandler(handler)
224 def removeHandler(self, handler):
225 """Remove the given handler from the underlying logger."""
226 self.logger.removeHandler(handler)
229def getTaskLogger(name=None, logger=None):
230 """Get a logger compatible with LSST usage.
232 Parameters
233 ----------
234 name : `str`, optional
235 Name of the logger. Root logger if `None`.
236 logger : `logging.Logger`
237 If given the logger is converted to the relevant logger class.
238 If ``name`` is given the logger is assumed to be a child of the
239 supplied logger.
241 Returns
242 -------
243 logger : `TaskLogAdapter`
244 The relevant logger.
246 Notes
247 -----
248 A `logging.LoggerAdapter` is used since it is easier to provide a more
249 uniform interface than when using `logging.setLoggerClass`. An adapter
250 can be wrapped around the root logger and the `~logging.setLoggerClass`
251 will return the logger first given that name even if the name was
252 used before the `Task` was created.
253 """
254 if not logger:
255 logger = logging.getLogger(name)
256 elif name:
257 logger = logger.getChild(name)
258 return TaskLogAdapter(logger, {})