Mercurial > libervia-backend
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): |