# -*- coding: utf-8 -*- # Copyright 2017 Camptocamp (http://www.camptocamp.com). # @author Guewen Baconnier # Copyright 2017 Akretion (http://www.akretion.com). # @author Sébastien BEAU # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import logging import os import threading import re import json from distutils.util import strtobool from openerp.netsvc import ColoredFormatter _logger = logging.getLogger(__name__) try: from pythonjsonlogger import jsonlogger except ImportError: jsonlogger = None # noqa _logger.debug("Cannot 'import pythonjsonlogger'.") def is_true(strval): return bool(strtobool(strval or '0'.lower())) # The following regex are use to extract information from native odoo log # We struct is the following # {name_of_the_log : [regex, formatter, extra_vals]} # The extra_vals "dict" will be merged into the jsonlogger if the regex match # In order to make understandable the regex please add an string example that # match REGEX = { 'openerp.addons.base.ir.ir_cron': [ # "cron.object.execute('db', 1, '*', u'base.action.rule', u'_check')" (r"cron\.object\.execute\('.+'(?P[\w.]+).+" r"'(?P[\w.]+)'\)", {}, {'cron_running': True}), # "0.016s (base.action.rule, _check)" (r"(?P[\d.]+)s \(" r"(?P[\w.]+), (?P[\w.]+)", {'cron_duration_second': float}, {'cron_running': False, 'cron_status': 'succeeded'}), # Call of self.pool.get('base.action.rule')._check( # cr, uid, *u'()') failed in Job 43 (r"Call of self\.pool\.get\('(?P[\w.]+)'\)" r".(?P[\w.]+)", {}, {'cron_running': False, 'cron_status': 'failed'}), ], } class OdooJsonFormatter(jsonlogger.JsonFormatter): def add_fields(self, log_record, record, message_dict): record.pid = os.getpid() record.dbname = getattr(threading.currentThread(), 'dbname', '?') _super = super(OdooJsonFormatter, self) res = _super.add_fields(log_record, record, message_dict) if log_record['name'] in REGEX: for regex, formatters, extra_vals in REGEX[log_record['name']]: match = re.match(regex, log_record['message']) if match: vals = match.groupdict() for key, func in formatters.items(): vals[key] = func(vals[key]) log_record.update(vals) if extra_vals: log_record.update(extra_vals) return res class OdooJsonDevFormatter(ColoredFormatter): def format(self, record): response = super(OdooJsonDevFormatter, self).format(record) extra = {} RESERVED_ATTRS = list(jsonlogger.RESERVED_ATTRS) + ["dbname", "pid"] for key, value in record.__dict__.items(): if (key not in RESERVED_ATTRS and not (hasattr(key, "startswith") and key.startswith('_'))): extra[key] = value if extra: response += " extra: " + json.dumps( extra, indent=4, sort_keys=True) return response if is_true(os.environ.get('ODOO_LOGGING_JSON')): format = ('%(asctime)s %(pid)s %(levelname)s' '%(dbname)s %(name)s: %(message)s') formatter = OdooJsonFormatter(format) logging.getLogger().handlers[0].formatter = formatter elif is_true(os.environ.get('ODOO_LOGGING_JSON_DEV')): format = ('%(asctime)s %(pid)s %(levelname)s' '%(dbname)s %(name)s: %(message)s') formatter = OdooJsonDevFormatter(format) logging.getLogger().handlers[0].formatter = formatter