Skip to content

Commit e7f1929

Browse files
CagriYoncapvital
authored andcommitted
fix: Werkzeuf instrumentation for Odoo
Signed-off-by: Cagri Yonca <cagri@ibm.com>
1 parent ecec084 commit e7f1929

2 files changed

Lines changed: 90 additions & 0 deletions

File tree

src/instana/instrumentation/werkzeug.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,34 @@ def run_simple_with_instana(
100100

101101
return wrapped(*args, **kwargs)
102102

103+
@wrapt.patch_function_wrapper("werkzeug.serving", "BaseWSGIServer.__init__")
104+
def base_wsgi_server_init_with_instana(
105+
wrapped: Callable,
106+
instance: Any,
107+
args: tuple,
108+
kwargs: dict[str, Any],
109+
) -> Any:
110+
"""
111+
Patch werkzeug.serving.BaseWSGIServer.__init__ to wrap WSGI applications.
112+
113+
Covers frameworks like Odoo that instantiate BaseWSGIServer (or its
114+
subclasses such as ThreadedWSGIServer) directly without going through
115+
run_simple. The app is wrapped after super().__init__ so that any
116+
subclass setup that reads self.app also sees the instrumented version.
117+
"""
118+
wrapped(*args, **kwargs)
119+
try:
120+
if _is_flask_app(instance.app):
121+
logger.debug("Skipping BaseWSGIServer instrumentation for Flask app")
122+
return
123+
if not isinstance(instance.app, InstanaWSGIMiddleware):
124+
instance.app = InstanaWSGIMiddleware(
125+
instance.app, status_as_string=False
126+
)
127+
logger.debug("BaseWSGIServer app wrapped")
128+
except Exception:
129+
logger.debug("Failed to wrap BaseWSGIServer app", exc_info=True)
130+
103131
logger.debug("Instrumenting werkzeug")
104132

105133
except ImportError:

tests/frameworks/test_werkzeug.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,68 @@ def __call__(self, environ, start_response):
601601
assert call_args[2] is flask_app
602602
assert not isinstance(call_args[2], InstanaWSGIMiddleware)
603603

604+
def test_base_wsgi_server_direct_instantiation(self) -> None:
605+
"""Test instrumentation when BaseWSGIServer is instantiated directly (e.g. Odoo).
606+
607+
Odoo's ThreadedWSGIServerReloadable extends werkzeug.serving.ThreadedWSGIServer
608+
which extends BaseWSGIServer, bypassing run_simple entirely. This test verifies
609+
that the BaseWSGIServer.__init__ patch wraps the app in that case.
610+
"""
611+
import socket
612+
from werkzeug.serving import BaseWSGIServer
613+
614+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
615+
s.bind(("127.0.0.1", 0))
616+
port = s.getsockname()[1]
617+
618+
server = BaseWSGIServer("127.0.0.1", port, simple_wsgi_app)
619+
try:
620+
assert isinstance(server.app, InstanaWSGIMiddleware)
621+
assert server.app.app is simple_wsgi_app
622+
finally:
623+
server.server_close()
624+
625+
def test_base_wsgi_server_skips_flask_app(self) -> None:
626+
"""Test that BaseWSGIServer patch skips Flask apps."""
627+
import socket
628+
from werkzeug.serving import BaseWSGIServer
629+
630+
class Flask:
631+
def __call__(self, environ, start_response):
632+
return simple_wsgi_app(environ, start_response)
633+
634+
Flask.__module__ = "flask.app"
635+
flask_app = Flask()
636+
637+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
638+
s.bind(("127.0.0.1", 0))
639+
port = s.getsockname()[1]
640+
641+
server = BaseWSGIServer("127.0.0.1", port, flask_app)
642+
try:
643+
assert server.app is flask_app
644+
assert not isinstance(server.app, InstanaWSGIMiddleware)
645+
finally:
646+
server.server_close()
647+
648+
def test_base_wsgi_server_not_double_wrapped(self) -> None:
649+
"""Test that an already-wrapped app is not wrapped again."""
650+
import socket
651+
from werkzeug.serving import BaseWSGIServer
652+
653+
pre_wrapped = InstanaWSGIMiddleware(simple_wsgi_app, status_as_string=False)
654+
655+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
656+
s.bind(("127.0.0.1", 0))
657+
port = s.getsockname()[1]
658+
659+
server = BaseWSGIServer("127.0.0.1", port, pre_wrapped)
660+
try:
661+
assert server.app is pre_wrapped
662+
assert not isinstance(server.app.app, InstanaWSGIMiddleware)
663+
finally:
664+
server.server_close()
665+
604666

605667
def test_parse_status_code_handles_valid_and_invalid_values() -> None:
606668
"""Test safe parsing of WSGI status strings."""

0 commit comments

Comments
 (0)