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#!/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", 

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

32 

33import logging 

34import inspect 

35import os 

36 

37from lsst.utils import continueClass 

38 

39from .log import Log 

40 

41TRACE = 5000 

42DEBUG = 10000 

43INFO = 20000 

44WARN = 30000 

45ERROR = 40000 

46FATAL = 50000 

47 

48 

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

53 

54 @classmethod 

55 def usePythonLogging(cls): 

56 """Forward log messages to Python `logging` 

57 

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. 

63 

64 This state only affects messages sent to the `lsst.log` 

65 package from Python. 

66 """ 

67 cls.UsePythonLogging = True 

68 

69 @classmethod 

70 def doNotUsePythonLogging(cls): 

71 """Forward log messages to LSST logging system. 

72 

73 Notes 

74 ----- 

75 This is the default state. 

76 """ 

77 cls.UsePythonLogging = False 

78 

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

80 self._log(Log.TRACE, False, fmt, *args) 

81 

82 def debug(self, fmt, *args): 

83 self._log(Log.DEBUG, False, fmt, *args) 

84 

85 def info(self, fmt, *args): 

86 self._log(Log.INFO, False, fmt, *args) 

87 

88 def warn(self, fmt, *args): 

89 self._log(Log.WARN, False, fmt, *args) 

90 

91 def warning(self, fmt, *args): 

92 self.warn(fmt, *args) 

93 

94 def error(self, fmt, *args): 

95 self._log(Log.ERROR, False, fmt, *args) 

96 

97 def fatal(self, fmt, *args): 

98 self._log(Log.FATAL, False, fmt, *args) 

99 

100 def tracef(self, fmt, *args, **kwargs): 

101 self._log(Log.TRACE, True, fmt, *args, **kwargs) 

102 

103 def debugf(self, fmt, *args, **kwargs): 

104 self._log(Log.DEBUG, True, fmt, *args, **kwargs) 

105 

106 def infof(self, fmt, *args, **kwargs): 

107 self._log(Log.INFO, True, fmt, *args, **kwargs) 

108 

109 def warnf(self, fmt, *args, **kwargs): 

110 self._log(Log.WARN, True, fmt, *args, **kwargs) 

111 

112 def errorf(self, fmt, *args, **kwargs): 

113 self._log(Log.ERROR, True, fmt, *args, **kwargs) 

114 

115 def fatalf(self, fmt, *args, **kwargs): 

116 self._log(Log.FATAL, True, fmt, *args, **kwargs) 

117 

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) 

135 

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) 

142 

143# Export static functions from Log class to module namespace 

144 

145 

146def configure(*args): 

147 Log.configure(*args) 

148 

149 

150def configure_prop(properties): 

151 Log.configure_prop(properties) 

152 

153 

154def getDefaultLogger(): 

155 return Log.getDefaultLogger() 

156 

157 

158def getLogger(loggername): 

159 return Log.getLogger(loggername) 

160 

161 

162def MDC(key, value): 

163 Log.MDC(key, str(value)) 

164 

165 

166def MDCRemove(key): 

167 Log.MDCRemove(key) 

168 

169 

170def MDCRegisterInit(func): 

171 Log.MDCRegisterInit(func) 

172 

173 

174def setLevel(loggername, level): 

175 Log.getLogger(loggername).setLevel(level) 

176 

177 

178def getLevel(loggername): 

179 Log.getLogger(loggername).getLevel() 

180 

181 

182def isEnabledFor(logger, level): 

183 Log.getLogger(logger).isEnabledFor(level) 

184 

185 

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) 

191 

192 

193def trace(fmt, *args): 

194 Log.getDefaultLogger()._log(TRACE, False, fmt, *args) 

195 

196 

197def debug(fmt, *args): 

198 Log.getDefaultLogger()._log(DEBUG, False, fmt, *args) 

199 

200 

201def info(fmt, *args): 

202 Log.getDefaultLogger()._log(INFO, False, fmt, *args) 

203 

204 

205def warn(fmt, *args): 

206 Log.getDefaultLogger()._log(WARN, False, fmt, *args) 

207 

208 

209def warning(fmt, *args): 

210 warn(fmt, *args) 

211 

212 

213def error(fmt, *args): 

214 Log.getDefaultLogger()._log(ERROR, False, fmt, *args) 

215 

216 

217def fatal(fmt, *args): 

218 Log.getDefaultLogger()._log(FATAL, False, fmt, *args) 

219 

220 

221def logf(loggername, level, fmt, *args, **kwargs): 

222 Log.getLogger(loggername)._log(level, True, fmt, *args, **kwargs) 

223 

224 

225def tracef(fmt, *args, **kwargs): 

226 Log.getDefaultLogger()._log(TRACE, True, fmt, *args, **kwargs) 

227 

228 

229def debugf(fmt, *args, **kwargs): 

230 Log.getDefaultLogger()._log(DEBUG, True, fmt, *args, **kwargs) 

231 

232 

233def infof(fmt, *args, **kwargs): 

234 Log.getDefaultLogger()._log(INFO, True, fmt, *args, **kwargs) 

235 

236 

237def warnf(fmt, *args, **kwargs): 

238 Log.getDefaultLogger()._log(WARN, True, fmt, *args, **kwargs) 

239 

240 

241def errorf(fmt, *args, **kwargs): 

242 Log.getDefaultLogger()._log(ERROR, True, fmt, *args, **kwargs) 

243 

244 

245def fatalf(fmt, *args, **kwargs): 

246 Log.getDefaultLogger()._log(FATAL, True, fmt, *args, **kwargs) 

247 

248 

249def lwpID(): 

250 return Log.lwpID 

251 

252 

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() 

259 

260 

261def doNotUsePythonLogging(): 

262 Log.doNotUsePythonLogging() 

263 

264 

265class UsePythonLogging: 

266 """Context manager to enable Python log forwarding temporarily. 

267 """ 

268 

269 def __init__(self): 

270 self.current = Log.UsePythonLogging 

271 

272 def __enter__(self): 

273 Log.usePythonLogging() 

274 

275 def __exit__(self, exc_type, exc_value, traceback): 

276 Log.UsePythonLogging = self.current 

277 

278 

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. 

286 

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.) 

292 

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 

302 

303 @staticmethod 

304 def logging2lsstLog(level): 

305 """Translates from standard python `logging` module levels to 

306 lsst.log/log4cxx levels. 

307 

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.) 

314 

315 Returns 

316 ------- 

317 level : `int` 

318 Correspoding logging level number for `lsst.log` module. 

319 """ 

320 return level*1000 

321 

322 

323class LogHandler(logging.Handler): 

324 """Handler for Python logging module that emits to LSST logging. 

325 

326 Parameters 

327 ---------- 

328 level : `int` 

329 Level at which to set the this handler. 

330 

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

342 

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

348 

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) 

353 

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. 

358 

359 # Work out whether any other handler is going to be invoked 

360 # for this logger. 

361 pylgr = logging.getLogger(record.name) 

362 

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 

367 

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 

373 

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 

381 

382 logger = Log.getLogger(record.name) 

383 # Use standard formatting class to format message part of the record 

384 message = self.formatter.format(record) 

385 

386 logger.logMsg(LevelTranslator.logging2lsstLog(record.levelno), 

387 record.filename, record.funcName, 

388 record.lineno, message)