OCA/server-tools

View on GitHub
logging_json/json_log.py

Summary

Maintainability
A
3 hrs
Test Coverage
# -*- coding: utf-8 -*-
# Copyright 2017 Camptocamp (http://www.camptocamp.com).
# @author Guewen Baconnier <guewen.baconnier@camptocamp.com>
# Copyright 2017 Akretion (http://www.akretion.com).
# @author Sébastien BEAU <sebastien.beau@akretion.com>
# 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.jsonlogger import JsonFormatter, RESERVED_ATTRS
except ImportError:
    JsonFormatter = object
    _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<cron_model>[\w.]+).+"
         r"'(?P<cron_method>[\w.]+)'\)",
         {}, {'cron_running': True}),

        # "0.016s (base.action.rule, _check)"
        (r"(?P<cron_duration_second>[\d.]+)s \("
         r"(?P<cron_model>[\w.]+), (?P<cron_method>[\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<cron_model>[\w.]+)'\)"
         r".(?P<cron_method>[\w.]+)",
         {}, {'cron_running': False, 'cron_status': 'failed'}),
    ],
}


class OdooJsonFormatter(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(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