Skip to content

[18.0][MIG] rest_log #509

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 60 commits into
base: 18.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
be5cb14
Add rest_log
simahawk Jan 29, 2021
45bc2e3
rest_log: add init hook for shopfloor.log
simahawk Jan 29, 2021
1d5e311
rest_log: move exceptions to their own file
simahawk Feb 4, 2021
303aaa7
rest_log: enable via conf per collection or usage
simahawk Feb 4, 2021
9ad153f
rest_log: inject log entry URL only for dict response
simahawk Feb 4, 2021
d5e4187
rest_log: add TODO to refactor dispatch w/ log
simahawk Feb 4, 2021
298263b
[ADD] icon.png
OCA-git-bot Feb 10, 2021
b4037f0
rest_log 13.0.1.0.1
OCA-git-bot Feb 10, 2021
e5fec8b
rest_log: re-license to LGPL
simahawk Feb 16, 2021
7cbba64
rest_log: fix init hook
simahawk Feb 16, 2021
cb0f0d1
rest_log 13.0.1.0.2
OCA-git-bot Feb 17, 2021
29356d8
rest_log 13.0.1.1.0
OCA-git-bot Feb 17, 2021
13c16c5
rest_log: fix test usage of MockRequest
simahawk Mar 8, 2021
8d0ddf8
rest_log 13.0.1.1.1
OCA-git-bot Mar 16, 2021
5f16478
[FIX] pre-commit
lmignon Mar 19, 2021
1b690c0
[UPD] README.rst
OCA-git-bot Mar 22, 2021
ad5075a
rest_log 14.0.1.0.2
OCA-git-bot Mar 22, 2021
bb48346
[UPD] Update rest_log.pot
oca-travis Aug 25, 2021
a6808fe
Initialize 15.0 branch
lmignon Nov 22, 2021
4a66819
rest_log: improve logging active conf
simahawk Apr 21, 2021
199317e
rest.log: store collection, filter and group by
simahawk Apr 21, 2021
4670c32
rest_log: prevent JSONEncodingError
simahawk Apr 21, 2021
79dfb55
rest_log: update README
simahawk Apr 21, 2021
8538013
rest_log: fix _dispatch_exception
simahawk May 3, 2021
22ca60a
rest_log: add full test cov for exception handling
simahawk May 3, 2021
7340d73
rest_log: drop post init hook for shopfloor.log
simahawk May 18, 2021
95abee6
rest_log: dev status -> Beta
simahawk Jan 20, 2022
1d6349f
rest_log: fix exc catch order
simahawk Jan 21, 2022
866a14c
rest_log: fix test isolation
simahawk Jan 21, 2022
5cebb9c
rest_log: [MIG] Migration to 15.0
yankinmax Dec 29, 2021
76b0e74
rest_log: fix env isolation in tests
simahawk May 13, 2022
cf849e9
rest_log: inject log entry URL only for dict response
simahawk Feb 4, 2021
8facd00
rest_log: store collection ID when available
simahawk Feb 16, 2022
0a372a1
[UPD] Update rest_log.pot
May 16, 2022
8fbcfb0
[UPD] README.rst
OCA-git-bot May 16, 2022
a31b31d
Initialize 16.0 branch
lmignon Sep 27, 2022
129a183
rest_log: add sanitize params hook
simahawk Oct 6, 2022
de5899b
rest_log 15.0.1.0.1
OCA-git-bot Nov 22, 2022
269012a
[16.0][MIG] - rest_log
sbejaoui Jun 26, 2023
37640b2
[FIX] rest_log: result is not always a json
lmignon Mar 11, 2022
31d8d5a
[IMP] rest_log: never crash the call because of the log
Apr 7, 2022
880a578
[IMP] rest_log: do not fail logging because of non-serializable param…
Apr 7, 2022
aa6fbea
[FIX] rest_lot: set the right status on the log in case of error
lmignon Apr 12, 2022
a8505b9
[FIX] rest_log: fix params management
SilvioC2C Oct 11, 2022
bcccd8f
[IMP] rest_log: refactor headers management
SilvioC2C Oct 11, 2022
8fcda57
[IMP] rest_log: handle odoo.http.Response objects in logs
SilvioC2C Oct 11, 2022
6bb3a1f
rest_log: add hook to customize result
simahawk Oct 18, 2022
801cbe2
rest_log: add hook to customize error
simahawk Oct 18, 2022
1353551
rest_log: postpone serialization of values
simahawk Oct 18, 2022
b3b33c7
[FIX] - fix rest log form view
sbejaoui Sep 22, 2023
0b3d83b
[FIX] rest_log: log result in case success
sbejaoui Dec 12, 2023
3575cd8
[UPD] Update rest_log.pot
Dec 12, 2023
61b15de
[BOT] post-merge updates
OCA-git-bot Dec 12, 2023
1232335
Added translation using Weblate (Italian)
mymage Jan 10, 2024
25364f6
Translated using Weblate (Italian)
mymage Jan 15, 2024
fc16e01
Translated using Weblate (Italian)
mymage Jan 18, 2024
2162a96
[IMP] rest_log: add MissingError to dispatch method
renda-dev Feb 14, 2024
35bca34
[BOT] post-merge updates
OCA-git-bot Jan 30, 2025
39c2fb7
[IMP] rest_log: pre-commit auto fixes
glitchov Mar 14, 2025
5605139
[MIG] : Migration to 18.0
glitchov Mar 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions rest_log/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
========
REST Log
========

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:f93e58dbd77af1254adb0cd7ace54af0b0609569883ed4a22028f11f0b663139
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |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/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
:target: https://github.com/OCA/rest-framework/tree/18.0/rest_log
:alt: OCA/rest-framework
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/rest-framework-18-0/rest-framework-18-0-rest_log
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/rest-framework&target_branch=18.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

When exposing REST services is often useful to see what's happening
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.

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.

**Table of contents**

.. contents::
:local:

Configuration
=============

Logs retention
--------------

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.

The Logs menu is shown only with Developer tools (``?debug=1``)
activated.

By default, REST logs are kept 30 days. You can change the duration of
the retention by changing the System Parameter
``rest.log.retention.days``.

If the value is set to 0, the logs are not stored at all.

Logged data is: request URL and method, parameters, headers, result or
error.

Logs activation
---------------

You have 2 ways to activate logging:

- on the service component set \_log_calls_in_db = True
- via configuration

In the 1st case, calls will be always be logged.

In the 2nd case you can set ``rest.log.active`` param as:

::

`collection_name` # enable for all endpoints of the collection
`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)

Changelog
=========

13.0.1.0.0
----------

First official version.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/rest-framework/issues>`_.
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
`feedback <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**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
-------

* Camptocamp
* ACSONE

Contributors
------------

- Guewen Baconnier <[email protected]>
- Simone Orsi <[email protected]>

Other credits
-------------

**Financial support**

- Cosanum
- Camptocamp R&D
- ACSONE R&D

Maintainers
-----------

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

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.

.. |maintainer-simahawk| image:: https://github.com/simahawk.png?size=40px
:target: https://github.com/simahawk
:alt: simahawk

Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:

|maintainer-simahawk|

This module is part of the `OCA/rest-framework <https://github.com/OCA/rest-framework/tree/18.0/rest_log>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
2 changes: 2 additions & 0 deletions rest_log/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import models
from . import components
23 changes: 23 additions & 0 deletions rest_log/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
# Copyright 2021 ACSONE SA/NV (http://www.acsone.eu)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).

{
"name": "REST Log",
"summary": "Track REST API calls into DB",
"version": "18.0.1.0.0",
"development_status": "Beta",
"website": "https://github.com/OCA/rest-framework",
"author": "Camptocamp, ACSONE, Odoo Community Association (OCA)",
"maintainers": ["simahawk"],
"license": "LGPL-3",
"depends": ["base_rest"],
"data": [
"data/ir_config_parameter_data.xml",
"data/ir_cron_data.xml",
"security/groups.xml",
"security/ir.model.access.csv",
"views/rest_log_views.xml",
"views/menu.xml",
],
}
1 change: 1 addition & 0 deletions rest_log/components/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import service
215 changes: 215 additions & 0 deletions rest_log/components/service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
# Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
# @author Guewen Baconnier <[email protected]>
# @author Simone Orsi <[email protected]>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).

import json
import logging
import traceback

from werkzeug.urls import url_encode, url_join

from odoo import exceptions
from odoo.http import Response, request
from odoo.modules.registry import Registry

from odoo.addons.base_rest.http import JSONEncoder
from odoo.addons.component.core import AbstractComponent

from ..exceptions import (
RESTServiceDispatchException,
RESTServiceMissingErrorException,
RESTServiceUserErrorException,
RESTServiceValidationErrorException,
)

_logger = logging.getLogger(__name__)


def json_dump(data):
"""Encode data to JSON as we like."""
return json.dumps(data, cls=JSONEncoder, indent=4, sort_keys=True, default=str)


class BaseRESTService(AbstractComponent):
_inherit = "base.rest.service"
# can be overridden to enable logging of requests to DB
_log_calls_in_db = False

def dispatch(self, method_name, *args, params=None):
if not self._db_logging_active(method_name):
return super().dispatch(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):
try:
with self.env.cr.savepoint():
result = super().dispatch(method_name, *args, params=params)
except exceptions.MissingError as orig_exception:
self._dispatch_exception(

Check warning on line 49 in rest_log/components/service.py

View check run for this annotation

Codecov / codecov/patch

rest_log/components/service.py#L49

Added line #L49 was not covered by tests
method_name,
RESTServiceMissingErrorException,
orig_exception,
*args,
params=params,
)
except exceptions.ValidationError as orig_exception:
self._dispatch_exception(
method_name,
RESTServiceValidationErrorException,
orig_exception,
*args,
params=params,
)
except exceptions.UserError as orig_exception:
self._dispatch_exception(
method_name,
RESTServiceUserErrorException,
orig_exception,
*args,
params=params,
)
except Exception as orig_exception:
self._dispatch_exception(
method_name,
RESTServiceDispatchException,
orig_exception,
*args,
params=params,
)
self._log_dispatch_success(method_name, result, *args, params)
return result

def _log_dispatch_success(self, method_name, result, *args, params=None):
try:
with self.env.cr.savepoint():
log_entry = self._log_call_in_db(
self.env, request, method_name, *args, params, result=result
)
if log_entry and not isinstance(result, Response):
log_entry_url = self._get_log_entry_url(log_entry)
result["log_entry_url"] = log_entry_url
except Exception as e:
_logger.exception("Rest Log Error Creation: %s", e)

Check warning on line 93 in rest_log/components/service.py

View check run for this annotation

Codecov / codecov/patch

rest_log/components/service.py#L92-L93

Added lines #L92 - L93 were not covered by tests

def _dispatch_exception(
self, method_name, exception_klass, orig_exception, *args, params=None
):
exc_msg, log_entry_url = None, None # in case it fails below
try:
exc_msg = self._get_exception_message(orig_exception)
tb = traceback.format_exc()
with Registry(self.env.cr.dbname).cursor() as cr:
log_entry = self._log_call_in_db(
self.env(cr=cr),
request,
method_name,
*args,
params=params,
traceback=tb,
orig_exception=orig_exception,
)
log_entry_url = self._get_log_entry_url(log_entry)
except Exception as e:
_logger.exception("Rest Log Error Creation: %s", e)

Check warning on line 114 in rest_log/components/service.py

View check run for this annotation

Codecov / codecov/patch

rest_log/components/service.py#L113-L114

Added lines #L113 - L114 were not covered by tests
raise exception_klass(exc_msg, log_entry_url) from orig_exception

def _get_exception_message(self, exception):
return getattr(exception, "name", str(exception))

def _get_log_entry_url(self, entry):
base_url = self.env["ir.config_parameter"].sudo().get_param("web.base.url")
url_params = {
"action": self.env.ref("rest_log.action_rest_log").id,
"view_type": "form",
"model": entry._name,
"id": entry.id,
}
url = f"/web?#{url_encode(url_params)}"
return url_join(base_url, url)

@property
def _log_call_header_strip(self):
return ("Cookie", "Api-Key")

def _log_call_in_db_values(self, _request, *args, params=None, **kw):
httprequest = _request.httprequest
headers = self._log_call_sanitize_headers(dict(httprequest.headers or []))
params = dict(params or {})
if args:
params.update(args=args)
params = self._log_call_sanitize_params(params)
error, exception_name, exception_message = self._log_call_prepare_error(**kw)
result, state = self._log_call_prepare_result(kw.get("result"))
collection = self.work.collection
return {
"collection": collection._name,
"collection_id": collection.id,
"request_url": httprequest.url,
"request_method": httprequest.method,
"params": params,
"headers": headers,
"result": result,
"error": error,
"exception_name": exception_name,
"exception_message": exception_message,
"state": state,
}

def _log_call_prepare_result(self, result):
# NB: ``result`` might be an object of class ``odoo.http.Response``,
# for example when you try to download a file. In this case, we need to
# handle it properly, without the assumption that ``result`` is a dict.
if isinstance(result, Response):
status_code = result.status_code
result = {
"status": status_code,
"headers": self._log_call_sanitize_headers(dict(result.headers or [])),
}
state = "success" if status_code in range(200, 300) else "failed"
else:
state = "success" if result else "failed"
return result, state

def _log_call_prepare_error(self, traceback=None, orig_exception=None, **kw):
exception_name = None
exception_message = None
if orig_exception:
exception_name = orig_exception.__class__.__name__
if hasattr(orig_exception, "__module__"):
exception_name = orig_exception.__module__ + "." + exception_name
exception_message = self._get_exception_message(orig_exception)
return traceback, exception_name, exception_message

_log_call_in_db_keys_to_serialize = ("params", "headers", "result")

def _log_call_in_db(self, env, _request, method_name, *args, params=None, **kw):
values = self._log_call_in_db_values(_request, *args, params=params, **kw)
for k in self._log_call_in_db_keys_to_serialize:
values[k] = json_dump(values[k])
enabled_states = self._get_matching_active_conf(method_name)
if not values or enabled_states and values["state"] not in enabled_states:
return

Check warning on line 192 in rest_log/components/service.py

View check run for this annotation

Codecov / codecov/patch

rest_log/components/service.py#L192

Added line #L192 was not covered by tests
return env["rest.log"].sudo().create(values)

def _log_call_sanitize_params(self, params: dict) -> dict:
if "password" in params:
params["password"] = "<redacted>"

Check warning on line 197 in rest_log/components/service.py

View check run for this annotation

Codecov / codecov/patch

rest_log/components/service.py#L197

Added line #L197 was not covered by tests
return params

def _log_call_sanitize_headers(self, headers: dict) -> dict:
for header_key in self._log_call_header_strip:
if header_key in headers:
headers[header_key] = "<redacted>"
return headers

def _db_logging_active(self, method_name):
enabled = self._log_calls_in_db
if not enabled:
enabled = bool(self._get_matching_active_conf(method_name))
return request and enabled and self.env["rest.log"].logging_active()

def _get_matching_active_conf(self, method_name):
return self.env["rest.log"]._get_matching_active_conf(
self._collection, self._usage, method_name
)
Loading