Skip to content
Draft
26 changes: 21 additions & 5 deletions rest_log/README.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association

========
REST Log
========
Expand All @@ -17,7 +13,7 @@ REST Log
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Frest--framework-lightgray.png?logo=github
Expand All @@ -38,6 +34,10 @@ especially in case of errors.
This module add DB logging for REST requests. It also inject in the
response the URL of the log entry created.

It can also enable profiling for selected REST endpoints. When profiling
is active, the request execution is wrapped by Odoo's profiler and
results are stored in ``ir_profile``.

NOTE: this feature was implemented initially inside shopfloor app. Up to
version 13.0.1.2.1 of this module, if shopfloor is installed, log
records will be copied from its table.
Expand Down Expand Up @@ -88,6 +88,22 @@ In the 2nd case you can set ``rest.log.active`` param as:
`collection_name.usage.endpoint` # enable for specific endpoints
`collection_name*:state` # enable only for specific state (success, failed)

Profiling
---------

Profiling is enabled per endpoint and per user via system parameters:

- ``rest.log.profiling.conf``: same matching syntax as
``rest.log.active``
- ``rest.log.profiling.uid``: comma-separated list of user ids allowed
to profile

When both parameters match, the request execution is wrapped in Odoo's
profiler and the results are stored in ``ir_profile``.

``base.profiling_enabled_until`` is only needed to view speedscope
output in the UI. It is not required to record profiles.

Changelog
=========

Expand Down
47 changes: 47 additions & 0 deletions rest_log/components/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from odoo.http import Response, request
from odoo.modules.registry import Registry
from odoo.service.model import PG_CONCURRENCY_ERRORS_TO_RETRY
from odoo.tools.profiler import Profiler

from odoo.addons.base_rest.http import JSONEncoder
from odoo.addons.component.core import AbstractComponent
Expand Down Expand Up @@ -41,6 +42,9 @@ class BaseRESTService(AbstractComponent):
def dispatch(self, method_name, *args, params=None):
if not self._db_logging_active(method_name):
return super().dispatch(method_name, *args, params=params)
if self._start_profiling(method_name):
with self._profiling_get_profiler():
return self._dispatch_with_db_logging(method_name, *args, params=params)
return self._dispatch_with_db_logging(method_name, *args, params=params)

def _dispatch_with_db_logging(self, method_name, *args, params=None):
Expand Down Expand Up @@ -224,3 +228,46 @@ def _get_matching_active_conf(self, method_name):
return self.env["rest.log"]._get_matching_active_conf(
self._collection, self._usage, method_name
)

def _start_profiling(self, method_name):
if request.session.profile_session and request.db:
return None
profiling_uids = self._profiling_get_uids()
profiling_conf_match = self._profiling_get_matching_conf(method_name)
res = profiling_conf_match and self.env.uid in profiling_uids
if res:
_logger.info(
"Profiling enabled for uids=%s %s",
str(profiling_uids),
f"{self._collection}.{self._usage}.{method_name}",
)
return res

def _profiling_get_uids(self):
try:
param = (
self.env["ir.config_parameter"]
.sudo()
.get_param("rest.log.profiling.uids", "")
)
if not param.strip():
return []
return [int(x.strip()) for x in param.strip().split(",") if x.strip()]
except ValueError as err:
_logger.warning(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should return None after ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If no valid uid is found, then profiling_uid is set to 0, which never matches self.env.uid.
Then nothing happens.

"Cannot get uid from system parameter rest.log.profiling.uid: %s",
str(err),
)
return []

def _profiling_get_matching_conf(self, method_name):
return self.env["rest.log"]._get_matching_conf_from_param(
"rest.log.profiling.conf", self._collection, self._usage, method_name
)

def _profiling_get_profiler(self, method_name):
call_name = f"{self._collection}.{self._usage}.{method_name}"
return Profiler(
description=f"REST LOG {call_name}",
profile_session=f"{self.env.user.name} (uid={self.env.uid})",
)
20 changes: 18 additions & 2 deletions rest_log/models/rest_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,10 @@ def autovacuum(self):
return True

def _get_log_active_param(self):
param = self.env["ir.config_parameter"].sudo().get_param("rest.log.active")
return self._get_log_param("rest.log.active")

def _get_log_param(self, config_parameter):
param = self.env["ir.config_parameter"].sudo().get_param(config_parameter)
return param.strip() if param else ""

@tools.ormcache("self._get_log_active_param()")
Expand All @@ -150,7 +153,11 @@ def _get_log_active_conf(self):
:return: mapping by matching key / enabled states
"""
param = self._get_log_active_param()
# TODO: Deprecate in favour of _get_log_param
config_param = self._get_log_active_param()
return self._get_conf_from_param_value(config_param)

def _get_conf_from_param_value(self, param):
conf = {}
lines = [x.strip() for x in param.split(",") if x.strip()]
for line in lines:
Expand All @@ -169,7 +176,16 @@ def _get_log_active_conf(self):
@api.model
def _get_matching_active_conf(self, collection, usage, method_name):
"""Retrieve conf matching current service and method."""
# TODO: Deprecate in favour of _get_matching_conf_from_param
conf = self._get_log_active_conf()
return self._get_matching_conf(conf, collection, usage, method_name)

def _get_matching_conf_from_param(self, param, collection, usage, method_name):
config_param = self._get_log_param(param)
conf = self._get_conf_from_param_value(config_param)
return self._get_matching_conf(conf, collection, usage, method_name)

def _get_matching_conf(self, conf, collection, usage, method_name):
candidates = (
collection + "." + usage + "." + method_name,
collection + "." + usage,
Expand Down
13 changes: 13 additions & 0 deletions rest_log/readme/CONFIGURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,16 @@ In the 2nd case you can set `rest.log.active` param as:
`collection_name.usage` # enable for specific endpoints
`collection_name.usage.endpoint` # enable for specific endpoints
`collection_name*:state` # enable only for specific state (success, failed)

## Profiling

Profiling is enabled per endpoint and per user via system parameters:

- `rest.log.profiling.conf`: same matching syntax as `rest.log.active`
- `rest.log.profiling.uid`: comma-separated list of user ids allowed to profile

When both parameters match, the request execution is wrapped in Odoo's
profiler and the results are stored in `ir_profile`.

`base.profiling_enabled_until` is only needed to view speedscope output
in the UI. It is not required to record profiles.
4 changes: 4 additions & 0 deletions rest_log/readme/DESCRIPTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ especially in case of errors.
This module add DB logging for REST requests. It also inject in the
response the URL of the log entry created.

It can also enable profiling for selected REST endpoints. When profiling is
active, the request execution is wrapped by Odoo's profiler and results are
stored in `ir_profile`.

NOTE: this feature was implemented initially inside shopfloor app. Up to
version 13.0.1.2.1 of this module, if shopfloor is installed, log
records will be copied from its table.
70 changes: 41 additions & 29 deletions rest_log/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>README.rst</title>
<title>REST Log</title>
<style type="text/css">

/*
Expand Down Expand Up @@ -360,25 +360,23 @@
</style>
</head>
<body>
<div class="document">
<div class="document" id="rest-log">
<h1 class="title">REST Log</h1>


<a class="reference external image-reference" href="https://odoo-community.org/get-involved?utm_source=readme">
<img alt="Odoo Community Association" src="https://odoo-community.org/readme-banner-image" />
</a>
<div class="section" id="rest-log">
<h1>REST Log</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:1149568a02aa9975271ddee0f11ef624385750bef50e4b92f44ab55de7680496
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/license-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/rest-framework/tree/18.0/rest_log"><img alt="OCA/rest-framework" src="https://img.shields.io/badge/github-OCA%2Frest--framework-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/rest-framework-18-0/rest-framework-18-0-rest_log"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/rest-framework&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/rest-framework/tree/18.0/rest_log"><img alt="OCA/rest-framework" src="https://img.shields.io/badge/github-OCA%2Frest--framework-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/rest-framework-18-0/rest-framework-18-0-rest_log"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/rest-framework&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>When exposing REST services is often useful to see what’s happening
especially in case of errors.</p>
<p>This module add DB logging for REST requests. It also inject in the
response the URL of the log entry created.</p>
<p>It can also enable profiling for selected REST endpoints. When profiling
is active, the request execution is wrapped by Odoo’s profiler and
results are stored in <tt class="docutils literal">ir_profile</tt>.</p>
<p>NOTE: this feature was implemented initially inside shopfloor app. Up to
version 13.0.1.2.1 of this module, if shopfloor is installed, log
records will be copied from its table.</p>
Expand All @@ -388,26 +386,27 @@ <h1>REST Log</h1>
<li><a class="reference internal" href="#configuration" id="toc-entry-1">Configuration</a><ul>
<li><a class="reference internal" href="#logs-retention" id="toc-entry-2">Logs retention</a></li>
<li><a class="reference internal" href="#logs-activation" id="toc-entry-3">Logs activation</a></li>
<li><a class="reference internal" href="#profiling" id="toc-entry-4">Profiling</a></li>
</ul>
</li>
<li><a class="reference internal" href="#changelog" id="toc-entry-4">Changelog</a><ul>
<li><a class="reference internal" href="#section-1" id="toc-entry-5">13.0.1.0.0</a></li>
<li><a class="reference internal" href="#changelog" id="toc-entry-5">Changelog</a><ul>
<li><a class="reference internal" href="#section-1" id="toc-entry-6">13.0.1.0.0</a></li>
</ul>
</li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-6">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-7">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-8">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-9">Contributors</a></li>
<li><a class="reference internal" href="#other-credits" id="toc-entry-10">Other credits</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-11">Maintainers</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-7">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-8">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-9">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-10">Contributors</a></li>
<li><a class="reference internal" href="#other-credits" id="toc-entry-11">Other credits</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-12">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h2><a class="toc-backref" href="#toc-entry-1">Configuration</a></h2>
<h1><a class="toc-backref" href="#toc-entry-1">Configuration</a></h1>
<div class="section" id="logs-retention">
<h3><a class="toc-backref" href="#toc-entry-2">Logs retention</a></h3>
<h2><a class="toc-backref" href="#toc-entry-2">Logs retention</a></h2>
<p>Logs are kept in database for every REST requests made by a client
application. They can be used for debugging and monitoring of the
activity.</p>
Expand All @@ -421,7 +420,7 @@ <h3><a class="toc-backref" href="#toc-entry-2">Logs retention</a></h3>
error.</p>
</div>
<div class="section" id="logs-activation">
<h3><a class="toc-backref" href="#toc-entry-3">Logs activation</a></h3>
<h2><a class="toc-backref" href="#toc-entry-3">Logs activation</a></h2>
<p>You have 2 ways to activate logging:</p>
<ul class="simple">
<li>on the service component set _log_calls_in_db = True</li>
Expand All @@ -436,40 +435,54 @@ <h3><a class="toc-backref" href="#toc-entry-3">Logs activation</a></h3>
`collection_name*:state` # enable only for specific state (success, failed)
</pre>
</div>
<div class="section" id="profiling">
<h2><a class="toc-backref" href="#toc-entry-4">Profiling</a></h2>
<p>Profiling is enabled per endpoint and per user via system parameters:</p>
<ul class="simple">
<li><tt class="docutils literal">rest.log.profiling.conf</tt>: same matching syntax as
<tt class="docutils literal">rest.log.active</tt></li>
<li><tt class="docutils literal">rest.log.profiling.uid</tt>: comma-separated list of user ids allowed
to profile</li>
</ul>
<p>When both parameters match, the request execution is wrapped in Odoo’s
profiler and the results are stored in <tt class="docutils literal">ir_profile</tt>.</p>
<p><tt class="docutils literal">base.profiling_enabled_until</tt> is only needed to view speedscope
output in the UI. It is not required to record profiles.</p>
</div>
</div>
<div class="section" id="changelog">
<h2><a class="toc-backref" href="#toc-entry-4">Changelog</a></h2>
<h1><a class="toc-backref" href="#toc-entry-5">Changelog</a></h1>
<div class="section" id="section-1">
<h3><a class="toc-backref" href="#toc-entry-5">13.0.1.0.0</a></h3>
<h2><a class="toc-backref" href="#toc-entry-6">13.0.1.0.0</a></h2>
<p>First official version.</p>
</div>
</div>
<div class="section" id="bug-tracker">
<h2><a class="toc-backref" href="#toc-entry-6">Bug Tracker</a></h2>
<h1><a class="toc-backref" href="#toc-entry-7">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/rest-framework/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/rest-framework/issues/new?body=module:%20rest_log%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h2><a class="toc-backref" href="#toc-entry-7">Credits</a></h2>
<h1><a class="toc-backref" href="#toc-entry-8">Credits</a></h1>
<div class="section" id="authors">
<h3><a class="toc-backref" href="#toc-entry-8">Authors</a></h3>
<h2><a class="toc-backref" href="#toc-entry-9">Authors</a></h2>
<ul class="simple">
<li>Camptocamp</li>
<li>ACSONE</li>
</ul>
</div>
<div class="section" id="contributors">
<h3><a class="toc-backref" href="#toc-entry-9">Contributors</a></h3>
<h2><a class="toc-backref" href="#toc-entry-10">Contributors</a></h2>
<ul class="simple">
<li>Guewen Baconnier &lt;<a class="reference external" href="mailto:guewen.baconnier&#64;camptocamp.com">guewen.baconnier&#64;camptocamp.com</a>&gt;</li>
<li>Simone Orsi &lt;<a class="reference external" href="mailto:simahawk&#64;gmail.com">simahawk&#64;gmail.com</a>&gt;</li>
</ul>
</div>
<div class="section" id="other-credits">
<h3><a class="toc-backref" href="#toc-entry-10">Other credits</a></h3>
<h2><a class="toc-backref" href="#toc-entry-11">Other credits</a></h2>
<p><strong>Financial support</strong></p>
<ul class="simple">
<li>Cosanum</li>
Expand All @@ -478,7 +491,7 @@ <h3><a class="toc-backref" href="#toc-entry-10">Other credits</a></h3>
</ul>
</div>
<div class="section" id="maintainers">
<h3><a class="toc-backref" href="#toc-entry-11">Maintainers</a></h3>
<h2><a class="toc-backref" href="#toc-entry-12">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
Expand All @@ -493,6 +506,5 @@ <h3><a class="toc-backref" href="#toc-entry-11">Maintainers</a></h3>
</div>
</div>
</div>
</div>
</body>
</html>
1 change: 1 addition & 0 deletions rest_log/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from . import test_db_logging
from . import test_db_logging_exception
Loading