From a32ddecdf2cce152e191da80fb8eeac52d165c16 Mon Sep 17 00:00:00 2001 From: "Loan (lse)" Date: Thu, 19 Aug 2021 13:24:35 +0000 Subject: [PATCH] [ADD] ir_actions.py: logger log in server action in context This PR is mainly meant for the support. Note that the `stack_info` is purposefully accessible to add some stack trace in the logs. Example of support tickets where it can be useful: A certain field of a particular model change it's value with no particular pattern and way to reproduce. With this commit, we can now create an automated actions on the model update trigger to dump the current stack that will lead us on the action that did trigger it. closes odoo/odoo#75320 Signed-off-by: Julien Castiaux --- odoo/addons/base/models/ir_actions.py | 27 +++++++++++++++++ odoo/addons/base/tests/test_ir_actions.py | 32 +++++++++++++++++++++ odoo/addons/base/views/ir_actions_views.xml | 11 ++++--- 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/odoo/addons/base/models/ir_actions.py b/odoo/addons/base/models/ir_actions.py index 68d4719a20f..8aee824604f 100644 --- a/odoo/addons/base/models/ir_actions.py +++ b/odoo/addons/base/models/ir_actions.py @@ -17,6 +17,32 @@ import logging from pytz import timezone _logger = logging.getLogger(__name__) +_server_action_logger = _logger.getChild("server_action_safe_eval") + + +class LoggerProxy: + """ Proxy of the `_logger` element in order to be used in server actions. + We purposefully restrict its method as it will be executed in `safe_eval`. + """ + @staticmethod + def log(level, message, *args, stack_info=False, exc_info=False): + _server_action_logger.log(level, message, *args, stack_info=stack_info, exc_info=exc_info) + + @staticmethod + def info(message, *args, stack_info=False, exc_info=False): + _server_action_logger.info(message, *args, stack_info=stack_info, exc_info=exc_info) + + @staticmethod + def warning(message, *args, stack_info=False, exc_info=False): + _server_action_logger.warning(message, *args, stack_info=stack_info, exc_info=exc_info) + + @staticmethod + def error(message, *args, stack_info=False, exc_info=False): + _server_action_logger.error(message, *args, stack_info=stack_info, exc_info=exc_info) + + @staticmethod + def exception(message, *args, stack_info=False, exc_info=True): + _server_action_logger.exception(message, *args, stack_info=stack_info, exc_info=exc_info) class IrActions(models.Model): @@ -610,6 +636,7 @@ class IrActionsServer(models.Model): 'records': records, # helpers 'log': log, + '_logger': LoggerProxy, }) return eval_context diff --git a/odoo/addons/base/tests/test_ir_actions.py b/odoo/addons/base/tests/test_ir_actions.py index 8646a341278..a6f5b81999e 100644 --- a/odoo/addons/base/tests/test_ir_actions.py +++ b/odoo/addons/base/tests/test_ir_actions.py @@ -59,8 +59,40 @@ class TestServerActionsBase(common.TransactionCase): 'code': 'record.write({"comment": "%s"})' % self.comment_html, }) + server_action_model = Model.search([('model', '=', 'ir.actions.server')]) + self.test_server_action = self.env['ir.actions.server'].create({ + 'name': 'TestDummyServerAction', + 'model_id': server_action_model.id, + 'state': 'code', + 'code': +""" +_logger.log(10, "This is a %s debug %s", "test", "log") +_logger.info("This is a %s info %s", "test", "log") +_logger.warning("This is a %s warning %s", "test", "log") +_logger.error("This is a %s error %s", "test", "log") +try: + 0/0 +except: + _logger.exception("This is a %s exception %s", "test", "log") +""", + }) + class TestServerActions(TestServerActionsBase): + def test_00_server_action(self): + with self.assertLogs('odoo.addons.base.models.ir_actions.server_action_safe_eval', + level='DEBUG') as log_catcher: + self.test_server_action.run() + self.assertEqual(log_catcher.output, [ + 'DEBUG:odoo.addons.base.models.ir_actions.server_action_safe_eval:This is a test debug log', + 'INFO:odoo.addons.base.models.ir_actions.server_action_safe_eval:This is a test info log', + 'WARNING:odoo.addons.base.models.ir_actions.server_action_safe_eval:This is a test warning log', + 'ERROR:odoo.addons.base.models.ir_actions.server_action_safe_eval:This is a test error log', +"""ERROR:odoo.addons.base.models.ir_actions.server_action_safe_eval:This is a test exception log +Traceback (most recent call last): + File "ir.actions.server(%d,)", line 6, in +ZeroDivisionError: division by zero""" % self.test_server_action.id + ]) def test_00_action(self): self.action.with_context(self.context).run() diff --git a/odoo/addons/base/views/ir_actions_views.xml b/odoo/addons/base/views/ir_actions_views.xml index e59d3b42bc1..faef35f41d4 100644 --- a/odoo/addons/base/views/ir_actions_views.xml +++ b/odoo/addons/base/views/ir_actions_views.xml @@ -331,13 +331,16 @@

Help with Python expressions

Various fields may use Python code or Python expressions. The following variables can be used:

    -
  • env: Odoo Environment on which the action is triggered
  • -
  • model: Odoo Model of the record on which the action is triggered; is a void recordset
  • +
  • env: environment on which the action is triggered
  • +
  • model: model of the record on which the action is triggered; is a void recordset
  • record: record on which the action is triggered; may be be void
  • records: recordset of all records on which the action is triggered in multi mode; may be void
  • time, datetime, dateutil, timezone: useful Python libraries
  • -
  • log(message, level='info'):logging function to record debug information in ir.logging table
  • -
  • UserError: Warning Exception to use with raise
  • +
  • float_compare(): utility function to compare floats based on a specific precision
  • +
  • log(message, level='info'): logging function to record debug information in ir.logging table
  • +
  • _logger.info(message): logger to emit messages in server logs
  • +
  • UserError: exception class for raising user-facing warning messages
  • +
  • Command: x2many commands namespace
  • To return an action, assign: action = {...}