comparison src/core/log.py @ 1005:b4af31a8a4f2

core (logs): added formatting, name filter and outputs management: - formatting is inspired from, and use when possible, standard logging. "message", "levelname", and "name" are the only format managed, depending on backend more can be managed (standard backend formats are specified in official python logging doc) - name filter use regular expressions. It's possible to log only plugins with SAT_LOG_LOGGER="^sat.plugins". To log only XEPs 96 & 65, we can use SAT_LOG_LOGGER='(xep_0095|xep_0065)' - output management use a particular syntax: - output handler are name with "//", so far there are "//default" (most of time stderr), "//memory" and "//file" - options can be specified in parenthesis, e.g. "//memory(50)" mean a 50 lines memory buffer (50 is the current default, so that's equivalent to "//memory") - several handlers can be specified: "//file(/tmp/sat.log)//default" will use the default logging + a the /tmp/sat.log file - if there is only one handler, it use the file handler: "/tmp/sat.log" is the same as "//file(/tmp/sat.log)" - not finished, need more work for twisted and basic backends
author Goffi <goffi@goffi.org>
date Mon, 05 May 2014 18:58:34 +0200
parents 652c01ca69b1
children 325fd230c15d
comparison
equal deleted inserted replaced
1004:191f440d11b4 1005:b4af31a8a4f2
16 16
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. In addition additional feature like environment variable and color 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 22
23 from sat.core.constants import Const as C 23 from sat.core.constants import Const as C
24 from sat.core import exceptions 24 from sat.core import exceptions
25 25
26 _backend = None 26 _backend = None
27 _loggers = {} 27 _loggers = {}
28 _handlers = {}
28 29
29 30
30 class Logger(object): 31 class Logger(object):
31 """ High level logging class """ 32 """ High level logging class """
32 33
47 48
48 def critical(self, msg): 49 def critical(self, msg):
49 print msg 50 print msg
50 51
51 52
52 def _configureStdLogging(logging, level=None, colors=False, force_colors=False): 53 class FilterName(object):
54
55 def __init__(self, name_re):
56 """Initialise name filter
57
58 @param name_re: regular expression used to filter names (using search and not match)
59 """
60 assert name_re
61 import re
62 self.name_re = re.compile(name_re)
63
64 def filter(self, record):
65 if self.name_re.search(record.name) is not None:
66 return 1
67 return 0
68
69 def _manageOutputs(outputs_raw):
70 """ Parse output option in a backend agnostic way, and fill _backend consequently
71
72 @param outputs_raw: output option as enterred in environment variable or in configuration
73 """
74 if not outputs_raw:
75 return
76 outputs = outputs_raw.split(C.LOG_OPT_OUTPUT_SEP)
77 global _handlers
78 if len(outputs) == 1:
79 _handlers[C.LOG_OPT_OUTPUT_FILE] = outputs.pop()
80
81 for output in outputs:
82 if not output:
83 continue
84 if output[-1] == ')':
85 # we have options
86 opt_begin = output.rfind('(')
87 options = output[opt_begin+1:-1]
88 output = output[:opt_begin]
89 else:
90 options = None
91
92 if output not in (C.LOG_OPT_OUTPUT_DEFAULT, C.LOG_OPT_OUTPUT_FILE, C.LOG_OPT_OUTPUT_MEMORY):
93 raise ValueError(u"Invalid output [%s]" % output)
94
95 if output == C.LOG_OPT_OUTPUT_DEFAULT:
96 # no option for defaut handler
97 _handlers[output] = None
98 elif output == C.LOG_OPT_OUTPUT_FILE:
99 if not options:
100 ValueError("%(handler)s output need a path as option" % {'handler': output})
101 _handlers[output] = options
102 options = None # option are parsed, we can empty them
103 elif output == C.LOG_OPT_OUTPUT_MEMORY:
104 # we have memory handler, option can be the len limit or None
105 try:
106 limit = int(options)
107 options = None # option are parsed, we can empty them
108 except (TypeError, ValueError):
109 limit = C.LOG_OPT_OUTPUT_MEMORY_LIMIT
110 _handlers[output] = limit
111
112 if options: # we should not have unparsed options
113 raise ValueError(u"options [%(options)s] are not supported for %(handler)s output" % {'options': options, 'handler': output})
114
115 def _configureStdLogging(logging, level=None, fmt=C.LOG_OPT_FORMAT[1], output=C.LOG_OPT_OUTPUT[1], logger=None, colors=False, force_colors=False):
53 """Configure standard logging module 116 """Configure standard logging module
117
54 @param logging: standard logging module 118 @param logging: standard logging module
119 @param level: one of C.LOG_LEVELS
120 @param fmt: format string, pretty much as in std logging. Accept the following keywords (maybe more depending on backend):
121 - "message"
122 - "levelname"
123 - "name" (logger name)
124 @param logger: if set, use it as a regular expression to filter on logger name.
125 Use search to match expression, so ^ or $ can be necessary.
55 @param colors: if True use ANSI colors to show log levels 126 @param colors: if True use ANSI colors to show log levels
56 @param force_colors: if True ANSI colors are used even if stdout is not a tty 127 @param force_colors: if True ANSI colors are used even if stdout is not a tty
57 """ 128 """
58 FORMAT = '%(message)s' 129 format_ = fmt
59 if level is None: 130 if level is None:
60 level = logging.DEBUG 131 level = C.LOG_LVL_DEBUG
61 import sys 132 import sys
62 with_color = colors & (sys.stdout.isatty() or force_colors) 133 with_color = colors & (sys.stdout.isatty() or force_colors)
63 if not colors and force_colors: 134 if not colors and force_colors:
64 raise ValueError("force_colors can't be used if colors is False") 135 raise ValueError("force_colors can't be used if colors is False")
65 136
96 167
97 return s 168 return s
98 169
99 root_logger = logging.getLogger() 170 root_logger = logging.getLogger()
100 if len(root_logger.handlers) == 0: 171 if len(root_logger.handlers) == 0:
101 hdlr = logging.StreamHandler() 172 _manageOutputs(output)
102 formatter = SatFormatter(FORMAT) 173 formatter = SatFormatter(format_)
103 hdlr.setFormatter(formatter) 174 name_filter = FilterName(logger) if logger else None
104 root_logger.addHandler(hdlr) 175 for handler, options in _handlers.items():
105 root_logger.setLevel(level) 176 if handler == C.LOG_OPT_OUTPUT_DEFAULT:
177 hdlr = logging.StreamHandler()
178 elif handler == C.LOG_OPT_OUTPUT_MEMORY:
179 import logging.handlers
180 hdlr = logging.handlers.BufferingHandler(options)
181 elif handler == C.LOG_OPT_OUTPUT_FILE:
182 import os.path
183 hdlr = logging.FileHandler(os.path.expanduser(options))
184 else:
185 raise ValueError("Unknown handler type")
186 hdlr.setFormatter(formatter)
187 root_logger.addHandler(hdlr)
188 root_logger.setLevel(level)
189 if name_filter is not None:
190 hdlr.addFilter(name_filter)
106 else: 191 else:
107 root_logger.warning(u"Handler already set on root logger") 192 root_logger.warning(u"Handlers already set on root logger")
108 193
109 def configure(backend=C.LOG_BACKEND_STANDARD, **options): 194 def configure(backend=C.LOG_BACKEND_STANDARD, **options):
110 """Configure logging bejaviour 195 """Configure logging bejaviour
111 @param backend: can be: 196 @param backend: can be:
112 C.LOG_BACKEND_STANDARD: use standard logging module 197 C.LOG_BACKEND_STANDARD: use standard logging module
147 def _parseOptions(options): 232 def _parseOptions(options):
148 """Parse string options as given in conf or environment variable, and return expected python value 233 """Parse string options as given in conf or environment variable, and return expected python value
149 234
150 @param options (dict): options with (key: name, value: string value) 235 @param options (dict): options with (key: name, value: string value)
151 """ 236 """
152 if 'colors' in options: 237 COLORS = C.LOG_OPT_COLORS[0]
153 if options['colors'].lower() in ('1', 'true'): 238 LEVEL = C.LOG_OPT_LEVEL[0]
154 options['colors'] = True 239
155 elif options['colors'] == 'force': 240 if COLORS in options:
156 options['colors'] = True 241 if options[COLORS].lower() in ('1', 'true'):
242 options[COLORS] = True
243 elif options[COLORS] == 'force':
244 options[COLORS] = True
157 options['force_colors'] = True 245 options['force_colors'] = True
158 else: 246 else:
159 options['colors'] = False 247 options[COLORS] = False
160 if 'level' in options: 248 if LEVEL in options:
161 level = options['level'].upper() 249 level = options[LEVEL].upper()
162 if level not in ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'): 250 if level not in C.LOG_LEVELS:
163 level = 'INFO' 251 level = C.LOG_LVL_INFO
164 options['level'] = level 252 options[LEVEL] = level
165 253
166 def satConfigure(backend=C.LOG_BACKEND_TWISTED): 254 def satConfigure(backend=C.LOG_BACKEND_TWISTED):
167 """Configure logging system for SàT, can be used by frontends 255 """Configure logging system for SàT, can be used by frontends
168 256
169 logs conf is read in SàT conf, then in environment variables. It must be done before Memory init 257 logs conf is read in SàT conf, then in environment variables. It must be done before Memory init
170 """ 258 """
171 import ConfigParser 259 import ConfigParser
172 import os 260 import os
173 to_get = (C.LOG_OPT_COLORS, C.LOG_OPT_LEVEL)
174 log_conf = {} 261 log_conf = {}
175 config = ConfigParser.SafeConfigParser() 262 config = ConfigParser.SafeConfigParser()
176 config.read(C.CONFIG_FILES) 263 config.read(C.CONFIG_FILES)
177 for opt_name, opt_default in to_get: 264 for opt_name, opt_default in C.LOG_OPTIONS:
178 try: 265 try:
179 log_conf[opt_name] = os.environ[''.join((C.ENV_PREFIX, C.LOG_OPT_PREFIX.upper(), opt_name.upper()))] 266 log_conf[opt_name] = os.environ[''.join((C.ENV_PREFIX, C.LOG_OPT_PREFIX.upper(), opt_name.upper()))]
180 except KeyError: 267 except KeyError:
181 try: 268 try:
182 log_conf[opt_name] = config.get('DEFAULT', C.LOG_OPT_PREFIX + opt_name) 269 log_conf[opt_name] = config.get('DEFAULT', C.LOG_OPT_PREFIX + opt_name)