1use std::any::Any;
13use std::fmt;
14use std::sync::{Arc, OnceLock};
15
16pub struct TracingField {
39 log_name: &'static str,
41 span_field: Option<&'static str>,
43 extract: Option<ExtractFns>,
46 fmt_fn: Option<Arc<dyn Fn(&dyn Any) -> Option<String> + Send + Sync>>,
48}
49
50pub(crate) struct ExtractFns {
55 pub from_str: Option<Arc<dyn Fn(&str, &'static str) + Send + Sync>>,
56 pub from_u64: Option<Arc<dyn Fn(u64, &'static str) + Send + Sync>>,
57 pub from_i64: Option<Arc<dyn Fn(i64, &'static str) + Send + Sync>>,
58 pub from_bool: Option<Arc<dyn Fn(bool, &'static str) + Send + Sync>>,
59}
60
61impl TracingField {
62 pub fn builder(log_name: &'static str) -> TracingFieldBuilder {
67 TracingFieldBuilder {
68 log_name,
69 span_field: None,
70 from_str: None,
71 from_u64: None,
72 from_i64: None,
73 from_bool: None,
74 fmt_fn: None,
75 }
76 }
77
78 pub fn log_name(&self) -> &'static str {
80 self.log_name
81 }
82
83 pub fn span_field(&self) -> Option<&'static str> {
85 self.span_field
86 }
87
88 pub fn has_extract(&self) -> bool {
90 self.extract.is_some()
91 }
92
93 pub fn has_enrich(&self) -> bool {
95 self.fmt_fn.is_some()
96 }
97
98 pub fn format(&self, any_val: &dyn Any) -> Option<String> {
101 self.fmt_fn.as_ref().and_then(|f| f(any_val))
102 }
103
104 pub(crate) fn extract_fns(&self) -> Option<&ExtractFns> {
106 self.extract.as_ref()
107 }
108}
109
110pub struct TracingFieldBuilder {
114 log_name: &'static str,
115 span_field: Option<&'static str>,
116 from_str: Option<Arc<dyn Fn(&str, &'static str) + Send + Sync>>,
117 from_u64: Option<Arc<dyn Fn(u64, &'static str) + Send + Sync>>,
118 from_i64: Option<Arc<dyn Fn(i64, &'static str) + Send + Sync>>,
119 from_bool: Option<Arc<dyn Fn(bool, &'static str) + Send + Sync>>,
120 fmt_fn: Option<Arc<dyn Fn(&dyn Any) -> Option<String> + Send + Sync>>,
121}
122
123impl TracingFieldBuilder {
124 pub fn span_field(mut self, name: &'static str) -> Self {
129 self.span_field = Some(name);
130 self
131 }
132
133 pub fn extract_from_str<T>(mut self, f: impl Fn(&str) -> Option<T> + Send + Sync + 'static) -> Self
140 where
141 T: Clone + Send + Sync + serde::Serialize + serde::de::DeserializeOwned + 'static,
142 {
143 self.from_str = Some(Arc::new(move |value, key| {
144 if let Some(v) = f(value) {
145 dcontext::set_context(key, v);
146 }
147 }));
148 self
149 }
150
151 pub fn extract_from_u64<T>(mut self, f: impl Fn(u64) -> Option<T> + Send + Sync + 'static) -> Self
153 where
154 T: Clone + Send + Sync + serde::Serialize + serde::de::DeserializeOwned + 'static,
155 {
156 self.from_u64 = Some(Arc::new(move |value, key| {
157 if let Some(v) = f(value) {
158 dcontext::set_context(key, v);
159 }
160 }));
161 self
162 }
163
164 pub fn extract_from_i64<T>(mut self, f: impl Fn(i64) -> Option<T> + Send + Sync + 'static) -> Self
166 where
167 T: Clone + Send + Sync + serde::Serialize + serde::de::DeserializeOwned + 'static,
168 {
169 self.from_i64 = Some(Arc::new(move |value, key| {
170 if let Some(v) = f(value) {
171 dcontext::set_context(key, v);
172 }
173 }));
174 self
175 }
176
177 pub fn extract_from_bool<T>(mut self, f: impl Fn(bool) -> Option<T> + Send + Sync + 'static) -> Self
179 where
180 T: Clone + Send + Sync + serde::Serialize + serde::de::DeserializeOwned + 'static,
181 {
182 self.from_bool = Some(Arc::new(move |value, key| {
183 if let Some(v) = f(value) {
184 dcontext::set_context(key, v);
185 }
186 }));
187 self
188 }
189
190 pub fn enrich_display<T: fmt::Display + 'static>(mut self) -> Self {
194 self.fmt_fn = Some(Arc::new(|any_val| {
195 any_val.downcast_ref::<T>().map(|v| v.to_string())
196 }));
197 self
198 }
199
200 pub fn enrich_debug<T: fmt::Debug + 'static>(mut self) -> Self {
202 self.fmt_fn = Some(Arc::new(|any_val| {
203 any_val.downcast_ref::<T>().map(|v| format!("{:?}", v))
204 }));
205 self
206 }
207
208 pub fn enrich_custom<T: 'static>(
210 mut self,
211 f: impl Fn(&T) -> String + Send + Sync + 'static,
212 ) -> Self {
213 self.fmt_fn = Some(Arc::new(move |any_val| {
214 any_val.downcast_ref::<T>().map(&f)
215 }));
216 self
217 }
218
219 pub fn build(self) -> TracingField {
221 let extract = if self.from_str.is_some()
222 || self.from_u64.is_some()
223 || self.from_i64.is_some()
224 || self.from_bool.is_some()
225 {
226 Some(ExtractFns {
227 from_str: self.from_str,
228 from_u64: self.from_u64,
229 from_i64: self.from_i64,
230 from_bool: self.from_bool,
231 })
232 } else {
233 None
234 };
235
236 TracingField {
237 log_name: self.log_name,
238 span_field: self.span_field,
239 extract,
240 fmt_fn: self.fmt_fn,
241 }
242 }
243}
244
245pub(crate) struct TracingFieldEntry {
249 pub context_key: &'static str,
250 pub span_field: &'static str,
252 pub extract: Option<ExtractFns>,
254 pub log_name: &'static str,
256 pub fmt_fn: Option<Arc<dyn Fn(&dyn Any) -> Option<String> + Send + Sync>>,
258}
259
260static TRACING_FIELDS: OnceLock<Vec<TracingFieldEntry>> = OnceLock::new();
263
264pub(crate) fn get_tracing_fields() -> &'static [TracingFieldEntry] {
266 TRACING_FIELDS.get_or_init(|| {
267 dcontext::keys_with_metadata::<TracingField, _>(|key, meta| {
268 let span_field = meta.span_field.unwrap_or(key);
269 TracingFieldEntry {
270 context_key: key,
271 span_field,
272 extract: meta.extract.as_ref().map(|e| ExtractFns {
273 from_str: e.from_str.as_ref().map(Arc::clone),
274 from_u64: e.from_u64.as_ref().map(Arc::clone),
275 from_i64: e.from_i64.as_ref().map(Arc::clone),
276 from_bool: e.from_bool.as_ref().map(Arc::clone),
277 }),
278 log_name: meta.log_name,
279 fmt_fn: meta.fmt_fn.as_ref().map(Arc::clone),
280 }
281 })
282 })
283}
284
285pub fn collect_log_fields() -> Vec<(&'static str, String)> {
294 let entries = get_tracing_fields();
295 let mut result = Vec::new();
296 for entry in entries {
297 if let Some(ref fmt_fn) = entry.fmt_fn {
298 if let Some(formatted) = dcontext::with_context_value(entry.context_key, |any_val| {
299 fmt_fn(any_val)
300 })
301 .flatten()
302 {
303 result.push((entry.log_name, formatted));
304 }
305 }
306 }
307 result
308}
309
310use tracing::Subscriber;
313use tracing_subscriber::fmt::format::Writer;
314use tracing_subscriber::fmt::{FmtContext, FormatEvent, FormatFields};
315use tracing_subscriber::registry::LookupSpan;
316
317pub struct WithContextFields<F> {
337 inner: F,
338}
339
340impl<F> WithContextFields<F> {
341 pub fn wrap(inner: F) -> Self {
346 Self { inner }
347 }
348}
349
350impl<S, N, F> FormatEvent<S, N> for WithContextFields<F>
351where
352 F: FormatEvent<S, N>,
353 S: Subscriber + for<'a> LookupSpan<'a>,
354 N: for<'writer> FormatFields<'writer> + 'static,
355{
356 fn format_event(
357 &self,
358 ctx: &FmtContext<'_, S, N>,
359 mut writer: Writer<'_>,
360 event: &tracing::Event<'_>,
361 ) -> fmt::Result {
362 let entries = get_tracing_fields();
363 for entry in entries {
364 if let Some(ref fmt_fn) = entry.fmt_fn {
365 if let Some(formatted) =
366 dcontext::with_context_value(entry.context_key, |any_val| fmt_fn(any_val))
367 .flatten()
368 {
369 write!(writer, "{}={} ", entry.log_name, formatted)?;
370 }
371 }
372 }
373 self.inner.format_event(ctx, writer, event)
374 }
375}