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 redefinition 

50class Log: 

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 

186def log(loggername, level, fmt, *args, **kwargs): 

187 Log.getLogger(loggername)._log(level, False, fmt, *args) 

188 

189 

190def trace(fmt, *args): 

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

192 

193 

194def debug(fmt, *args): 

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

196 

197 

198def info(fmt, *args): 

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

200 

201 

202def warn(fmt, *args): 

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

204 

205 

206def warning(fmt, *args): 

207 warn(fmt, *args) 

208 

209 

210def error(fmt, *args): 

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

212 

213 

214def fatal(fmt, *args): 

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

216 

217 

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

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

220 

221 

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

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

224 

225 

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

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

228 

229 

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

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

232 

233 

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

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

236 

237 

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

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

240 

241 

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

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

244 

245 

246def lwpID(): 

247 return Log.lwpID 

248 

249 

250def usePythonLogging(): 

251 Log.usePythonLogging() 

252 

253 

254def doNotUsePythonLogging(): 

255 Log.doNotUsePythonLogging() 

256 

257 

258class UsePythonLogging: 

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

260 """ 

261 

262 def __init__(self): 

263 self.current = Log.UsePythonLogging 

264 

265 def __enter__(self): 

266 Log.usePythonLogging() 

267 

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

269 Log.UsePythonLogging = self.current 

270 

271 

272class LevelTranslator: 

273 """Helper class to translate levels between ``lsst.log`` and Python 

274 `logging`. 

275 """ 

276 @staticmethod 

277 def lsstLog2logging(level): 

278 """Translates from lsst.log/log4cxx levels to `logging` module levels. 

279 

280 Parameters 

281 ---------- 

282 level : `int` 

283 Logging level number used by `lsst.log`, typically one of the 

284 constants defined in this module (`DEBUG`, `INFO`, etc.) 

285 

286 Returns 

287 ------- 

288 level : `int` 

289 Correspoding logging level number for Python `logging` module. 

290 """ 

291 # Python logging levels are same as lsst.log divided by 1000, 

292 # logging does not have TRACE level by default but it is OK to use 

293 # that numeric level and we may even add TRACE later. 

294 return level//1000 

295 

296 @staticmethod 

297 def logging2lsstLog(level): 

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

299 lsst.log/log4cxx levels. 

300 

301 Parameters 

302 ---------- 

303 level : `int` 

304 Logging level number used by Python `logging`, typically one of 

305 the constants defined by `logging` module (`logging.DEBUG`, 

306 `logging.INFO`, etc.) 

307 

308 Returns 

309 ------- 

310 level : `int` 

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

312 """ 

313 return level*1000 

314 

315 

316class LogHandler(logging.Handler): 

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

318 

319 Parameters 

320 --------- 

321 level : `int` 

322 Level at which to set the this handler. 

323 

324 Notes 

325 ----- 

326 If this handler is enabled and `lsst.log` has been configured to use 

327 Python `logging`, the handler will do nothing itself if any other 

328 handler has been registered with the Python logger. If it does not 

329 think that anything else is handling the message it will attempt to 

330 send the message via a default `~logging.StreamHandler`. The safest 

331 approach is to configure the logger with an additional handler 

332 (possibly the ROOT logger) if `lsst.log` is to be configured to use 

333 Python logging. 

334 """ 

335 

336 def __init__(self, level=logging.NOTSET): 

337 logging.Handler.__init__(self, level=level) 

338 # Format as a simple message because lsst.log will format the 

339 # message a second time. 

340 self.formatter = logging.Formatter(fmt="%(message)s") 

341 

342 def handle(self, record): 

343 logger = Log.getLogger(record.name) 

344 if logger.isEnabledFor(LevelTranslator.logging2lsstLog(record.levelno)): 

345 logging.Handler.handle(self, record) 

346 

347 def emit(self, record): 

348 if Log.UsePythonLogging: 

349 # Do not forward this message to lsst.log since this may cause 

350 # a logging loop. 

351 

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

353 # for this logger. 

354 pylgr = logging.getLogger(record.name) 

355 

356 # If another handler is registered that is not LogHandler 

357 # we ignore this request 

358 if any(not isinstance(h, self.__class__) for h in pylgr.handlers): 

359 return 

360 

361 # If the parent has handlers and propagation is enabled 

362 # we punt as well (and if a LogHandler is involved then we will 

363 # ask the same question when we get to it). 

364 if pylgr.parent and pylgr.parent.hasHandlers() and pylgr.propagate: 

365 return 

366 

367 # Force this message to appear somewhere. 

368 # If something else should happen then the caller should add a 

369 # second Handler. 

370 stream = logging.StreamHandler() 

371 stream.setFormatter(logging.Formatter(fmt="%(name)s %(levelname)s (fallback): %(message)s")) 

372 stream.handle(record) 

373 return 

374 

375 logger = Log.getLogger(record.name) 

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

377 message = self.formatter.format(record) 

378 

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

380 record.filename, record.funcName, 

381 record.lineno, message)