mirror of
https://github.com/Xevion/linkpulse.git
synced 2025-12-16 22:12:18 -06:00
Add ASGI Request-Id correlation, add structlog LoggingMiddleware, overhaul all logging
- minor formatting details, type fixes.
This commit is contained in:
51
backend/linkpulse/middleware.py
Normal file
51
backend/linkpulse/middleware.py
Normal file
@@ -0,0 +1,51 @@
|
||||
import time
|
||||
from asgi_correlation_id import correlation_id
|
||||
import structlog
|
||||
|
||||
from fastapi import FastAPI, Request, Response
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
|
||||
|
||||
|
||||
class LoggingMiddleware(BaseHTTPMiddleware):
|
||||
def __init__(self, app: FastAPI):
|
||||
super().__init__(app)
|
||||
self.access_logger = structlog.get_logger("api.access")
|
||||
|
||||
async def dispatch(self, request: Request, call_next) -> Response:
|
||||
structlog.contextvars.clear_contextvars()
|
||||
|
||||
# These context vars will be added to all log entries emitted during the request
|
||||
request_id = correlation_id.get()
|
||||
structlog.contextvars.bind_contextvars(request_id=request_id)
|
||||
|
||||
start_time = time.perf_counter_ns()
|
||||
# If the call_next raises an error, we still want to return our own 500 response,
|
||||
# so we can add headers to it (process time, request ID...)
|
||||
response = Response(status_code=500)
|
||||
try:
|
||||
response = await call_next(request)
|
||||
except Exception:
|
||||
# TODO: Validate that we don't swallow exceptions (unit test?)
|
||||
structlog.stdlib.get_logger("api.error").exception("Uncaught exception")
|
||||
raise
|
||||
finally:
|
||||
process_time = time.perf_counter_ns() - start_time
|
||||
self.access_logger.info(
|
||||
"Request",
|
||||
http={
|
||||
"url": str(request.url),
|
||||
"query": dict(request.query_params),
|
||||
"status_code": response.status_code,
|
||||
"method": request.method,
|
||||
"request_id": request_id,
|
||||
"version": request.scope["http_version"],
|
||||
},
|
||||
client={"ip": request.client.host, "port": request.client.port} if request.client else None,
|
||||
duration=process_time,
|
||||
)
|
||||
|
||||
# TODO: Is this being returned in production? We shouldn't be leaking this.
|
||||
response.headers["X-Process-Time"] = str(process_time / 10 ** 9)
|
||||
|
||||
return response
|
||||
Reference in New Issue
Block a user