11"""Utility functions for logging."""
22
33import sys
4+ import time
45import uuid
56from collections .abc import Awaitable , Callable
67from pathlib import Path
@@ -20,7 +21,11 @@ def setup_log_sinks(configuration_file: Path | None = None) -> None:
2021 sink = sink_configuration .pop ("sink" )
2122 if sink == "sys.stderr" :
2223 sink = sys .stderr
23- logger .add (sink , serialize = True , ** sink_configuration )
24+ # Logs the additionally provided data as JSON.
25+ sink_configuration .setdefault ("serialize" , True )
26+ # Decouples log calls from I/O and makes it multiprocessing safe.
27+ sink_configuration .setdefault ("enqueue" , True )
28+ logger .add (sink , ** sink_configuration )
2429
2530
2631async def add_request_context_to_log (
@@ -29,10 +34,42 @@ async def add_request_context_to_log(
2934) -> Response :
3035 """Add a unique request id to each log call."""
3136 identifier = uuid .uuid4 ().hex
32- with logger .contextualize (request_id = identifier ):
37+ with logger .contextualize (
38+ request_id = identifier ,
39+ method = request .method ,
40+ path = request .url .path ,
41+ ):
3342 return await call_next (request )
3443
3544
45+ async def log_request_duration (
46+ request : Request ,
47+ call_next : Callable [[Request ], Awaitable [Response ]],
48+ ) -> Response :
49+ """Log the process and wallclock time for each call.
50+
51+ Reported times cannot be attributed solely to processing the request.
52+ As multiple requests can be handled concurrently in the same process,
53+ process time may be spent on other requests as well. The same goes for
54+ wallclock time, which is additionally influenced by e.g., context switches.
55+ """
56+ start_mono_ns = time .monotonic_ns ()
57+ start_process_ns = time .process_time_ns ()
58+ response : Response = await call_next (request )
59+
60+ duration_mono_ns = time .monotonic_ns () - start_mono_ns
61+ duration_process_ns = time .process_time_ns () - start_process_ns
62+ logger .info (
63+ "Request took {mono_ms} ms wallclock time (process time {process_ms} ms)" ,
64+ mono_ms = int (duration_mono_ns / 1_000_000 ),
65+ process_ms = int (duration_process_ns / 1_000_000 ),
66+ wallclock_time_ns = duration_mono_ns ,
67+ process_time_ns = duration_process_ns ,
68+ status = response .status_code ,
69+ )
70+ return response
71+
72+
3673async def request_response_logger (
3774 request : Request ,
3875 call_next : Callable [[Request ], Awaitable [Response ]],
0 commit comments