comparison src/core/log.py @ 1940:3fdacba9da68

core (logs): log color location can now be specified with %(color_start)s and %(color_end)s
author Goffi <goffi@goffi.org>
date Mon, 18 Apr 2016 18:33:59 +0200
parents 2daf7b4c6756
children 7f053e1f0b67
comparison
equal deleted inserted replaced
1939:e68483c5a999 1940:3fdacba9da68
17 # You should have received a copy of the GNU Affero General Public License 17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 """High level logging functions""" 20 """High level logging functions"""
21 # XXX: this module use standard logging module when possible, but as SàT can work in different cases where logging is not the best choice (twisted, pyjamas, etc), it is necessary to have a dedicated module. Additional feature like environment variables and colors are also managed. 21 # XXX: this module use standard logging module when possible, but as SàT can work in different cases where logging is not the best choice (twisted, pyjamas, etc), it is necessary to have a dedicated module. Additional feature like environment variables and colors are also managed.
22 # TODO: change formatting from "%s" style to "{}" when moved to Python 3
22 23
23 from sat.core.constants import Const as C 24 from sat.core.constants import Const as C
24 from sat.core import exceptions 25 from sat.core import exceptions
25 26
26 backend = None 27 backend = None
27 _loggers = {} 28 _loggers = {}
28 handlers = {} 29 handlers = {}
30 COLOR_START = '%(color_start)s'
31 COLOR_END = '%(color_end)s'
29 32
30 33
31 class Filtered(Exception): 34 class Filtered(Exception):
32 pass 35 pass
33 36
152 return self.filter(log_record) == 1 155 return self.filter(log_record) == 1
153 156
154 157
155 class ConfigureBase(object): 158 class ConfigureBase(object):
156 LOGGER_CLASS = Logger 159 LOGGER_CLASS = Logger
160 _color_location = False # True if color location is specified in fmt (with COLOR_START)
157 161
158 def __init__(self, level=None, fmt=None, output=None, logger=None, colors=False, force_colors=False, backend_data=None): 162 def __init__(self, level=None, fmt=None, output=None, logger=None, colors=False, force_colors=False, backend_data=None):
159 """Configure a backend 163 """Configure a backend
160 164
161 @param level: one of C.LOG_LEVELS 165 @param level: one of C.LOG_LEVELS
199 203
200 def configureFormat(self, fmt): 204 def configureFormat(self, fmt):
201 if fmt is not None: 205 if fmt is not None:
202 if fmt != '%(message)s': # %(message)s is the same as None 206 if fmt != '%(message)s': # %(message)s is the same as None
203 Logger.fmt = fmt 207 Logger.fmt = fmt
208 if COLOR_START in fmt:
209 ConfigureBase._color_location = True
210 if fmt.find(COLOR_END,fmt.rfind(COLOR_START))<0:
211 # color_start not followed by an end, we add it
212 Logger.fmt += COLOR_END
204 213
205 def configureOutput(self, output): 214 def configureOutput(self, output):
206 if output is not None: 215 if output is not None:
207 if output != C.LOG_OPT_OUTPUT_SEP + C.LOG_OPT_OUTPUT_DEFAULT: 216 if output != C.LOG_OPT_OUTPUT_SEP + C.LOG_OPT_OUTPUT_DEFAULT:
208 # TODO: manage other outputs 217 # TODO: manage other outputs
247 if output == C.LOG_OPT_OUTPUT_DEFAULT: 256 if output == C.LOG_OPT_OUTPUT_DEFAULT:
248 # no option for defaut handler 257 # no option for defaut handler
249 handlers[output] = None 258 handlers[output] = None
250 elif output == C.LOG_OPT_OUTPUT_FILE: 259 elif output == C.LOG_OPT_OUTPUT_FILE:
251 if not options: 260 if not options:
252 ValueError("%(handler)s output need a path as option" % {'handler': output}) 261 ValueError("{handler} output need a path as option" .format(handle=output))
253 handlers.setdefault(output, []).append(options) 262 handlers.setdefault(output, []).append(options)
254 options = None # option are parsed, we can empty them 263 options = None # option are parsed, we can empty them
255 elif output == C.LOG_OPT_OUTPUT_MEMORY: 264 elif output == C.LOG_OPT_OUTPUT_MEMORY:
256 # we have memory handler, option can be the len limit or None 265 # we have memory handler, option can be the len limit or None
257 try: 266 try:
260 except (TypeError, ValueError): 269 except (TypeError, ValueError):
261 limit = C.LOG_OPT_OUTPUT_MEMORY_LIMIT 270 limit = C.LOG_OPT_OUTPUT_MEMORY_LIMIT
262 handlers[output] = limit 271 handlers[output] = limit
263 272
264 if options: # we should not have unparsed options 273 if options: # we should not have unparsed options
265 raise ValueError(u"options [%(options)s] are not supported for %(handler)s output" % {'options': options, 'handler': output}) 274 raise ValueError(u"options [{options}] are not supported for {handler} output".format(options=options, handler=output))
266 275
267 @staticmethod 276 @staticmethod
268 def memoryGet(size=None): 277 def memoryGet(size=None):
269 """Return buffered logs 278 """Return buffered logs
270 279
271 @param size: number of logs to return 280 @param size: number of logs to return
272 """ 281 """
273 raise NotImplementedError 282 raise NotImplementedError
274 283
275 @staticmethod 284 @classmethod
276 def ansiColors(level, message): 285 def ansiColors(cls, level, message):
277 """Colorise message depending on level for terminals 286 """Colorise message depending on level for terminals
278 287
279 @param level: one of C.LOG_LEVELS 288 @param level: one of C.LOG_LEVELS
280 @param message: formatted message to log 289 @param message: formatted message to log
281 @return: message with ANSI escape codes for coloration 290 @return: message with ANSI escape codes for coloration
282 """ 291 """
283 if level == C.LOG_LVL_DEBUG: 292 if level == C.LOG_LVL_DEBUG:
284 out = (C.ANSI_FG_CYAN, message, C.ANSI_RESET) 293 start = (C.ANSI_FG_CYAN,)
294 elif level == C.LOG_LVL_INFO:
295 start = ()
285 elif level == C.LOG_LVL_WARNING: 296 elif level == C.LOG_LVL_WARNING:
286 out = (C.ANSI_FG_YELLOW, message, C.ANSI_RESET) 297 start = (C.ANSI_FG_YELLOW,)
287 elif level == C.LOG_LVL_ERROR: 298 elif level == C.LOG_LVL_ERROR:
288 out = (C.ANSI_FG_RED, 299 start = (C.ANSI_FG_RED,
289 C.ANSI_BLINK, 300 C.ANSI_BLINK,
290 r'/!\ ', 301 r'/!\ ',
291 C.ANSI_BLINK_OFF, 302 C.ANSI_BLINK_OFF)
292 message,
293 C.ANSI_RESET)
294 elif level == C.LOG_LVL_CRITICAL: 303 elif level == C.LOG_LVL_CRITICAL:
295 out = (C.ANSI_BOLD, 304 start = (C.ANSI_BOLD,
296 C.ANSI_FG_RED, 305 C.ANSI_FG_RED,
297 'Guru Meditation ', 306 'Guru Meditation ',
298 C.ANSI_NORMAL_WEIGHT, 307 C.ANSI_NORMAL_WEIGHT)
299 message,
300 C.ANSI_RESET)
301 else: 308 else:
302 out = message 309 start = ()
303 return ''.join(out) 310 if cls._color_location:
311 return message % {'color_start': ''.join(start),
312 'color_end': C.ANSI_RESET}
313 else:
314 return '%s%s%s' % (''.join(start), message, C.ANSI_RESET)
304 315
305 @staticmethod 316 @staticmethod
306 def getProfile(): 317 def getProfile():
307 """Try to find profile value using introspection""" 318 """Try to find profile value using introspection"""
308 raise NotImplementedError 319 raise NotImplementedError
332 backend = backend_ 343 backend = backend_
333 344
334 try: 345 try:
335 configure_class = configure_cls[backend] 346 configure_class = configure_cls[backend]
336 except KeyError: 347 except KeyError:
337 raise ValueError("unknown backend [%s]" % backend) 348 raise ValueError("unknown backend [{}]".format(backend))
338 if backend == C.LOG_BACKEND_CUSTOM: 349 if backend == C.LOG_BACKEND_CUSTOM:
339 logger_class = options.pop('logger_class') 350 logger_class = options.pop('logger_class')
340 configure_class(logger_class, **options) 351 configure_class(logger_class, **options)
341 else: 352 else:
342 configure_class(**options) 353 configure_class(**options)
348 359
349 def getLogger(name=C.LOG_BASE_LOGGER): 360 def getLogger(name=C.LOG_BASE_LOGGER):
350 try: 361 try:
351 logger_class = configure_cls[backend].LOGGER_CLASS 362 logger_class = configure_cls[backend].LOGGER_CLASS
352 except KeyError: 363 except KeyError:
353 raise ValueError("This method should not be called with backend [%s]" % backend) 364 raise ValueError("This method should not be called with backend [{}]".format(backend))
354 return _loggers.setdefault(name, logger_class(name)) 365 return _loggers.setdefault(name, logger_class(name))
355 366
356 _root_logger = getLogger() 367 _root_logger = getLogger()
357 368
358 def debug(msg): 369 def debug(msg):