Skip to content

Commit 19e4ef9

Browse files
Fix flask and fastapi get logs (#42)
Co-authored-by: José Valim <[email protected]>
1 parent 9ad3273 commit 19e4ef9

File tree

4 files changed

+72
-9
lines changed

4 files changed

+72
-9
lines changed

src/tidewave/django/__init__.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,11 @@ def __init__(self, get_response: Callable):
6969
self.base_middleware = BaseMiddleware(self._dummy_wsgi_app, self.mcp_handler, self.config)
7070

7171
def _setup_logging(self):
72+
file_handler.addFilter(lambda record: record.name != "django.utils.autoreload")
7273
file_handler.addFilter(
73-
CallbackFilter(lambda record: record.name != "django.utils.autoreload")
74-
)
75-
file_handler.addFilter(
76-
CallbackFilter(
77-
lambda record: not (
78-
(record.name == "django.server" or record.name == "django.request")
79-
and "/tidewave" in record.getMessage()
80-
)
74+
lambda record: not (
75+
(record.name == "django.server" or record.name == "django.request")
76+
and " /tidewave" in record.getMessage()
8177
)
8278
)
8379

src/tidewave/fastapi/__init__.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
FastAPI-specific integration for Tidewave
33
"""
44

5+
import logging
56
import os
67
from typing import Any, Optional
78

@@ -14,6 +15,7 @@
1415
from tidewave.fastapi.middleware import Middleware
1516
from tidewave.mcp_handler import MCPHandler
1617
from tidewave.middleware import Middleware as MCPMiddleware
18+
from tidewave.tools.get_logs import file_handler
1719

1820

1921
class Tidewave:
@@ -74,3 +76,21 @@ def wsgi_app(environ, start_response):
7476
mcp_middleware = MCPMiddleware(wsgi_app, mcp_handler, config)
7577
app.mount("/tidewave", WSGIMiddleware(mcp_middleware))
7678
app.add_middleware(Middleware)
79+
80+
self._setup_logging()
81+
82+
def _setup_logging(self):
83+
file_handler.addFilter(
84+
lambda record: not (
85+
(record.name == "uvicorn.access") and " /tidewave" in record.getMessage()
86+
)
87+
)
88+
89+
# We set a global logger handler. The "uvicorn.error" logger
90+
# propagates [1], so it will also invoke that handler, on the
91+
# other hand, "uvicorn.access" logger does not propagate, so
92+
# we need to add the handler separately.
93+
#
94+
# [1]: https://github.com/Kludex/uvicorn/blob/0.37.0/uvicorn/config.py#L94-L98
95+
logging.getLogger().addHandler(file_handler)
96+
logging.getLogger("uvicorn.access").addHandler(file_handler)

src/tidewave/flask/__init__.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
Flask-specific integration for Tidewave
33
"""
44

5+
import logging
56
from typing import Any, Optional
67

7-
from flask import current_app
8+
from flask import current_app, got_request_exception, request
89

910
from tidewave import tools
1011
from tidewave.flask.middleware import Middleware
@@ -69,3 +70,22 @@ def init_app(self, app):
6970

7071
app.wsgi_app = MCPMiddleware(Middleware(app.wsgi_app), mcp_handler, middleware_config)
7172
app.jinja_env.add_extension(Extension)
73+
74+
# In debug mode, exceptions are not logged [1], they propagate,
75+
# the debugger shows an error page and only writes to stderr [2].
76+
# We want the exceptions to appear in get_logs, so we log them
77+
# explicitly.
78+
#
79+
# [1]: https://flask.palletsprojects.com/en/stable/api/#flask.Flask.handle_exception
80+
# [2]: https://github.com/pallets/werkzeug/blob/3.1.3/src/werkzeug/debug/__init__.py#L378
81+
got_request_exception.connect(app_exception_handler, app)
82+
83+
84+
def app_exception_handler(sender, exception, **extra):
85+
# We log to the default logger. This does not log to the terminal,
86+
# because terminal output comes from terminal handlers in more
87+
# specific flask/werkzeug loggers.
88+
logging.getLogger().exception(
89+
f"Exception on {request.path} [{request.method}]",
90+
exc_info=exception,
91+
)

src/tidewave/flask/middleware.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,49 @@
22
Flask-specific middleware for dealing with CSP/frame headers.
33
"""
44

5+
import logging
56
from typing import Any, Callable
67

78
from tidewave.middleware import modify_csp
9+
from tidewave.tools.get_logs import file_handler
810

911

1012
class Middleware:
1113
def __init__(self, app: Callable):
1214
self.app = app
15+
self.logging_initialized = False
1316

1417
def __call__(self, environ: dict[str, Any], start_response: Callable):
18+
self._maybe_initialize_logging()
19+
1520
def handle_response(status, headers):
1621
modified_headers = self._process_response(headers)
1722
return start_response(status, modified_headers)
1823

1924
return self.app(environ, handle_response)
2025

26+
def _maybe_initialize_logging(self):
27+
# Ideally we would add the logger handler as part of
28+
# tidewave.flask.Tidewave, however that is too soon. Both
29+
# werkzeug [1] and flask [2] have conditional logic that adds
30+
# a default terminal handler, only if no other handler is
31+
# present, so if we add our handler too soon, no logs would
32+
# show up in the terminal. Instead, we add our handler on the
33+
# first request, by which point the default handler is already
34+
# added.
35+
#
36+
# [1]: https://github.com/pallets/werkzeug/blob/3.1.3/src/werkzeug/_internal.py#L94-L95
37+
# [2]: https://github.com/pallets/flask/blob/3.1.2/src/flask/logging.py#L76-L77
38+
39+
if not self.logging_initialized:
40+
file_handler.addFilter(
41+
lambda record: not (
42+
record.name == "werkzeug" and " /tidewave" in record.getMessage()
43+
)
44+
)
45+
logging.getLogger().addHandler(file_handler)
46+
self.logging_initialized = True
47+
2148
def _process_response(self, headers):
2249
"""
2350
Modify headers to allow embedding in Tidewave:

0 commit comments

Comments
 (0)