1use std::any::Any;
15use std::fmt;
16use std::sync::{Arc, OnceLock};
17
18#[allow(clippy::type_complexity)]
42pub struct TracingField {
43 log_name: &'static str,
45 span_field: Option<&'static str>,
47 record_field: Option<&'static str>,
49 extract: Option<ExtractFns>,
51 log_fmt_fn: Option<Arc<dyn Fn(&dyn Any) -> Option<String> + Send + Sync>>,
53 span_fmt_fn: Option<Arc<dyn Fn(&dyn Any) -> Option<String> + Send + Sync>>,
55}
56
57#[allow(clippy::type_complexity)]
63pub(crate) struct ExtractFns {
64 pub from_str:
65 Option<Arc<dyn Fn(&str) -> Option<Arc<dyn dcontext::value::ContextValue>> + Send + Sync>>,
66 pub from_u64:
67 Option<Arc<dyn Fn(u64) -> Option<Arc<dyn dcontext::value::ContextValue>> + Send + Sync>>,
68 pub from_i64:
69 Option<Arc<dyn Fn(i64) -> Option<Arc<dyn dcontext::value::ContextValue>> + Send + Sync>>,
70 pub from_bool:
71 Option<Arc<dyn Fn(bool) -> Option<Arc<dyn dcontext::value::ContextValue>> + Send + Sync>>,
72}
73
74impl TracingField {
75 pub fn builder(log_name: &'static str) -> TracingFieldBuilder {
80 TracingFieldBuilder {
81 log_name,
82 span_field: None,
83 record_field: None,
84 from_str: None,
85 from_u64: None,
86 from_i64: None,
87 from_bool: None,
88 log_fmt_fn: None,
89 span_fmt_fn: None,
90 }
91 }
92
93 pub fn log_name(&self) -> &'static str {
95 self.log_name
96 }
97
98 pub fn span_field(&self) -> Option<&'static str> {
100 self.span_field
101 }
102
103 pub fn record_field(&self) -> &'static str {
106 self.record_field.unwrap_or(self.log_name)
107 }
108
109 pub fn has_extract(&self) -> bool {
111 self.extract.is_some()
112 }
113
114 pub fn has_enrich(&self) -> bool {
116 self.log_fmt_fn.is_some() || self.span_fmt_fn.is_some()
117 }
118
119 pub fn has_log_enrich(&self) -> bool {
121 self.log_fmt_fn.is_some()
122 }
123
124 pub fn has_span_record(&self) -> bool {
126 self.span_fmt_fn.is_some()
127 }
128
129 pub fn format(&self, any_val: &dyn Any) -> Option<String> {
132 self.log_fmt_fn.as_ref().and_then(|f| f(any_val))
133 }
134
135 pub fn format_for_span(&self, any_val: &dyn Any) -> Option<String> {
138 self.span_fmt_fn.as_ref().and_then(|f| f(any_val))
139 }
140}
141
142#[allow(clippy::type_complexity)]
146pub struct TracingFieldBuilder {
147 log_name: &'static str,
148 span_field: Option<&'static str>,
149 record_field: Option<&'static str>,
150 from_str:
151 Option<Arc<dyn Fn(&str) -> Option<Arc<dyn dcontext::value::ContextValue>> + Send + Sync>>,
152 from_u64:
153 Option<Arc<dyn Fn(u64) -> Option<Arc<dyn dcontext::value::ContextValue>> + Send + Sync>>,
154 from_i64:
155 Option<Arc<dyn Fn(i64) -> Option<Arc<dyn dcontext::value::ContextValue>> + Send + Sync>>,
156 from_bool:
157 Option<Arc<dyn Fn(bool) -> Option<Arc<dyn dcontext::value::ContextValue>> + Send + Sync>>,
158 log_fmt_fn: Option<Arc<dyn Fn(&dyn Any) -> Option<String> + Send + Sync>>,
159 span_fmt_fn: Option<Arc<dyn Fn(&dyn Any) -> Option<String> + Send + Sync>>,
160}
161
162#[allow(clippy::type_complexity)]
163impl TracingFieldBuilder {
164 pub fn span_field(mut self, name: &'static str) -> Self {
169 self.span_field = Some(name);
170 self
171 }
172
173 pub fn record_as(mut self, name: &'static str) -> Self {
179 self.record_field = Some(name);
180 self
181 }
182
183 pub fn extract_from_str<T>(
190 mut self,
191 f: impl Fn(&str) -> Option<T> + Send + Sync + 'static,
192 ) -> Self
193 where
194 T: Clone + Send + Sync + serde::Serialize + serde::de::DeserializeOwned + 'static,
195 {
196 self.from_str = Some(Arc::new(move |value| {
197 f(value).map(|v| Arc::new(v) as Arc<dyn dcontext::value::ContextValue>)
198 }));
199 self
200 }
201
202 pub fn extract_from_u64<T>(
204 mut self,
205 f: impl Fn(u64) -> Option<T> + Send + Sync + 'static,
206 ) -> Self
207 where
208 T: Clone + Send + Sync + serde::Serialize + serde::de::DeserializeOwned + 'static,
209 {
210 self.from_u64 = Some(Arc::new(move |value| {
211 f(value).map(|v| Arc::new(v) as Arc<dyn dcontext::value::ContextValue>)
212 }));
213 self
214 }
215
216 pub fn extract_from_i64<T>(
218 mut self,
219 f: impl Fn(i64) -> Option<T> + Send + Sync + 'static,
220 ) -> Self
221 where
222 T: Clone + Send + Sync + serde::Serialize + serde::de::DeserializeOwned + 'static,
223 {
224 self.from_i64 = Some(Arc::new(move |value| {
225 f(value).map(|v| Arc::new(v) as Arc<dyn dcontext::value::ContextValue>)
226 }));
227 self
228 }
229
230 pub fn extract_from_bool<T>(
232 mut self,
233 f: impl Fn(bool) -> Option<T> + Send + Sync + 'static,
234 ) -> Self
235 where
236 T: Clone + Send + Sync + serde::Serialize + serde::de::DeserializeOwned + 'static,
237 {
238 self.from_bool = Some(Arc::new(move |value| {
239 f(value).map(|v| Arc::new(v) as Arc<dyn dcontext::value::ContextValue>)
240 }));
241 self
242 }
243
244 pub fn enrich_display<T: fmt::Display + 'static>(self) -> Self {
251 let fmt: Arc<dyn Fn(&dyn Any) -> Option<String> + Send + Sync> =
252 Arc::new(|any_val| any_val.downcast_ref::<T>().map(|v| v.to_string()));
253 Self {
254 log_fmt_fn: Some(Arc::clone(&fmt)),
255 span_fmt_fn: Some(fmt),
256 ..self
257 }
258 }
259
260 pub fn enrich_debug<T: fmt::Debug + 'static>(self) -> Self {
265 let fmt: Arc<dyn Fn(&dyn Any) -> Option<String> + Send + Sync> =
266 Arc::new(|any_val| any_val.downcast_ref::<T>().map(|v| format!("{:?}", v)));
267 Self {
268 log_fmt_fn: Some(Arc::clone(&fmt)),
269 span_fmt_fn: Some(fmt),
270 ..self
271 }
272 }
273
274 pub fn enrich_custom<T: 'static>(
279 self,
280 f: impl Fn(&T) -> String + Send + Sync + 'static,
281 ) -> Self {
282 let fmt: Arc<dyn Fn(&dyn Any) -> Option<String> + Send + Sync> =
283 Arc::new(move |any_val| any_val.downcast_ref::<T>().map(&f));
284 Self {
285 log_fmt_fn: Some(Arc::clone(&fmt)),
286 span_fmt_fn: Some(fmt),
287 ..self
288 }
289 }
290
291 pub fn enrich_log_display<T: fmt::Display + 'static>(mut self) -> Self {
298 self.log_fmt_fn = Some(Arc::new(|any_val| {
299 any_val.downcast_ref::<T>().map(|v| v.to_string())
300 }));
301 self
302 }
303
304 pub fn enrich_log_debug<T: fmt::Debug + 'static>(mut self) -> Self {
306 self.log_fmt_fn = Some(Arc::new(|any_val| {
307 any_val.downcast_ref::<T>().map(|v| format!("{:?}", v))
308 }));
309 self
310 }
311
312 pub fn enrich_log_custom<T: 'static>(
314 mut self,
315 f: impl Fn(&T) -> String + Send + Sync + 'static,
316 ) -> Self {
317 self.log_fmt_fn = Some(Arc::new(move |any_val| any_val.downcast_ref::<T>().map(&f)));
318 self
319 }
320
321 pub fn enrich_span_display<T: fmt::Display + 'static>(mut self) -> Self {
329 self.span_fmt_fn = Some(Arc::new(|any_val| {
330 any_val.downcast_ref::<T>().map(|v| v.to_string())
331 }));
332 self
333 }
334
335 pub fn enrich_span_debug<T: fmt::Debug + 'static>(mut self) -> Self {
337 self.span_fmt_fn = Some(Arc::new(|any_val| {
338 any_val.downcast_ref::<T>().map(|v| format!("{:?}", v))
339 }));
340 self
341 }
342
343 pub fn enrich_span_custom<T: 'static>(
348 mut self,
349 f: impl Fn(&T) -> String + Send + Sync + 'static,
350 ) -> Self {
351 self.span_fmt_fn = Some(Arc::new(move |any_val| any_val.downcast_ref::<T>().map(&f)));
352 self
353 }
354
355 pub fn build(self) -> TracingField {
357 let extract = if self.from_str.is_some()
358 || self.from_u64.is_some()
359 || self.from_i64.is_some()
360 || self.from_bool.is_some()
361 {
362 Some(ExtractFns {
363 from_str: self.from_str,
364 from_u64: self.from_u64,
365 from_i64: self.from_i64,
366 from_bool: self.from_bool,
367 })
368 } else {
369 None
370 };
371
372 TracingField {
373 log_name: self.log_name,
374 span_field: self.span_field,
375 record_field: self.record_field,
376 extract,
377 log_fmt_fn: self.log_fmt_fn,
378 span_fmt_fn: self.span_fmt_fn,
379 }
380 }
381}
382
383#[allow(clippy::type_complexity)]
387pub(crate) struct TracingFieldEntry {
388 pub context_key: &'static str,
389 pub span_field: &'static str,
391 pub record_field: &'static str,
393 pub extract: Option<ExtractFns>,
395 pub log_name: &'static str,
397 pub log_fmt_fn: Option<Arc<dyn Fn(&dyn Any) -> Option<String> + Send + Sync>>,
399 pub span_fmt_fn: Option<Arc<dyn Fn(&dyn Any) -> Option<String> + Send + Sync>>,
401}
402
403static TRACING_FIELDS: OnceLock<Vec<TracingFieldEntry>> = OnceLock::new();
406
407pub(crate) fn get_tracing_fields() -> &'static [TracingFieldEntry] {
409 TRACING_FIELDS.get_or_init(|| {
410 dcontext::keys_with_metadata::<TracingField, _>(|key, meta| {
411 let span_field = meta.span_field.unwrap_or(key);
412 let record_field = meta.record_field.unwrap_or(meta.log_name);
413 TracingFieldEntry {
414 context_key: key,
415 span_field,
416 record_field,
417 extract: meta.extract.as_ref().map(|e| ExtractFns {
418 from_str: e.from_str.as_ref().map(Arc::clone),
419 from_u64: e.from_u64.as_ref().map(Arc::clone),
420 from_i64: e.from_i64.as_ref().map(Arc::clone),
421 from_bool: e.from_bool.as_ref().map(Arc::clone),
422 }),
423 log_name: meta.log_name,
424 log_fmt_fn: meta.log_fmt_fn.as_ref().map(Arc::clone),
425 span_fmt_fn: meta.span_fmt_fn.as_ref().map(Arc::clone),
426 }
427 })
428 })
429}
430
431pub fn collect_log_fields() -> Vec<(&'static str, String)> {
440 let entries = get_tracing_fields();
441 let mut result = Vec::new();
442 for entry in entries {
443 if let Some(ref fmt_fn) = entry.log_fmt_fn {
444 let formatted = dcontext::async_ctx::with_context_value(entry.context_key, |any_val| {
446 fmt_fn(any_val)
447 })
448 .flatten()
449 .or_else(|| {
450 dcontext::sync_ctx::with_context_value(entry.context_key, |any_val| fmt_fn(any_val))
451 .flatten()
452 });
453
454 if let Some(formatted) = formatted {
455 result.push((entry.log_name, formatted));
456 }
457 }
458 }
459 result
460}
461
462use tracing::Subscriber;
465use tracing_subscriber::fmt::format::Writer;
466use tracing_subscriber::fmt::{FmtContext, FormatEvent, FormatFields};
467use tracing_subscriber::registry::LookupSpan;
468
469pub struct WithContextFields<F> {
489 inner: F,
490}
491
492impl<F> WithContextFields<F> {
493 pub fn wrap(inner: F) -> Self {
498 Self { inner }
499 }
500}
501
502impl<S, N, F> FormatEvent<S, N> for WithContextFields<F>
503where
504 F: FormatEvent<S, N>,
505 S: Subscriber + for<'a> LookupSpan<'a>,
506 N: for<'writer> FormatFields<'writer> + 'static,
507{
508 fn format_event(
509 &self,
510 ctx: &FmtContext<'_, S, N>,
511 mut writer: Writer<'_>,
512 event: &tracing::Event<'_>,
513 ) -> fmt::Result {
514 let entries = get_tracing_fields();
515 for entry in entries {
516 if let Some(ref fmt_fn) = entry.log_fmt_fn {
517 let formatted =
519 dcontext::async_ctx::with_context_value(entry.context_key, |any_val| {
520 fmt_fn(any_val)
521 })
522 .flatten()
523 .or_else(|| {
524 dcontext::sync_ctx::with_context_value(entry.context_key, |any_val| {
525 fmt_fn(any_val)
526 })
527 .flatten()
528 });
529
530 if let Some(formatted) = formatted {
531 write!(writer, "{}={} ", entry.log_name, formatted)?;
532 }
533 }
534 }
535 self.inner.format_event(ctx, writer, event)
536 }
537}