diff --git a/sentry/hooks.py b/sentry/hooks.py
index 6a6af920967..eb1ee9bc6c8 100644
--- a/sentry/hooks.py
+++ b/sentry/hooks.py
@@ -19,6 +19,7 @@
_logger = logging.getLogger(__name__)
HAS_SENTRY_SDK = True
+_ORIGINAL_APPLICATION_CALL = None
try:
import sentry_sdk
from sentry_sdk.integrations.logging import ignore_logger
@@ -136,12 +137,29 @@ def initialize_sentry(config):
for item in exclude_loggers:
ignore_logger(item)
- # The server app is already registered so patch it here
- if server:
+ global _ORIGINAL_APPLICATION_CALL
+
+ # The server app is already registered so patch it here.
+ # this is mostly a defensive fallback
+ if server and server.app is not odoo.http.root:
server.app = SentryWsgiMiddleware(server.app)
- # Patch the wsgi server in case of further registration
- odoo.http.Application = SentryWsgiMiddleware(odoo.http.Application)
+ # Patch the actual WSGI entrypoint while keeping
+ # odoo.http.Application as a class
+ # odoo.http.root as the regular root/application object
+ if _ORIGINAL_APPLICATION_CALL is None:
+ _ORIGINAL_APPLICATION_CALL = odoo.http.Application.__call__
+
+ def sentry_application_call(self, environ, start_response):
+ middleware = getattr(self, "_sentry_wsgi_middleware", None)
+ if middleware is None:
+ middleware = SentryWsgiMiddleware(
+ _ORIGINAL_APPLICATION_CALL.__get__(self, type(self))
+ )
+ self._sentry_wsgi_middleware = middleware
+ return middleware(environ, start_response)
+
+ odoo.http.Application.__call__ = sentry_application_call
with sentry_sdk.new_scope() as scope:
scope.set_extra("debug", False)
diff --git a/sentry/tests/test_client.py b/sentry/tests/test_client.py
index e506582caad..e25d50cac7b 100644
--- a/sentry/tests/test_client.py
+++ b/sentry/tests/test_client.py
@@ -1,13 +1,16 @@
# Copyright 2016-2017 Versada
+# Copyright 2026 Therp BV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-
+import inspect
import logging
import sys
from unittest.mock import patch
from sentry_sdk.integrations.logging import _IGNORED_LOGGERS
+from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware
from sentry_sdk.transport import HttpTransport
+import odoo.http
from odoo import exceptions
from odoo.tests import TransactionCase
from odoo.tools import config
@@ -266,3 +269,71 @@ def test_config_release(self, get_odoo_commit):
RELEASE,
"Failed to use 'sentry_release' parameter appropriately",
)
+
+ def test_initialize_sentry_patches_application_call_when_server_missing(self):
+ original_root = odoo.http.root
+ original_application = odoo.http.Application
+
+ class DummyRoot:
+ def __init__(self):
+ self.session_store = object()
+
+ class DummyApplication:
+ def __call__(self, environ, start_response):
+ return "ok"
+
+ try:
+ dummy_root = DummyRoot()
+ original_call = DummyApplication.__call__
+ odoo.http.root = dummy_root
+ odoo.http.Application = DummyApplication
+ with (
+ patch("odoo.addons.sentry.hooks.server", new=None),
+ patch("odoo.addons.sentry.hooks._ORIGINAL_APPLICATION_CALL", new=None),
+ ):
+ initialize_sentry(config)
+ self.assertTrue(inspect.isclass(odoo.http.Application))
+ self.assertIs(odoo.http.root, dummy_root)
+ self.assertTrue(hasattr(odoo.http.root, "session_store"))
+ self.assertIs(odoo.http.root.session_store, dummy_root.session_store)
+ self.assertIsNot(odoo.http.Application.__call__, original_call)
+ finally:
+ odoo.http.root = original_root
+ odoo.http.Application = original_application
+
+ def test_initialize_sentry_wraps_server_app_and_patches_application_call(self):
+ original_root = odoo.http.root
+ original_application = odoo.http.Application
+
+ class DummyRoot:
+ def __init__(self):
+ self.session_store = object()
+
+ class DummyApplication:
+ def __call__(self, environ, start_response):
+ return "ok"
+
+ class DummyServer:
+ def __init__(self):
+ self.app = lambda environ, start_response: "server-ok"
+
+ dummy_server = DummyServer()
+ try:
+ dummy_root = DummyRoot()
+ original_call = DummyApplication.__call__
+ odoo.http.root = dummy_root
+ odoo.http.Application = DummyApplication
+ with (
+ patch("odoo.addons.sentry.hooks.server", new=dummy_server),
+ patch("odoo.addons.sentry.hooks._ORIGINAL_APPLICATION_CALL", new=None),
+ ):
+ initialize_sentry(config)
+ self.assertIsInstance(dummy_server.app, SentryWsgiMiddleware)
+ self.assertTrue(inspect.isclass(odoo.http.Application))
+ self.assertIs(odoo.http.root, dummy_root)
+ self.assertTrue(hasattr(odoo.http.root, "session_store"))
+ self.assertIs(odoo.http.root.session_store, dummy_root.session_store)
+ self.assertIsNot(odoo.http.Application.__call__, original_call)
+ finally:
+ odoo.http.root = original_root
+ odoo.http.Application = original_application