http_util.py 1.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
  1. import logging
  2. from typing import Any
  3. import httpx
  4. logger = logging.getLogger("app.http")
  5. _MAX_BODY_LOG_LENGTH = 2000
  6. def _truncate(text: str, max_len: int = _MAX_BODY_LOG_LENGTH) -> str:
  7. if len(text) <= max_len:
  8. return text
  9. return text[:max_len] + f"... (truncated, total {len(text)} chars)"
  10. def _on_request(request: httpx.Request) -> None:
  11. body = request.content.decode("utf-8", errors="replace") if request.content else ""
  12. logger.info(
  13. "[HTTP >>> ] %s %s | body(%d): %s",
  14. request.method,
  15. request.url,
  16. len(body),
  17. _truncate(body),
  18. )
  19. def _on_response(response: httpx.Response) -> None:
  20. response.read()
  21. logger.info(
  22. "[HTTP <<< ] %s %s | body(%d): %s",
  23. response.status_code,
  24. response.url,
  25. len(response.text),
  26. _truncate(response.text),
  27. )
  28. def logged_client(**kwargs: Any) -> httpx.Client:
  29. """Create an ``httpx.Client`` that logs every request body and response body."""
  30. hooks = kwargs.pop("event_hooks", {})
  31. req_hooks = list(hooks.get("request", []))
  32. resp_hooks = list(hooks.get("response", []))
  33. req_hooks.append(_on_request)
  34. resp_hooks.append(_on_response)
  35. kwargs["event_hooks"] = {"request": req_hooks, "response": resp_hooks}
  36. return httpx.Client(**kwargs)