fastapi_access_logger.py 2.6 KB

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