Browse Source
Merge pull request #777 from akretion/8.0-logging-json
Merge pull request #777 from akretion/8.0-logging-json
[8.0] add module logging jsonpull/970/head
committed by
GitHub
5 changed files with 215 additions and 0 deletions
-
90logging_json/README.rst
-
3logging_json/__init__.py
-
18logging_json/__openerp__.py
-
103logging_json/json_log.py
-
1requirements.txt
@ -0,0 +1,90 @@ |
|||||
|
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg |
||||
|
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html |
||||
|
:alt: License: AGPL-3 |
||||
|
|
||||
|
============== |
||||
|
JSON Logging |
||||
|
============== |
||||
|
|
||||
|
This addon allows to output the Odoo logs in JSON. |
||||
|
|
||||
|
|
||||
|
It also add the possibility to log extra data in json in your log. |
||||
|
See documentation of json logger here : https://github.com/madzak/python-json-logger |
||||
|
|
||||
|
Example: |
||||
|
|
||||
|
.. code-block:: python |
||||
|
|
||||
|
_logger.info('My Message', extra={ |
||||
|
'my_extra_key_1': 'value_1', |
||||
|
'my_extra_key_2': 'value_2'}) |
||||
|
|
||||
|
The key "extra" is a standard key of logger module so you can add this key and your log will work with or without this module (see documentation here : https://docs.python.org/2/library/logging.html#logging.Logger.debug) |
||||
|
|
||||
|
|
||||
|
Configuration |
||||
|
============= |
||||
|
|
||||
|
The json logging is activated with the environment variable |
||||
|
``ODOO_LOGGING_JSON`` set to ``1``. |
||||
|
|
||||
|
In order to have the logs from the start of the server, you should add |
||||
|
``logging_json`` in the ``--load`` flag or in the ``server_wide_modules`` |
||||
|
option in the configuration file. |
||||
|
|
||||
|
If you want to see the extra keys when you are developping in local |
||||
|
but with the odoo logger output and not the JSON output |
||||
|
you can add the environment variable ``ODOO_LOGGING_JSON_DEV`` set to ``1``. |
||||
|
|
||||
|
Note : ``ODOO_LOGGING_JSON_DEV`` and ``ODOO_LOGGING_JSON`` should be not set both to ``1`` |
||||
|
|
||||
|
Known issues / Roadmap |
||||
|
====================== |
||||
|
|
||||
|
* Completed the extraction (in regex) of interesting message to get more metric |
||||
|
|
||||
|
Bug Tracker |
||||
|
=========== |
||||
|
|
||||
|
Bugs are tracked on `GitHub Issues |
||||
|
<https://github.com/OCA/server-tools/issues>`_. In case of trouble, please |
||||
|
check there if your issue has already been reported. If you spotted it first, |
||||
|
help us smash it by providing detailed and welcomed feedback. |
||||
|
|
||||
|
Credits |
||||
|
======= |
||||
|
|
||||
|
Images |
||||
|
------ |
||||
|
|
||||
|
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_. |
||||
|
|
||||
|
Contributors |
||||
|
------------ |
||||
|
|
||||
|
* Guewen Baconnier <guewen.baconnier@camptocamp.com> |
||||
|
* Sébastien BEAU <sebastien.beau@akretion.com> |
||||
|
|
||||
|
Funders |
||||
|
------- |
||||
|
|
||||
|
The development of this module has been financially supported by: |
||||
|
|
||||
|
* Camptocamp |
||||
|
* Akretion |
||||
|
|
||||
|
Maintainer |
||||
|
---------- |
||||
|
|
||||
|
.. image:: https://odoo-community.org/logo.png |
||||
|
:alt: Odoo Community Association |
||||
|
:target: https://odoo-community.org |
||||
|
|
||||
|
This module is maintained by the OCA. |
||||
|
|
||||
|
OCA, or the Odoo Community Association, is a nonprofit organization whose |
||||
|
mission is to support the collaborative development of Odoo features and |
||||
|
promote its widespread use. |
||||
|
|
||||
|
To contribute to this module, please visit https://odoo-community.org. |
@ -0,0 +1,3 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
|
||||
|
from . import json_log |
@ -0,0 +1,18 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2016 Camptocamp SA |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) |
||||
|
|
||||
|
{'name': 'JSON Logging', |
||||
|
'version': '8.0.1.0.0', |
||||
|
'author': 'Camptocamp,Akretion,Odoo Community Association (OCA)', |
||||
|
'license': 'AGPL-3', |
||||
|
'category': 'Extra Tools', |
||||
|
'depends': ['base', |
||||
|
], |
||||
|
'external_dependencies': { |
||||
|
'python': ['pythonjsonlogger'], |
||||
|
}, |
||||
|
'website': 'http://www.camptocamp.com', |
||||
|
'data': [], |
||||
|
'installable': True, |
||||
|
} |
@ -0,0 +1,103 @@ |
|||||
|
# -*- 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 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<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(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 |
Write
Preview
Loading…
Cancel
Save
Reference in new issue