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
31 changes: 25 additions & 6 deletions src/blueapi/service/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
Response,
status,
)
from fastapi.concurrency import iterate_in_threadpool
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import RedirectResponse
from fastapi.responses import RedirectResponse, StreamingResponse
from fastapi.security import OAuth2AuthorizationCodeBearer
from observability_utils.tracing import (
add_span_attributes,
Expand Down Expand Up @@ -596,14 +597,32 @@ async def add_api_version_header(


async def log_request_details(
request: Request, call_next: Callable[[Request], Awaitable[Response]]
request: Request, call_next: Callable[[Request], Awaitable[StreamingResponse]]
) -> Response:
msg = f"method: {request.method} url: {request.url} body: {await request.body()}"
"""Middleware to log all request's host, method, path, status and request and
response bodies"""
request_body = await request.body()

response = await call_next(request)

# https://github.com/Kludex/starlette/issues/874#issuecomment-1027743996
response_body = [section async for section in response.body_iterator]
response.body_iterator = iterate_in_threadpool(iter(response_body))

log_message = (
f"{getattr(request.client, 'host', 'NO_ADDRESS')} {request.method}"
f" {request.url.path} {response.status_code}"
)

extra = {
"request_body": request_body,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The response body could be massive (in theory), this is why the FastAPI tracing does not set it as an attribute by default, rather they recommend truncating it when manually adding it to the trace, if it is over a certain length. So it could be useful to add this modality, possibly as a selectable option that defaults to truncate=true as there may legitimately be times when you want to see the whole response to a particular request in the log.

"response_body": response_body,
}
if request.url.path == "/healthz":
LOGGER.debug(msg)
LOGGER.debug(log_message, extra=extra)
else:
LOGGER.info(msg)
response = await call_next(request)
LOGGER.info(log_message, extra=extra)

return response


Expand Down
10 changes: 7 additions & 3 deletions tests/unit_tests/service/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,20 @@ async def test_log_request_details():
app = FastAPI()
app.middleware("http")(log_request_details)

@app.get("/")
@app.post("/")
async def root():
return {"message": "Hello World"}

client = TestClient(app)
response = client.get("/")
response = client.post("/", content="foo")

assert response.status_code == 200
logger.info.assert_called_once_with(
"method: GET url: http://testserver/ body: b''"
"testclient POST / 200",
extra={
"request_body": b"foo",
"response_body": [b'{"message":"Hello World"}'],
},
)


Expand Down