fastapi_access_logger.py 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. # (c) Nelen & Schuurmans
  2. import os
  3. import time
  4. from typing import Awaitable
  5. from typing import Callable
  6. from typing import Optional
  7. import inject
  8. from starlette.background import BackgroundTasks
  9. from starlette.requests import Request
  10. from starlette.responses import Response
  11. from clean_python import ctx
  12. from clean_python import Gateway
  13. from clean_python.fluentbit import FluentbitGateway
  14. __all__ = ["FastAPIAccessLogger"]
  15. class FastAPIAccessLogger:
  16. def __init__(self, hostname: str, gateway_override: Optional[Gateway] = None):
  17. self.origin = f"{hostname}-{os.getpid()}"
  18. self.gateway_override = gateway_override
  19. @property
  20. def gateway(self) -> Gateway:
  21. return self.gateway_override or inject.instance(FluentbitGateway)
  22. async def __call__(
  23. self, request: Request, call_next: Callable[[Request], Awaitable[Response]]
  24. ) -> Response:
  25. time_received = time.time()
  26. response = await call_next(request)
  27. request_time = time.time() - time_received
  28. # Instead of logging directly, set it as background task so that it is
  29. # executed after the response. See https://www.starlette.io/background/.
  30. if response.background is None:
  31. response.background = BackgroundTasks()
  32. response.background.add_task(
  33. log_access, self.gateway, request, response, time_received, request_time
  34. )
  35. return response
  36. async def log_access(
  37. gateway: Gateway,
  38. request: Request,
  39. response: Response,
  40. time_received: float,
  41. request_time: float,
  42. ) -> None:
  43. """
  44. Create a dictionary with logging data.
  45. """
  46. try:
  47. content_length = int(response.headers.get("content-length"))
  48. except (TypeError, ValueError):
  49. content_length = None
  50. try:
  51. view_name = request.scope["route"].name
  52. except KeyError:
  53. view_name = None
  54. item = {
  55. "tag_suffix": "access_log",
  56. "remote_address": getattr(request.client, "host", None),
  57. "method": request.method,
  58. "path": request.url.path,
  59. "portal": request.url.netloc,
  60. "referer": request.headers.get("referer"),
  61. "user_agent": request.headers.get("user-agent"),
  62. "query_params": request.url.query,
  63. "view_name": view_name,
  64. "status": response.status_code,
  65. "content_type": response.headers.get("content-type"),
  66. "content_length": content_length,
  67. "time": time_received,
  68. "request_time": request_time,
  69. "correlation_id": str(ctx.correlation_id) if ctx.correlation_id else None,
  70. }
  71. await gateway.add(item)