[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 <juc@odoo.com>
This commit is contained in:
Loan (lse)
2022-12-15 12:36:50 +01:00
committed by Loan (LSE)
parent b9069bbaff
commit a32ddecdf2
3 changed files with 66 additions and 4 deletions
+27
View File
@@ -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
+32
View File
@@ -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 <module>
ZeroDivisionError: division by zero""" % self.test_server_action.id
])
def test_00_action(self):
self.action.with_context(self.context).run()
+7 -4
View File
@@ -331,13 +331,16 @@
<h3>Help with Python expressions</h3>
<p>Various fields may use Python code or Python expressions. The following variables can be used:</p>
<ul>
<li><code>env</code>: Odoo Environment on which the action is triggered</li>
<li><code>model</code>: Odoo Model of the record on which the action is triggered; is a void recordset</li>
<li><code>env</code>: environment on which the action is triggered</li>
<li><code>model</code>: model of the record on which the action is triggered; is a void recordset</li>
<li><code>record</code>: record on which the action is triggered; may be be void</li>
<li><code>records</code>: recordset of all records on which the action is triggered in multi mode; may be void</li>
<li><code>time</code>, <code>datetime</code>, <code>dateutil</code>, <code>timezone</code>: useful Python libraries</li>
<li><code>log(message, level='info')</code>:logging function to record debug information in <code>ir.logging</code> table</li>
<li><code>UserError</code>: Warning Exception to use with <code>raise</code></li>
<li><code>float_compare()</code>: utility function to compare floats based on a specific precision</li>
<li><code>log(message, level='info')</code>: logging function to record debug information in <code>ir.logging</code> table</li>
<li><code>_logger.info(message)</code>: logger to emit messages in server logs</li>
<li><code>UserError</code>: exception class for raising user-facing warning messages</li>
<li><code>Command</code>: x2many commands namespace</li>
<li>To return an action, assign: <code>action = {...}</code></li>
</ul>
<div attrs="{'invisible': [('state', '!=', 'code')]}">