comparison src/core/log.py @ 1006:325fd230c15d

core (log): added advanced feature to basic backend (colors/formatting/level and logger filtering)
author Goffi <goffi@goffi.org>
date Mon, 05 May 2014 18:58:34 +0200
parents b4af31a8a4f2
children a7d33c7a8277
comparison
equal deleted inserted replaced
1005:b4af31a8a4f2 1006:325fd230c15d
25 25
26 _backend = None 26 _backend = None
27 _loggers = {} 27 _loggers = {}
28 _handlers = {} 28 _handlers = {}
29 29
30 class Filtered(Exception):
31 pass
30 32
31 class Logger(object): 33 class Logger(object):
32 """ High level logging class """ 34 """High level logging class"""
35 fmt = None
36 filter_name = None
37 post_treat = None
33 38
34 def __init__(self, name): 39 def __init__(self, name):
35 self._name = name 40 self._name = name
36 41
42 def log(self, level, message):
43 """Print message
44
45 @param level: one of C.LOG_LEVELS
46 @param message: message to format and print
47 """
48 try:
49 formatted = self.format(level, message)
50 if self.post_treat is None:
51 print formatted
52 else:
53 print self.post_treat(level, formatted)
54 except Filtered:
55 pass
56
57 def format(self, level, message):
58 """Format message according to Logger.fmt
59
60 @param level: one of C.LOG_LEVELS
61 @param message: message to format
62 @return: formatted message
63
64 @raise: Filtered the message must not be logged
65 """
66 if self.fmt is None and self.filter_name is None:
67 return message
68 record = {'name': self._name,
69 'message': message,
70 'levelname': level,
71 }
72 try:
73 if not self.filter_name.dictFilter(record):
74 raise Filtered
75 except AttributeError:
76 if self.filter_name is not None:
77 raise ValueError("Bad filter: filters must have a .filter method")
78 return self.fmt % record
79
37 def debug(self, msg): 80 def debug(self, msg):
38 print msg 81 self.log(C.LOG_LVL_DEBUG, msg)
39 82
40 def info(self, msg): 83 def info(self, msg):
41 print msg 84 self.log(C.LOG_LVL_INFO, msg)
42 85
43 def warning(self, msg): 86 def warning(self, msg):
44 print msg 87 self.log(C.LOG_LVL_WARNING, msg)
45 88
46 def error(self, msg): 89 def error(self, msg):
47 print msg 90 self.log(C.LOG_LVL_ERROR, msg)
48 91
49 def critical(self, msg): 92 def critical(self, msg):
50 print msg 93 self.log(C.LOG_LVL_CRITICAL, msg)
51 94
52 95
53 class FilterName(object): 96 class FilterName(object):
97 """Filter on logger name according to a regex"""
54 98
55 def __init__(self, name_re): 99 def __init__(self, name_re):
56 """Initialise name filter 100 """Initialise name filter
57 101
58 @param name_re: regular expression used to filter names (using search and not match) 102 @param name_re: regular expression used to filter names (using search and not match)
64 def filter(self, record): 108 def filter(self, record):
65 if self.name_re.search(record.name) is not None: 109 if self.name_re.search(record.name) is not None:
66 return 1 110 return 1
67 return 0 111 return 0
68 112
113 def dictFilter(self, dict_record):
114 """Filter using a dictionary record
115
116 @param dict_record: dictionary with at list a key "name" with logger name
117 @return: True if message should be logged
118 """
119 class LogRecord(object):
120 pass
121 log_record = LogRecord()
122 log_record.name = dict_record['name']
123 return self.filter(log_record) == 1
124
125
126 def _ansiColors(level, message):
127 """Colorise message depending on level for terminals
128
129 @param level: one of C.LOG_LEVELS
130 @param message: formatted message to log
131 @return: message with ANSI escape codes for coloration
132 """
133 if level == C.LOG_LVL_DEBUG:
134 out = (C.ANSI_FG_CYAN, message, C.ANSI_RESET)
135 elif level == C.LOG_LVL_WARNING:
136 out = (C.ANSI_FG_YELLOW, message, C.ANSI_RESET)
137 elif level == C.LOG_LVL_ERROR:
138 out = (C.ANSI_FG_RED,
139 C.ANSI_BLINK,
140 r'/!\ ',
141 C.ANSI_BLINK_OFF,
142 message,
143 C.ANSI_RESET)
144 elif level == C.LOG_LVL_CRITICAL:
145 out = (C.ANSI_BOLD,
146 C.ANSI_FG_RED,
147 'Guru Meditation ',
148 C.ANSI_NORMAL_WEIGHT,
149 message,
150 C.ANSI_RESET)
151 else:
152 out = message
153 return ''.join(out)
154
69 def _manageOutputs(outputs_raw): 155 def _manageOutputs(outputs_raw):
70 """ Parse output option in a backend agnostic way, and fill _backend consequently 156 """ Parse output option in a backend agnostic way, and fill _handlers consequently
71 157
72 @param outputs_raw: output option as enterred in environment variable or in configuration 158 @param outputs_raw: output option as enterred in environment variable or in configuration
73 """ 159 """
74 if not outputs_raw: 160 if not outputs_raw:
75 return 161 return
141 super(SatFormatter, self).__init__(fmt, datefmt) 227 super(SatFormatter, self).__init__(fmt, datefmt)
142 228
143 def format(self, record): 229 def format(self, record):
144 s = super(SatFormatter, self).format(record) 230 s = super(SatFormatter, self).format(record)
145 if with_color: 231 if with_color:
146 if record.levelno == logging.DEBUG: 232 s = _ansiColors(record.levelname, s)
147 fmt = (C.ANSI_FG_CYAN, s, C.ANSI_RESET)
148 elif record.levelno == logging.WARNING:
149 fmt = (C.ANSI_FG_YELLOW, s, C.ANSI_RESET)
150 elif record.levelno == logging.ERROR:
151 fmt = (C.ANSI_FG_RED,
152 C.ANSI_BLINK,
153 r'/!\ ',
154 C.ANSI_BLINK_OFF,
155 s,
156 C.ANSI_RESET)
157 elif record.levelno == logging.CRITICAL:
158 fmt = (C.ANSI_BOLD,
159 C.ANSI_FG_RED,
160 'Guru Meditation ',
161 C.ANSI_NORMAL_WEIGHT,
162 s,
163 C.ANSI_RESET)
164 else:
165 fmt = s
166 s = ''.join(fmt)
167
168 return s 233 return s
169 234
170 root_logger = logging.getLogger() 235 root_logger = logging.getLogger()
171 if len(root_logger.handlers) == 0: 236 if len(root_logger.handlers) == 0:
172 _manageOutputs(output) 237 _manageOutputs(output)
176 if handler == C.LOG_OPT_OUTPUT_DEFAULT: 241 if handler == C.LOG_OPT_OUTPUT_DEFAULT:
177 hdlr = logging.StreamHandler() 242 hdlr = logging.StreamHandler()
178 elif handler == C.LOG_OPT_OUTPUT_MEMORY: 243 elif handler == C.LOG_OPT_OUTPUT_MEMORY:
179 import logging.handlers 244 import logging.handlers
180 hdlr = logging.handlers.BufferingHandler(options) 245 hdlr = logging.handlers.BufferingHandler(options)
246 _handlers[handler] = hdlr # we keep a reference to the handler to read the buffer later
181 elif handler == C.LOG_OPT_OUTPUT_FILE: 247 elif handler == C.LOG_OPT_OUTPUT_FILE:
182 import os.path 248 import os.path
183 hdlr = logging.FileHandler(os.path.expanduser(options)) 249 hdlr = logging.FileHandler(os.path.expanduser(options))
184 else: 250 else:
185 raise ValueError("Unknown handler type") 251 raise ValueError("Unknown handler type")
189 if name_filter is not None: 255 if name_filter is not None:
190 hdlr.addFilter(name_filter) 256 hdlr.addFilter(name_filter)
191 else: 257 else:
192 root_logger.warning(u"Handlers already set on root logger") 258 root_logger.warning(u"Handlers already set on root logger")
193 259
260 def _configureBasic(level=None, fmt=None, output=None, logger=None, colors=False, force_colors=False):
261 """Configure basic backend
262 @param level: same as _configureStdLogging.level
263 @param fmt: same as _configureStdLogging.fmt
264 @param output: not implemented yet TODO
265 @param logger: same as _configureStdLogging.logger
266 @param colors: same as _configureStdLogging.colors
267 @param force_colors: same as _configureStdLogging.force_colors
268 """
269 if level is not None:
270 # we deactivate methods below level
271 level_idx = C.LOG_LEVELS.index(level)
272 def dev_null(self, msg):
273 pass
274 for _level in C.LOG_LEVELS[:level_idx]:
275 setattr(Logger, _level.lower(), dev_null)
276 if fmt is not None:
277 if fmt != '%(message)s': # %(message)s is the same as None
278 Logger.fmt = fmt
279 if output is not None:
280 if output != C.LOG_OPT_OUTPUT_SEP + C.LOG_OPT_OUTPUT_DEFAULT:
281 # TODO: manage other outputs
282 raise NotImplementedError("Basic backend only manage default output yet")
283
284 if logger:
285 Logger.filter_name = FilterName(logger)
286
287 if colors:
288 import sys
289 if force_colors or sys.stdout.isatty():
290 # we need colors
291 Logger.post_treat = lambda self, level, message: _ansiColors(level, message)
292 elif force_colors:
293 raise ValueError("force_colors can't be used if colors is False")
294
194 def configure(backend=C.LOG_BACKEND_STANDARD, **options): 295 def configure(backend=C.LOG_BACKEND_STANDARD, **options):
195 """Configure logging bejaviour 296 """Configure logging behaviour
196 @param backend: can be: 297 @param backend: can be:
197 C.LOG_BACKEND_STANDARD: use standard logging module 298 C.LOG_BACKEND_STANDARD: use standard logging module
198 C.LOG_BACKEND_TWISTED: use twisted logging module (with standard logging observer) 299 C.LOG_BACKEND_TWISTED: use twisted logging module (with standard logging observer)
199 C.LOG_BACKEND_BASIC: use a basic print based logging 300 C.LOG_BACKEND_BASIC: use a basic print based logging
200 """ 301 """
222 warning = logging.warning 323 warning = logging.warning
223 error = logging.error 324 error = logging.error
224 critical = logging.critical 325 critical = logging.critical
225 326
226 elif backend == C.LOG_BACKEND_BASIC: 327 elif backend == C.LOG_BACKEND_BASIC:
227 pass 328 _configureBasic(**options)
228 329
229 else: 330 else:
230 raise ValueError("unknown backend") 331 raise ValueError("unknown backend")
231 332
232 def _parseOptions(options): 333 def _parseOptions(options):