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

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (http://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 <http://www.gnu.org/licenses/>. 

21 

22import logging 

23 

24try: 

25 import lsst.log as lsstLog 

26except ModuleNotFoundError: 

27 lsstLog = None 

28 

29 

30# logging properties 

31_LOG_PROP = """\ 

32log4j.rootLogger=INFO, A1 

33log4j.appender.A1=ConsoleAppender 

34log4j.appender.A1.Target=System.err 

35log4j.appender.A1.layout=PatternLayout 

36log4j.appender.A1.layout.ConversionPattern={} 

37""" 

38 

39 

40class CliLog: 

41 

42 defaultLsstLogLevel = lsstLog.FATAL if lsstLog is not None else None 

43 

44 longLogFmt = "%-5p %d{yyyy-MM-ddTHH:mm:ss.SSSZ} %c (%X{LABEL})(%F:%L)- %m%n" 

45 normalLogFmt = "%c %p: %m%n" 

46 

47 _initialized = False 

48 _lsstLogHandler = None 

49 _componentSettings = [] 

50 

51 @classmethod 

52 def initLog(cls, longlog): 

53 """Initialize logging. This should only be called once per program 

54 execution. After the first call this will log a warning and return. 

55 

56 If lsst.log is importable, will add its log handler to the python 

57 root logger's handlers. 

58 

59 Parameters 

60 ---------- 

61 longlog : `bool` 

62 If True, make log messages appear in long format, by default False. 

63 """ 

64 if cls._initialized: 

65 # Unit tests that execute more than one command do end up 

66 # calling this fucntion multiple times in one program execution, 

67 # so do log a debug but don't log an error or fail, just make the 

68 # re-initalization a no-op. 

69 log = logging.getLogger(__name__.partition(".")[2]) 

70 log.debug("Log is already initialized, returning without re-initializing.") 

71 return 

72 cls._initialized = True 

73 

74 if lsstLog is not None: 

75 # global logging config 

76 lsstLog.configure_prop(_LOG_PROP.format(cls.longLogFmt if longlog else cls.normalLogFmt)) 

77 cls._recordComponentSetting(None) 

78 pythonLogger = logging.getLogger() 

79 pythonLogger.setLevel(logging.INFO) 

80 cls._lsstLogHandler = lsstLog.LogHandler() 

81 # Forward all Python logging to lsstLog 

82 pythonLogger.addHandler(cls._lsstLogHandler) 

83 else: 

84 cls._recordComponentSetting(None) 

85 logging.basicConfig(level=logging.INFO) 

86 

87 # also capture warnings and send them to logging 

88 logging.captureWarnings(True) 

89 

90 @classmethod 

91 def getHandlerId(cls): 

92 """Get the id of the lsst.log handler added to the python logger. 

93 

94 Used for unit testing to verify addition & removal of the lsst.log 

95 handler. 

96 

97 Returns 

98 ------- 

99 `id` or `None` 

100 The id of the handler that was added if one was added, or None. 

101 """ 

102 return id(cls._lsstLogHandler) 

103 

104 @classmethod 

105 def resetLog(cls): 

106 """Uninitialize the butler CLI Log handler and reset component log 

107 levels. 

108 

109 If the lsst.log handler was added to the python root logger's handlers 

110 in `initLog`, it will be removed here. 

111 

112 For each loger level that was set by this class, sets that logger's 

113 level to the value it was before this class set it. For lsst.log, if a 

114 component level was uninitialzed, it will be set to 

115 `Log.defaultLsstLogLevel` because there is no log4cxx api to set a 

116 component back to an uninitialized state. 

117 """ 

118 if cls._lsstLogHandler is not None: 

119 logging.getLogger().removeHandler(cls._lsstLogHandler) 

120 for componentSetting in reversed(cls._componentSettings): 

121 if lsstLog is not None and componentSetting.lsstLogLevel is not None: 

122 lsstLog.setLevel(componentSetting.component or "", componentSetting.lsstLogLevel) 

123 logger = logging.getLogger(componentSetting.component) 

124 logger.setLevel(componentSetting.pythonLogLevel) 

125 cls._setLogLevel(None, "INFO") 

126 cls._initialized = False 

127 

128 @classmethod 

129 def setLogLevels(cls, logLevels): 

130 """Set log level for one or more components or the root logger. 

131 

132 Parameters 

133 ---------- 

134 longlog : `bool` 

135 If True then make log messages appear in "long format" 

136 logLevels : `list` of `tuple` 

137 per-component logging levels, each item in the list is a tuple 

138 (component, level), `component` is a logger name or an empty string 

139 or `None` for root logger, `level` is a logging level name, one of 

140 CRITICAL, ERROR, WARNING, INFO, DEBUG (case insensitive). 

141 """ 

142 if isinstance(logLevels, dict): 

143 logLevels = logLevels.items() 

144 

145 # configure individual loggers 

146 for component, level in logLevels: 

147 cls._setLogLevel(component, level) 

148 

149 @classmethod 

150 def _setLogLevel(cls, component, level): 

151 """Set the log level for the given component. Record the current log 

152 level of the component so that it can be restored when resetting this 

153 log. 

154 

155 Parameters 

156 ---------- 

157 component : `str` or None 

158 The name of the log component or None for the root logger. 

159 level : `str` 

160 A valid python logging level. 

161 """ 

162 cls._recordComponentSetting(component) 

163 if lsstLog is not None: 

164 lsstLogger = lsstLog.Log.getLogger(component or "") 

165 lsstLogger.setLevel(cls._getLsstLogLevel(level)) 

166 logging.getLogger(component or None).setLevel(cls._getPyLogLevel(level)) 

167 

168 @staticmethod 

169 def _getPyLogLevel(level): 

170 """Get the numeric value for the given log level name. 

171 

172 Parameters 

173 ---------- 

174 level : `str` 

175 One of the python `logging` log level names. 

176 

177 Returns 

178 ------- 

179 numericValue : `int` 

180 The python `logging` numeric value for the log level. 

181 """ 

182 return getattr(logging, level, None) 

183 

184 @staticmethod 

185 def _getLsstLogLevel(level): 

186 """Get the numeric value for the given log level name. 

187 

188 If `lsst.log` is not setup this function will return `None` regardless 

189 of input. `daf_butler` does not directly depend on `lsst.log` and so it 

190 will not be setup when `daf_butler` is setup. Packages that depend on 

191 `daf_butler` and use `lsst.log` may setup `lsst.log`. 

192 

193 Will adapt the python name to an `lsst.log` name: 

194 - CRITICAL to FATAL 

195 - WARNING to WARN 

196 

197 Parameters 

198 ---------- 

199 level : `str` 

200 One of the python `logging` log level names. 

201 

202 Returns 

203 ------- 

204 numericValue : `int` or `None` 

205 The `lsst.log` numeric value. 

206 """ 

207 if lsstLog is None: 

208 return None 

209 if level == "CRITICAL": 

210 level = "FATAL" 

211 elif level == "WARNING": 

212 level = "WARN" 

213 return getattr(lsstLog.Log, level, None) 

214 

215 class ComponentSettings: 

216 """Container for log level values for a logging component.""" 

217 def __init__(self, component): 

218 self.component = component 

219 self.pythonLogLevel = logging.getLogger(component).level 

220 self.lsstLogLevel = (lsstLog.Log.getLogger(component or "").getLevel() 

221 if lsstLog is not None else None) 

222 if self.lsstLogLevel == -1: 

223 self.lsstLogLevel = CliLog.defaultLsstLogLevel 

224 

225 def __repr__(self): 

226 return (f"ComponentSettings(component={self.component}, pythonLogLevel={self.pythonLogLevel}, " 

227 f"lsstLogLevel={self.lsstLogLevel})") 

228 

229 @classmethod 

230 def _recordComponentSetting(cls, component): 

231 """Cache current levels for the given component in the list of 

232 component levels.""" 

233 componentSettings = cls.ComponentSettings(component) 

234 cls._componentSettings.append(componentSettings)