Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 22 additions & 4 deletions sentry/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
73 changes: 72 additions & 1 deletion sentry/tests/test_client.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# Copyright 2016-2017 Versada <https://versada.eu/>
# Copyright 2026 Therp BV <https://therp.nl/>
# 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
Expand Down Expand Up @@ -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
Loading