You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

103 lines
3.7 KiB

  1. # -*- coding: utf-8 -*-
  2. # Copyright 2017 Camptocamp (http://www.camptocamp.com).
  3. # @author Guewen Baconnier <guewen.baconnier@camptocamp.com>
  4. # Copyright 2017 Akretion (http://www.akretion.com).
  5. # @author Sébastien BEAU <sebastien.beau@akretion.com>
  6. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
  7. import logging
  8. import os
  9. import threading
  10. import re
  11. import json
  12. from distutils.util import strtobool
  13. from openerp.netsvc import ColoredFormatter
  14. _logger = logging.getLogger(__name__)
  15. try:
  16. from pythonjsonlogger.jsonlogger import JsonFormatter, RESERVED_ATTRS
  17. except ImportError:
  18. JsonFormatter = object
  19. _logger.debug("Cannot 'import pythonjsonlogger'.")
  20. def is_true(strval):
  21. return bool(strtobool(strval or '0'.lower()))
  22. # The following regex are use to extract information from native odoo log
  23. # We struct is the following
  24. # {name_of_the_log : [regex, formatter, extra_vals]}
  25. # The extra_vals "dict" will be merged into the jsonlogger if the regex match
  26. # In order to make understandable the regex please add an string example that
  27. # match
  28. REGEX = {
  29. 'openerp.addons.base.ir.ir_cron': [
  30. # "cron.object.execute('db', 1, '*', u'base.action.rule', u'_check')"
  31. (r"cron\.object\.execute\('.+'(?P<cron_model>[\w.]+).+"
  32. r"'(?P<cron_method>[\w.]+)'\)",
  33. {}, {'cron_running': True}),
  34. # "0.016s (base.action.rule, _check)"
  35. (r"(?P<cron_duration_second>[\d.]+)s \("
  36. r"(?P<cron_model>[\w.]+), (?P<cron_method>[\w.]+)",
  37. {'cron_duration_second': float},
  38. {'cron_running': False, 'cron_status': 'succeeded'}),
  39. # Call of self.pool.get('base.action.rule')._check(
  40. # cr, uid, *u'()') failed in Job 43
  41. (r"Call of self\.pool\.get\('(?P<cron_model>[\w.]+)'\)"
  42. r".(?P<cron_method>[\w.]+)",
  43. {}, {'cron_running': False, 'cron_status': 'failed'}),
  44. ],
  45. }
  46. class OdooJsonFormatter(JsonFormatter):
  47. def add_fields(self, log_record, record, message_dict):
  48. record.pid = os.getpid()
  49. record.dbname = getattr(threading.currentThread(), 'dbname', '?')
  50. _super = super(OdooJsonFormatter, self)
  51. res = _super.add_fields(log_record, record, message_dict)
  52. if log_record['name'] in REGEX:
  53. for regex, formatters, extra_vals in REGEX[log_record['name']]:
  54. match = re.match(regex, log_record['message'])
  55. if match:
  56. vals = match.groupdict()
  57. for key, func in formatters.items():
  58. vals[key] = func(vals[key])
  59. log_record.update(vals)
  60. if extra_vals:
  61. log_record.update(extra_vals)
  62. return res
  63. class OdooJsonDevFormatter(ColoredFormatter):
  64. def format(self, record):
  65. response = super(OdooJsonDevFormatter, self).format(record)
  66. extra = {}
  67. reserved_attrs = list(RESERVED_ATTRS) + ["dbname", "pid"]
  68. for key, value in record.__dict__.items():
  69. if (key not in reserved_attrs and not
  70. (hasattr(key, "startswith") and key.startswith('_'))):
  71. extra[key] = value
  72. if extra:
  73. response += " extra: " + json.dumps(
  74. extra, indent=4, sort_keys=True)
  75. return response
  76. if is_true(os.environ.get('ODOO_LOGGING_JSON')):
  77. format = ('%(asctime)s %(pid)s %(levelname)s'
  78. '%(dbname)s %(name)s: %(message)s')
  79. formatter = OdooJsonFormatter(format)
  80. logging.getLogger().handlers[0].formatter = formatter
  81. elif is_true(os.environ.get('ODOO_LOGGING_JSON_DEV')):
  82. format = ('%(asctime)s %(pid)s %(levelname)s'
  83. '%(dbname)s %(name)s: %(message)s')
  84. formatter = OdooJsonDevFormatter(format)
  85. logging.getLogger().handlers[0].formatter = formatter