use std::any::Any;
use std::fmt;
use std::sync::{Arc, OnceLock};
#[allow(clippy::type_complexity)]
pub struct TracingField {
log_name: &'static str,
span_field: Option<&'static str>,
record_field: Option<&'static str>,
extract: Option<ExtractFns>,
log_fmt_fn: Option<Arc<dyn Fn(&dyn Any) -> Option<String> + Send + Sync>>,
span_fmt_fn: Option<Arc<dyn Fn(&dyn Any) -> Option<String> + Send + Sync>>,
}
#[allow(clippy::type_complexity)]
pub(crate) struct ExtractFns {
pub from_str:
Option<Arc<dyn Fn(&str) -> Option<Arc<dyn dcontext::value::ContextValue>> + Send + Sync>>,
pub from_u64:
Option<Arc<dyn Fn(u64) -> Option<Arc<dyn dcontext::value::ContextValue>> + Send + Sync>>,
pub from_i64:
Option<Arc<dyn Fn(i64) -> Option<Arc<dyn dcontext::value::ContextValue>> + Send + Sync>>,
pub from_bool:
Option<Arc<dyn Fn(bool) -> Option<Arc<dyn dcontext::value::ContextValue>> + Send + Sync>>,
}
impl TracingField {
pub fn builder(log_name: &'static str) -> TracingFieldBuilder {
TracingFieldBuilder {
log_name,
span_field: None,
record_field: None,
from_str: None,
from_u64: None,
from_i64: None,
from_bool: None,
log_fmt_fn: None,
span_fmt_fn: None,
}
}
pub fn log_name(&self) -> &'static str {
self.log_name
}
pub fn span_field(&self) -> Option<&'static str> {
self.span_field
}
pub fn record_field(&self) -> &'static str {
self.record_field.unwrap_or(self.log_name)
}
pub fn has_extract(&self) -> bool {
self.extract.is_some()
}
pub fn has_enrich(&self) -> bool {
self.log_fmt_fn.is_some() || self.span_fmt_fn.is_some()
}
pub fn has_log_enrich(&self) -> bool {
self.log_fmt_fn.is_some()
}
pub fn has_span_record(&self) -> bool {
self.span_fmt_fn.is_some()
}
pub fn format(&self, any_val: &dyn Any) -> Option<String> {
self.log_fmt_fn.as_ref().and_then(|f| f(any_val))
}
pub fn format_for_span(&self, any_val: &dyn Any) -> Option<String> {
self.span_fmt_fn.as_ref().and_then(|f| f(any_val))
}
}
#[allow(clippy::type_complexity)]
pub struct TracingFieldBuilder {
log_name: &'static str,
span_field: Option<&'static str>,
record_field: Option<&'static str>,
from_str:
Option<Arc<dyn Fn(&str) -> Option<Arc<dyn dcontext::value::ContextValue>> + Send + Sync>>,
from_u64:
Option<Arc<dyn Fn(u64) -> Option<Arc<dyn dcontext::value::ContextValue>> + Send + Sync>>,
from_i64:
Option<Arc<dyn Fn(i64) -> Option<Arc<dyn dcontext::value::ContextValue>> + Send + Sync>>,
from_bool:
Option<Arc<dyn Fn(bool) -> Option<Arc<dyn dcontext::value::ContextValue>> + Send + Sync>>,
log_fmt_fn: Option<Arc<dyn Fn(&dyn Any) -> Option<String> + Send + Sync>>,
span_fmt_fn: Option<Arc<dyn Fn(&dyn Any) -> Option<String> + Send + Sync>>,
}
#[allow(clippy::type_complexity)]
impl TracingFieldBuilder {
pub fn span_field(mut self, name: &'static str) -> Self {
self.span_field = Some(name);
self
}
pub fn record_as(mut self, name: &'static str) -> Self {
self.record_field = Some(name);
self
}
pub fn extract_from_str<T>(
mut self,
f: impl Fn(&str) -> Option<T> + Send + Sync + 'static,
) -> Self
where
T: Clone + Send + Sync + serde::Serialize + serde::de::DeserializeOwned + 'static,
{
self.from_str = Some(Arc::new(move |value| {
f(value).map(|v| Arc::new(v) as Arc<dyn dcontext::value::ContextValue>)
}));
self
}
pub fn extract_from_u64<T>(
mut self,
f: impl Fn(u64) -> Option<T> + Send + Sync + 'static,
) -> Self
where
T: Clone + Send + Sync + serde::Serialize + serde::de::DeserializeOwned + 'static,
{
self.from_u64 = Some(Arc::new(move |value| {
f(value).map(|v| Arc::new(v) as Arc<dyn dcontext::value::ContextValue>)
}));
self
}
pub fn extract_from_i64<T>(
mut self,
f: impl Fn(i64) -> Option<T> + Send + Sync + 'static,
) -> Self
where
T: Clone + Send + Sync + serde::Serialize + serde::de::DeserializeOwned + 'static,
{
self.from_i64 = Some(Arc::new(move |value| {
f(value).map(|v| Arc::new(v) as Arc<dyn dcontext::value::ContextValue>)
}));
self
}
pub fn extract_from_bool<T>(
mut self,
f: impl Fn(bool) -> Option<T> + Send + Sync + 'static,
) -> Self
where
T: Clone + Send + Sync + serde::Serialize + serde::de::DeserializeOwned + 'static,
{
self.from_bool = Some(Arc::new(move |value| {
f(value).map(|v| Arc::new(v) as Arc<dyn dcontext::value::ContextValue>)
}));
self
}
pub fn enrich_display<T: fmt::Display + 'static>(self) -> Self {
let fmt: Arc<dyn Fn(&dyn Any) -> Option<String> + Send + Sync> =
Arc::new(|any_val| any_val.downcast_ref::<T>().map(|v| v.to_string()));
Self {
log_fmt_fn: Some(Arc::clone(&fmt)),
span_fmt_fn: Some(fmt),
..self
}
}
pub fn enrich_debug<T: fmt::Debug + 'static>(self) -> Self {
let fmt: Arc<dyn Fn(&dyn Any) -> Option<String> + Send + Sync> =
Arc::new(|any_val| any_val.downcast_ref::<T>().map(|v| format!("{:?}", v)));
Self {
log_fmt_fn: Some(Arc::clone(&fmt)),
span_fmt_fn: Some(fmt),
..self
}
}
pub fn enrich_custom<T: 'static>(
self,
f: impl Fn(&T) -> String + Send + Sync + 'static,
) -> Self {
let fmt: Arc<dyn Fn(&dyn Any) -> Option<String> + Send + Sync> =
Arc::new(move |any_val| any_val.downcast_ref::<T>().map(&f));
Self {
log_fmt_fn: Some(Arc::clone(&fmt)),
span_fmt_fn: Some(fmt),
..self
}
}
pub fn enrich_log_display<T: fmt::Display + 'static>(mut self) -> Self {
self.log_fmt_fn = Some(Arc::new(|any_val| {
any_val.downcast_ref::<T>().map(|v| v.to_string())
}));
self
}
pub fn enrich_log_debug<T: fmt::Debug + 'static>(mut self) -> Self {
self.log_fmt_fn = Some(Arc::new(|any_val| {
any_val.downcast_ref::<T>().map(|v| format!("{:?}", v))
}));
self
}
pub fn enrich_log_custom<T: 'static>(
mut self,
f: impl Fn(&T) -> String + Send + Sync + 'static,
) -> Self {
self.log_fmt_fn = Some(Arc::new(move |any_val| any_val.downcast_ref::<T>().map(&f)));
self
}
pub fn enrich_span_display<T: fmt::Display + 'static>(mut self) -> Self {
self.span_fmt_fn = Some(Arc::new(|any_val| {
any_val.downcast_ref::<T>().map(|v| v.to_string())
}));
self
}
pub fn enrich_span_debug<T: fmt::Debug + 'static>(mut self) -> Self {
self.span_fmt_fn = Some(Arc::new(|any_val| {
any_val.downcast_ref::<T>().map(|v| format!("{:?}", v))
}));
self
}
pub fn enrich_span_custom<T: 'static>(
mut self,
f: impl Fn(&T) -> String + Send + Sync + 'static,
) -> Self {
self.span_fmt_fn = Some(Arc::new(move |any_val| any_val.downcast_ref::<T>().map(&f)));
self
}
pub fn build(self) -> TracingField {
let extract = if self.from_str.is_some()
|| self.from_u64.is_some()
|| self.from_i64.is_some()
|| self.from_bool.is_some()
{
Some(ExtractFns {
from_str: self.from_str,
from_u64: self.from_u64,
from_i64: self.from_i64,
from_bool: self.from_bool,
})
} else {
None
};
TracingField {
log_name: self.log_name,
span_field: self.span_field,
record_field: self.record_field,
extract,
log_fmt_fn: self.log_fmt_fn,
span_fmt_fn: self.span_fmt_fn,
}
}
}
#[allow(clippy::type_complexity)]
pub(crate) struct TracingFieldEntry {
pub context_key: &'static str,
pub span_field: &'static str,
pub record_field: &'static str,
pub extract: Option<ExtractFns>,
pub log_name: &'static str,
pub log_fmt_fn: Option<Arc<dyn Fn(&dyn Any) -> Option<String> + Send + Sync>>,
pub span_fmt_fn: Option<Arc<dyn Fn(&dyn Any) -> Option<String> + Send + Sync>>,
}
static TRACING_FIELDS: OnceLock<Vec<TracingFieldEntry>> = OnceLock::new();
pub(crate) fn get_tracing_fields() -> &'static [TracingFieldEntry] {
TRACING_FIELDS.get_or_init(|| {
dcontext::keys_with_metadata::<TracingField, _>(|key, meta| {
let span_field = meta.span_field.unwrap_or(key);
let record_field = meta.record_field.unwrap_or(meta.log_name);
TracingFieldEntry {
context_key: key,
span_field,
record_field,
extract: meta.extract.as_ref().map(|e| ExtractFns {
from_str: e.from_str.as_ref().map(Arc::clone),
from_u64: e.from_u64.as_ref().map(Arc::clone),
from_i64: e.from_i64.as_ref().map(Arc::clone),
from_bool: e.from_bool.as_ref().map(Arc::clone),
}),
log_name: meta.log_name,
log_fmt_fn: meta.log_fmt_fn.as_ref().map(Arc::clone),
span_fmt_fn: meta.span_fmt_fn.as_ref().map(Arc::clone),
}
})
})
}
pub fn collect_log_fields() -> Vec<(&'static str, String)> {
let entries = get_tracing_fields();
let mut result = Vec::new();
for entry in entries {
if let Some(ref fmt_fn) = entry.log_fmt_fn {
let formatted = dcontext::async_ctx::with_context_value(entry.context_key, |any_val| {
fmt_fn(any_val)
})
.flatten()
.or_else(|| {
dcontext::sync_ctx::with_context_value(entry.context_key, |any_val| fmt_fn(any_val))
.flatten()
});
if let Some(formatted) = formatted {
result.push((entry.log_name, formatted));
}
}
}
result
}
use tracing::Subscriber;
use tracing_subscriber::fmt::format::Writer;
use tracing_subscriber::fmt::{FmtContext, FormatEvent, FormatFields};
use tracing_subscriber::registry::LookupSpan;
pub struct WithContextFields<F> {
inner: F,
}
impl<F> WithContextFields<F> {
pub fn wrap(inner: F) -> Self {
Self { inner }
}
}
impl<S, N, F> FormatEvent<S, N> for WithContextFields<F>
where
F: FormatEvent<S, N>,
S: Subscriber + for<'a> LookupSpan<'a>,
N: for<'writer> FormatFields<'writer> + 'static,
{
fn format_event(
&self,
ctx: &FmtContext<'_, S, N>,
mut writer: Writer<'_>,
event: &tracing::Event<'_>,
) -> fmt::Result {
let entries = get_tracing_fields();
for entry in entries {
if let Some(ref fmt_fn) = entry.log_fmt_fn {
let formatted =
dcontext::async_ctx::with_context_value(entry.context_key, |any_val| {
fmt_fn(any_val)
})
.flatten()
.or_else(|| {
dcontext::sync_ctx::with_context_value(entry.context_key, |any_val| {
fmt_fn(any_val)
})
.flatten()
});
if let Some(formatted) = formatted {
write!(writer, "{}={} ", entry.log_name, formatted)?;
}
}
}
self.inner.format_event(ctx, writer, event)
}
}