fastapi_access_logger.py 2.6 KB

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