message_logger.py 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. import logging
  2. from typing import Any
  3. from uvicorn._types import (
  4. ASGI3Application,
  5. ASGIReceiveCallable,
  6. ASGIReceiveEvent,
  7. ASGISendCallable,
  8. ASGISendEvent,
  9. WWWScope,
  10. )
  11. from uvicorn.logging import TRACE_LOG_LEVEL
  12. PLACEHOLDER_FORMAT = {
  13. "body": "<{length} bytes>",
  14. "bytes": "<{length} bytes>",
  15. "text": "<{length} chars>",
  16. "headers": "<...>",
  17. }
  18. def message_with_placeholders(message: Any) -> Any:
  19. """
  20. Return an ASGI message, with any body-type content omitted and replaced
  21. with a placeholder.
  22. """
  23. new_message = message.copy()
  24. for attr in PLACEHOLDER_FORMAT.keys():
  25. if message.get(attr) is not None:
  26. content = message[attr]
  27. placeholder = PLACEHOLDER_FORMAT[attr].format(length=len(content))
  28. new_message[attr] = placeholder
  29. return new_message
  30. class MessageLoggerMiddleware:
  31. def __init__(self, app: "ASGI3Application"):
  32. self.task_counter = 0
  33. self.app = app
  34. self.logger = logging.getLogger("uvicorn.asgi")
  35. def trace(message: Any, *args: Any, **kwargs: Any) -> None:
  36. self.logger.log(TRACE_LOG_LEVEL, message, *args, **kwargs)
  37. self.logger.trace = trace # type: ignore
  38. async def __call__(
  39. self,
  40. scope: "WWWScope",
  41. receive: "ASGIReceiveCallable",
  42. send: "ASGISendCallable",
  43. ) -> None:
  44. self.task_counter += 1
  45. task_counter = self.task_counter
  46. client = scope.get("client")
  47. prefix = "%s:%d - ASGI" % (client[0], client[1]) if client else "ASGI"
  48. async def inner_receive() -> "ASGIReceiveEvent":
  49. message = await receive()
  50. logged_message = message_with_placeholders(message)
  51. log_text = "%s [%d] Receive %s"
  52. self.logger.trace( # type: ignore
  53. log_text, prefix, task_counter, logged_message
  54. )
  55. return message
  56. async def inner_send(message: "ASGISendEvent") -> None:
  57. logged_message = message_with_placeholders(message)
  58. log_text = "%s [%d] Send %s"
  59. self.logger.trace( # type: ignore
  60. log_text, prefix, task_counter, logged_message
  61. )
  62. await send(message)
  63. logged_scope = message_with_placeholders(scope)
  64. log_text = "%s [%d] Started scope=%s"
  65. self.logger.trace(log_text, prefix, task_counter, logged_scope) # type: ignore
  66. try:
  67. await self.app(scope, inner_receive, inner_send)
  68. except BaseException as exc:
  69. log_text = "%s [%d] Raised exception"
  70. self.logger.trace(log_text, prefix, task_counter) # type: ignore
  71. raise exc from None
  72. else:
  73. log_text = "%s [%d] Completed"
  74. self.logger.trace(log_text, prefix, task_counter) # type: ignore