1mod fmt;
7mod future;
8mod layer;
9mod service;
10
11use std::{
12 borrow::Cow,
13 sync::{
14 Arc,
15 atomic::{AtomicU64, Ordering},
16 },
17 time::Duration,
18};
19
20use quanta::Instant;
21use tokio::task_local;
22
23pub use self::{
24 fmt::EventFormatter,
25 future::{LogContextFuture, PollRecordingFuture},
26 layer::LogContextLayer,
27 service::LogContextService,
28};
29
30static LOG_CONTEXT_INDEX: AtomicU64 = AtomicU64::new(0);
33task_local! {
34 pub static CURRENT_LOG_CONTEXT: LogContext;
35}
36
37#[derive(Clone)]
40pub struct LogContext {
41 inner: Arc<LogContextInner>,
42}
43
44struct LogContextInner {
45 tag: Cow<'static, str>,
47
48 index: u64,
50
51 start: Instant,
53
54 polls: AtomicU64,
56
57 cpu_time: AtomicU64,
60}
61
62impl LogContext {
63 pub fn new(tag: impl Into<Cow<'static, str>>) -> Self {
65 let tag = tag.into();
66 let inner = LogContextInner {
67 tag,
68 index: LOG_CONTEXT_INDEX.fetch_add(1, Ordering::Relaxed),
69 start: Instant::now(),
70 polls: AtomicU64::new(0),
71 cpu_time: AtomicU64::new(0),
72 };
73
74 Self {
75 inner: Arc::new(inner),
76 }
77 }
78
79 pub fn maybe_with<F, R>(f: F) -> Option<R>
81 where
82 F: FnOnce(&Self) -> R,
83 {
84 CURRENT_LOG_CONTEXT.try_with(f).ok()
85 }
86
87 pub fn run<F: FnOnce() -> Fut, Fut: Future>(&self, f: F) -> LogContextFuture<Fut> {
90 let future = self.run_sync(f);
91 self.wrap_future(future)
92 }
93
94 pub fn run_sync<F: FnOnce() -> R, R>(&self, f: F) -> R {
97 let start = Instant::now();
98 let result = CURRENT_LOG_CONTEXT.sync_scope(self.clone(), f);
99 let elapsed = start.elapsed().as_nanos().try_into().unwrap_or(u64::MAX);
100 self.inner.cpu_time.fetch_add(elapsed, Ordering::Relaxed);
101 result
102 }
103
104 #[must_use]
106 pub fn stats(&self) -> LogContextStats {
107 let polls = self.inner.polls.load(Ordering::Relaxed);
108 let cpu_time = self.inner.cpu_time.load(Ordering::Relaxed);
109 let cpu_time = Duration::from_nanos(cpu_time);
110 let elapsed = self.inner.start.elapsed();
111 LogContextStats {
112 polls,
113 cpu_time,
114 elapsed,
115 }
116 }
117}
118
119impl std::fmt::Display for LogContext {
120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121 let tag = &self.inner.tag;
122 let index = self.inner.index;
123 write!(f, "{tag}-{index}")
124 }
125}
126
127#[derive(Debug, Clone, Copy)]
129pub struct LogContextStats {
130 pub polls: u64,
132
133 pub cpu_time: Duration,
135
136 pub elapsed: Duration,
138}
139
140impl std::fmt::Display for LogContextStats {
141 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142 let polls = self.polls;
143 #[expect(clippy::cast_precision_loss)]
144 let cpu_time_ms = self.cpu_time.as_nanos() as f64 / 1_000_000.;
145 #[expect(clippy::cast_precision_loss)]
146 let elapsed_ms = self.elapsed.as_nanos() as f64 / 1_000_000.;
147 write!(
148 f,
149 "polls: {polls}, cpu: {cpu_time_ms:.1}ms, elapsed: {elapsed_ms:.1}ms",
150 )
151 }
152}