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