use std::any::Any;
use std::fmt;
use std::sync::{Arc, OnceLock};
pub struct TracingField {
log_name: &'static str,
span_field: Option<&'static str>,
extract: Option<ExtractFns>,
fmt_fn: Option<Arc<dyn Fn(&dyn Any) -> Option<String> + Send + Sync>>,
}
pub(crate) struct ExtractFns {
pub from_str: Option<Arc<dyn Fn(&str, &'static str) + Send + Sync>>,
pub from_u64: Option<Arc<dyn Fn(u64, &'static str) + Send + Sync>>,
pub from_i64: Option<Arc<dyn Fn(i64, &'static str) + Send + Sync>>,
pub from_bool: Option<Arc<dyn Fn(bool, &'static str) + Send + Sync>>,
}
impl TracingField {
pub fn builder(log_name: &'static str) -> TracingFieldBuilder {
TracingFieldBuilder {
log_name,
span_field: None,
from_str: None,
from_u64: None,
from_i64: None,
from_bool: None,
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 has_extract(&self) -> bool {
self.extract.is_some()
}
pub fn has_enrich(&self) -> bool {
self.fmt_fn.is_some()
}
pub fn format(&self, any_val: &dyn Any) -> Option<String> {
self.fmt_fn.as_ref().and_then(|f| f(any_val))
}
pub(crate) fn extract_fns(&self) -> Option<&ExtractFns> {
self.extract.as_ref()
}
}
pub struct TracingFieldBuilder {
log_name: &'static str,
span_field: Option<&'static str>,
from_str: Option<Arc<dyn Fn(&str, &'static str) + Send + Sync>>,
from_u64: Option<Arc<dyn Fn(u64, &'static str) + Send + Sync>>,
from_i64: Option<Arc<dyn Fn(i64, &'static str) + Send + Sync>>,
from_bool: Option<Arc<dyn Fn(bool, &'static str) + Send + Sync>>,
fmt_fn: Option<Arc<dyn Fn(&dyn Any) -> Option<String> + Send + Sync>>,
}
impl TracingFieldBuilder {
pub fn span_field(mut self, name: &'static str) -> Self {
self.span_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, key| {
if let Some(v) = f(value) {
dcontext::set_context(key, v);
}
}));
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, key| {
if let Some(v) = f(value) {
dcontext::set_context(key, v);
}
}));
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, key| {
if let Some(v) = f(value) {
dcontext::set_context(key, v);
}
}));
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, key| {
if let Some(v) = f(value) {
dcontext::set_context(key, v);
}
}));
self
}
pub fn enrich_display<T: fmt::Display + 'static>(mut self) -> Self {
self.fmt_fn = Some(Arc::new(|any_val| {
any_val.downcast_ref::<T>().map(|v| v.to_string())
}));
self
}
pub fn enrich_debug<T: fmt::Debug + 'static>(mut self) -> Self {
self.fmt_fn = Some(Arc::new(|any_val| {
any_val.downcast_ref::<T>().map(|v| format!("{:?}", v))
}));
self
}
pub fn enrich_custom<T: 'static>(
mut self,
f: impl Fn(&T) -> String + Send + Sync + 'static,
) -> Self {
self.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,
extract,
fmt_fn: self.fmt_fn,
}
}
}
pub(crate) struct TracingFieldEntry {
pub context_key: &'static str,
pub span_field: &'static str,
pub extract: Option<ExtractFns>,
pub log_name: &'static str,
pub 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);
TracingFieldEntry {
context_key: key,
span_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,
fmt_fn: meta.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.fmt_fn {
if let Some(formatted) = dcontext::with_context_value(entry.context_key, |any_val| {
fmt_fn(any_val)
})
.flatten()
{
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.fmt_fn {
if let Some(formatted) =
dcontext::with_context_value(entry.context_key, |any_val| fmt_fn(any_val))
.flatten()
{
write!(writer, "{}={} ", entry.log_name, formatted)?;
}
}
}
self.inner.format_event(ctx, writer, event)
}
}