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 

25import logging 

26import inspect 

27import os 

28 

29from lsst.utils import continueClass 

30 

31from .log import Log 

32 

33TRACE = 5000 

34DEBUG = 10000 

35INFO = 20000 

36WARN = 30000 

37ERROR = 40000 

38FATAL = 50000 

39 

40 

41@continueClass # noqa F811 redefinition 

42class Log: 

43 UsePythonLogging = False 

44 """Forward Python `lsst.log` messages to Python `logging` package.""" 

45 

46 @classmethod 

47 def usePythonLogging(cls): 

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

49 

50 Notes 

51 ----- 

52 This is useful for unit testing when you want to ensure 

53 that log messages are captured by the testing environment 

54 as distinct from standard output. 

55 

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

57 package from Python. 

58 """ 

59 cls.UsePythonLogging = True 

60 

61 @classmethod 

62 def doNotUsePythonLogging(cls): 

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

64 

65 Notes 

66 ----- 

67 This is the default state. 

68 """ 

69 cls.UsePythonLogging = False 

70 

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

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

73 

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

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

76 

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

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

79 

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

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

82 

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

84 self.warn(fmt, *args) 

85 

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

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

88 

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

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

91 

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

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

94 

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

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

97 

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

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

100 

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

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

103 

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

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

106 

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

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

109 

110 def _log(self, level, use_format, fmt, *args, **kwargs): 

111 if self.isEnabledFor(level): 

112 frame = inspect.currentframe().f_back # calling method 

113 frame = frame.f_back # original log location 

114 filename = os.path.split(frame.f_code.co_filename)[1] 

115 funcname = frame.f_code.co_name 

116 if use_format: 

117 msg = fmt.format(*args, **kwargs) if args or kwargs else fmt 

118 else: 

119 msg = fmt % args if args else fmt 

120 if self.UsePythonLogging: 

121 pylog = logging.getLogger(self.getName()) 

122 record = logging.LogRecord(self.getName(), LevelTranslator.lsstLog2logging(level), 

123 filename, frame.f_lineno, msg, None, False, func=funcname) 

124 pylog.handle(record) 

125 else: 

126 self.logMsg(level, filename, funcname, frame.f_lineno, msg) 

127 

128 def __reduce__(self): 

129 """Implement pickle support. 

130 """ 

131 args = (self.getName(), ) 

132 # method has to be module-level, not class method 

133 return (getLogger, args) 

134 

135# Export static functions from Log class to module namespace 

136 

137 

138def configure(*args): 

139 Log.configure(*args) 

140 

141 

142def configure_prop(properties): 

143 Log.configure_prop(properties) 

144 

145 

146def getDefaultLogger(): 

147 return Log.getDefaultLogger() 

148 

149 

150def getLogger(loggername): 

151 return Log.getLogger(loggername) 

152 

153 

154def MDC(key, value): 

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

156 

157 

158def MDCRemove(key): 

159 Log.MDCRemove(key) 

160 

161 

162def MDCRegisterInit(func): 

163 Log.MDCRegisterInit(func) 

164 

165 

166def setLevel(loggername, level): 

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

168 

169 

170def getLevel(loggername): 

171 Log.getLogger(loggername).getLevel() 

172 

173 

174def isEnabledFor(logger, level): 

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

176 

177 

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

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

180 

181 

182def trace(fmt, *args): 

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

184 

185 

186def debug(fmt, *args): 

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

188 

189 

190def info(fmt, *args): 

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

192 

193 

194def warn(fmt, *args): 

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

196 

197 

198def warning(fmt, *args): 

199 warn(fmt, *args) 

200 

201 

202def error(fmt, *args): 

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

204 

205 

206def fatal(fmt, *args): 

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

208 

209 

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

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

212 

213 

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

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

216 

217 

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

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

220 

221 

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

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

224 

225 

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

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

228 

229 

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

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

232 

233 

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

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

236 

237 

238def lwpID(): 

239 return Log.lwpID 

240 

241 

242def usePythonLogging(): 

243 Log.usePythonLogging() 

244 

245 

246def doNotUsePythonLogging(): 

247 Log.doNotUsePythonLogging() 

248 

249 

250class UsePythonLogging: 

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

252 

253 def __init__(self): 

254 self.current = Log.UsePythonLogging 

255 

256 def __enter__(self): 

257 Log.usePythonLogging() 

258 

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

260 Log.UsePythonLogging = self.current 

261 

262 

263class LevelTranslator: 

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

265 `logging`. 

266 """ 

267 @staticmethod 

268 def lsstLog2logging(level): 

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

270 

271 Parameters 

272 ---------- 

273 level : `int` 

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

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

276 

277 Returns 

278 ------- 

279 level : `int` 

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

281 """ 

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

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

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

285 return level//1000 

286 

287 @staticmethod 

288 def logging2lsstLog(level): 

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

290 lsst.log/log4cxx levels. 

291 

292 Parameters 

293 ---------- 

294 level : `int` 

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

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

297 `logging.INFO`, etc.) 

298 

299 Returns 

300 ------- 

301 level : `int` 

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

303 """ 

304 return level*1000 

305 

306 

307class LogHandler(logging.Handler): 

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

309 

310 Notes 

311 ----- 

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

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

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

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

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

317 approach is to configure the logger with an additional handler 

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

319 Python logging. 

320 """ 

321 

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

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

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

325 # message a second time. 

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

327 

328 def handle(self, record): 

329 logger = Log.getLogger(record.name) 

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

331 logging.Handler.handle(self, record) 

332 

333 def emit(self, record): 

334 if Log.UsePythonLogging: 

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

336 # a logging loop. 

337 

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

339 # for this logger. 

340 pylgr = logging.getLogger(record.name) 

341 

342 # If another handler is registered that is not LogHandler 

343 # we ignore this request 

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

345 return 

346 

347 # If the parent has handlers and propagation is enabled 

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

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

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

351 return 

352 

353 # Force this message to appear somewhere. 

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

355 # second Handler. 

356 stream = logging.StreamHandler() 

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

358 stream.handle(record) 

359 return 

360 

361 logger = Log.getLogger(record.name) 

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

363 message = self.formatter.format(record) 

364 

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

366 record.filename, record.funcName, 

367 record.lineno, message)