Hide keyboard shortcuts

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# 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/>. 

21 

22__all__ = ("TRACE", "VERBOSE", "getTaskLogger", "TaskLogAdapter") 

23 

24import logging 

25from logging import LoggerAdapter 

26from deprecated.sphinx import deprecated 

27from contextlib import contextmanager 

28 

29# log level for trace (verbose debug). 

30TRACE = 5 

31logging.addLevelName(TRACE, "TRACE") 

32 

33# Verbose logging is midway between INFO and DEBUG. 

34VERBOSE = (logging.INFO + logging.DEBUG) // 2 

35logging.addLevelName(VERBOSE, "VERBOSE") 

36 

37 

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 

47 

48 def __str__(self): 

49 return self.fmt.format(*self.args, **self.kwargs) 

50 

51 

52class TaskLogAdapter(LoggerAdapter): 

53 """A special logging adapter to provide log features for `Task`. 

54 

55 Expected to be instantiated initially by a call to `getLogger()`. 

56 

57 This class provides enhancements over `logging.Logger` that include: 

58 

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 """ 

65 

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 

74 

75 # Python supports these but prefers they are not used. 

76 FATAL = logging.FATAL 

77 WARN = logging.WARN 

78 

79 # These are specific to Tasks 

80 TRACE = TRACE 

81 VERBOSE = VERBOSE 

82 

83 @contextmanager 

84 def temporary_log_level(self, level): 

85 """A context manager that temporarily sets the level of this logger. 

86 

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) 

98 

99 @property 

100 def level(self): 

101 """Current level of this logger (``int``).""" 

102 return self.logger.level 

103 

104 def getChild(self, name): 

105 """Get the named child logger. 

106 

107 Parameters 

108 ---------- 

109 name : `str` 

110 Name of the child relative to this logger. 

111 

112 Returns 

113 ------- 

114 child : `TaskLogAdapter` 

115 The child logger. 

116 """ 

117 return getTaskLogger(name=name, logger=self.logger) 

118 

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) 

123 

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 

128 

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 

133 

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) 

140 

141 def verbose(self, fmt, *args, **kwargs): 

142 """Issue a VERBOSE level log message. 

143 

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) 

154 

155 def trace(self, fmt, *args): 

156 """Issue a TRACE level log message. 

157 

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) 

164 

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) 

170 

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) 

175 

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) 

180 

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) 

185 

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) 

190 

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) 

195 

196 def setLevel(self, level): 

197 """Set the level for the logger, trapping lsst.log values. 

198 

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 

209 

210 self.logger.setLevel(level) 

211 

212 @property 

213 def handlers(self): 

214 """Log handlers associated with this logger.""" 

215 return self.logger.handlers 

216 

217 def addHandler(self, handler): 

218 """Add a handler to this logger. 

219 

220 The handler is forwarded to the underlying logger. 

221 """ 

222 self.logger.addHandler(handler) 

223 

224 def removeHandler(self, handler): 

225 """Remove the given handler from the underlying logger.""" 

226 self.logger.removeHandler(handler) 

227 

228 

229def getTaskLogger(name=None, logger=None): 

230 """Get a logger compatible with LSST usage. 

231 

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. 

240 

241 Returns 

242 ------- 

243 logger : `TaskLogAdapter` 

244 The relevant logger. 

245 

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, {})