This is an automated email from the ASF dual-hosted git repository.
jason810496 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new b66f4433e00 Guard finally-block logger.info in HTTP access log
middleware (#67501)
b66f4433e00 is described below
commit b66f4433e004fad81b66023bd8caccee55e6e4f7
Author: Jarek Potiuk <[email protected]>
AuthorDate: Thu May 28 08:55:01 2026 +0200
Guard finally-block logger.info in HTTP access log middleware (#67501)
The ``finally`` block in ``HttpAccessLogMiddleware`` called
``logger.info()`` without exception protection. If ``logger.info()``
raised — broken handler, OOM in the formatter, downstream forwarder
unavailable — and the original ``try`` block was already propagating
an application exception, Python's ``finally``-replacement semantics
would discard the original exception in favour of the logger's, so
uvicorn never saw the real failure.
Wrap the emit in ``contextlib.suppress(Exception)`` so logging failures
never disrupt the application or mask the original exception. The
HTTP response has already been sent to the client by the time we
reach the log emit, so swallowing the logger failure costs nothing
beyond a missing log line for that one request.
---
.../airflow/api_fastapi/common/http_access_log.py | 24 ++++++----
.../api_fastapi/common/test_http_access_log.py | 52 ++++++++++++++++++++++
2 files changed, 67 insertions(+), 9 deletions(-)
diff --git a/airflow-core/src/airflow/api_fastapi/common/http_access_log.py
b/airflow-core/src/airflow/api_fastapi/common/http_access_log.py
index efaf570c580..ad37b094a9f 100644
--- a/airflow-core/src/airflow/api_fastapi/common/http_access_log.py
+++ b/airflow-core/src/airflow/api_fastapi/common/http_access_log.py
@@ -121,12 +121,18 @@ class HttpAccessLogMiddleware:
client = scope.get("client")
client_addr = f"{client[0]}:{client[1]}" if client else
None
- logger.info(
- "request finished",
- method=method,
- path=path,
- query=query,
- status_code=status,
- duration_us=duration_us,
- client_addr=client_addr,
- )
+ # Guard the log emit: if it raised inside a ``finally``
while the
+ # original ``try`` block was already propagating an app
exception,
+ # Python's exception-replacement semantics would discard
the
+ # original. Swallow logging failures so the application
exception
+ # always reaches uvicorn intact.
+ with contextlib.suppress(Exception):
+ logger.info(
+ "request finished",
+ method=method,
+ path=path,
+ query=query,
+ status_code=status,
+ duration_us=duration_us,
+ client_addr=client_addr,
+ )
diff --git a/airflow-core/tests/unit/api_fastapi/common/test_http_access_log.py
b/airflow-core/tests/unit/api_fastapi/common/test_http_access_log.py
index 243049aedbe..882f93a3151 100644
--- a/airflow-core/tests/unit/api_fastapi/common/test_http_access_log.py
+++ b/airflow-core/tests/unit/api_fastapi/common/test_http_access_log.py
@@ -184,3 +184,55 @@ def
test_logs_redact_sensitive_query_param(_password_sensitive_field):
query = logs[0]["query"]
assert "topsecret" not in query
assert "keep=ok" in query
+
+
+def test_logger_failure_does_not_mask_app_exception(monkeypatch):
+ """
+ If ``logger.info`` raises while the app already raised, the original app
exception must
+ still propagate (rather than being replaced by the logger's exception).
+ """
+ import airflow.api_fastapi.common.http_access_log as mod
+
+ def broken_info(*_args, **_kwargs):
+ raise RuntimeError("logger broken")
+
+ monkeypatch.setattr(mod.logger, "info", broken_info)
+
+ import asyncio
+
+ async def raising_app(scope, receive, send):
+ # Send response.start so the middleware's response variable is
populated, then raise.
+ await send({"type": "http.response.start", "status": 503, "headers":
[]})
+ raise RuntimeError("app exception")
+
+ middleware = HttpAccessLogMiddleware(raising_app)
+ scope = {
+ "type": "http",
+ "method": "GET",
+ "path": "/boom",
+ "query_string": b"",
+ "headers": [],
+ "client": ("test", 1),
+ }
+
+ async def receive():
+ return {"type": "http.request", "body": b""}
+
+ async def send(_message):
+ return None
+
+ with pytest.raises(RuntimeError, match="app exception"):
+ asyncio.run(middleware(scope, receive, send))
+
+
+def test_logger_failure_swallowed_on_clean_request(monkeypatch):
+ """No app exception + a broken logger must not break the request."""
+ import airflow.api_fastapi.common.http_access_log as mod
+
+ monkeypatch.setattr(
+ mod.logger, "info", lambda *_a, **_kw: (_ for _ in
()).throw(RuntimeError("logger broken"))
+ )
+
+ client = TestClient(_make_app(), raise_server_exceptions=False)
+ response = client.get("/")
+ assert response.status_code == 200