Skip to content

Commit e06b1fe

Browse files
committed
Merge PR #547 into 16.0
Signed-off-by lmignon
2 parents 1390f0d + 8e16b49 commit e06b1fe

2 files changed

Lines changed: 58 additions & 19 deletions

File tree

rest_log/components/service.py

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# @author Simone Orsi <simahawk@gmail.com>
44
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
55

6+
import inspect
67
import json
78
import logging
89
import traceback
@@ -93,9 +94,37 @@ def _log_dispatch_success(self, method_name, result, *args, params=None):
9394
except Exception as e:
9495
_logger.exception("Rest Log Error Creation: %s", e)
9596

97+
def _is_request_will_be_retried(self):
98+
# we need to analyze the current call stack to determine if
99+
# a 'tryleft' local variable is set, which indicates that
100+
# the current request is being retried.
101+
# This is a workaround for the fact that we cannot access
102+
# the 'tryleft' variable from the request context, as it is
103+
# not part of the request object.
104+
# This is a hack, but it is the only way to determine if
105+
# the current request is being retried.
106+
stack = inspect.stack()
107+
for frame in stack:
108+
if "tryleft" in frame.frame.f_locals:
109+
return frame.frame.f_locals["tryleft"]
110+
return False
111+
96112
def _dispatch_exception(
97113
self, method_name, exception_klass, orig_exception, *args, params=None
98114
):
115+
must_return_original_exception = False
116+
if (
117+
isinstance(orig_exception, OperationalError)
118+
and orig_exception.pgcode in PG_CONCURRENCY_ERRORS_TO_RETRY
119+
):
120+
must_return_original_exception = True
121+
if self._is_request_will_be_retried():
122+
# If we are in a retry, we don't log the error in the database,
123+
# as it will be retried and logged again. And we let the
124+
# OperationalError bubble up to the retrying mechanism where
125+
# it will be handled by the default handler at the end of the chain.
126+
raise orig_exception
127+
99128
exc_msg, log_entry_url = None, None # in case it fails below
100129
try:
101130
exc_msg = self._get_exception_message(orig_exception)
@@ -113,14 +142,7 @@ def _dispatch_exception(
113142
log_entry_url = self._get_log_entry_url(log_entry)
114143
except Exception as e:
115144
_logger.exception("Rest Log Error Creation: %s", e)
116-
# let the OperationalError bubble up to the retrying mechanism
117-
# We can't wrap the OperationalError because we want to let it
118-
# bubble up to the retrying mechanism, it will be handled by
119-
# the default handler at the end of the chain.
120-
if (
121-
isinstance(orig_exception, OperationalError)
122-
and orig_exception.pgcode in PG_CONCURRENCY_ERRORS_TO_RETRY
123-
):
145+
if must_return_original_exception:
124146
raise orig_exception
125147
raise exception_klass(exc_msg, log_entry_url) from orig_exception
126148

rest_log/tests/test_db_logging.py

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,9 @@ def tearDownClass(cls):
390390
cls._teardown_registry(cls)
391391
super().tearDownClass()
392392

393-
def _test_exception(self, test_type, wrapping_exc, exc_name, severity):
393+
def _test_exception(
394+
self, test_type, wrapping_exc, exc_name, severity, will_be_retried=True
395+
):
394396
log_model = self.env["rest.log"].sudo()
395397
initial_entries = log_model.search([])
396398
# Context: we are running in a transaction case which uses savepoints.
@@ -400,7 +402,9 @@ def _test_exception(self, test_type, wrapping_exc, exc_name, severity):
400402
# Init fake collection w/ new env
401403
collection = _PseudoCollection(self._collection_name, new_env)
402404
service = self._get_service(self, collection=collection)
403-
with self._get_mocked_request(env=new_env):
405+
with self._get_mocked_request(env=new_env), mock.patch.object(
406+
service, "_is_request_will_be_retried", return_value=will_be_retried
407+
):
404408
try:
405409
service.dispatch("fail", test_type)
406410
except Exception as err:
@@ -413,15 +417,20 @@ def _test_exception(self, test_type, wrapping_exc, exc_name, severity):
413417
with new_rollbacked_env() as new_env:
414418
log_model = new_env["rest.log"].sudo()
415419
entry = log_model.search([]) - initial_entries
416-
expected = {
417-
"collection": service._collection,
418-
"state": "failed",
419-
"result": "null",
420-
"exception_name": exc_name,
421-
"exception_message": "Failed as you wanted!",
422-
"severity": severity,
423-
}
424-
self.assertRecordValues(entry, [expected])
420+
if will_be_retried:
421+
# retryable error must bubble up to the retrying mechanism
422+
self.assertEqual(len(entry), 0)
423+
else:
424+
# not retryable, so we log it
425+
expected = {
426+
"collection": service._collection,
427+
"state": "failed",
428+
"result": "null",
429+
"exception_name": exc_name,
430+
"exception_message": "Failed as you wanted!",
431+
"severity": severity,
432+
}
433+
self.assertRecordValues(entry, [expected])
425434

426435
@staticmethod
427436
def _get_test_controller(class_or_instance, root_path=None):
@@ -436,4 +445,12 @@ def test_log_exception_retryable(self):
436445
FakeConcurrentUpdateError,
437446
"odoo.addons.rest_log.tests.common.FakeConcurrentUpdateError",
438447
"warning",
448+
will_be_retried=True,
449+
)
450+
self._test_exception(
451+
"retryable",
452+
FakeConcurrentUpdateError,
453+
"odoo.addons.rest_log.tests.common.FakeConcurrentUpdateError",
454+
"warning",
455+
will_be_retried=False,
439456
)

0 commit comments

Comments
 (0)