1use std::{fmt, thread, thread::ThreadId};
15
16use tracing::{field::Field, span, Event, Metadata, Subscriber};
17use tracing_subscriber::{
18 layer::{Context, SubscriberExt},
19 registry::LookupSpan,
20 EnvFilter,
21};
22
23pub fn try_init_log_from_env() {
35 if let Ok(env_filter) = EnvFilter::try_from_default_env() {
36 init_env_filter(env_filter);
37 }
38}
39
40pub fn init_log_from_env_or<S>(fallback: S)
51where
52 S: AsRef<str>,
53{
54 let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(fallback));
55 init_env_filter(env_filter);
56}
57
58fn init_env_filter(env_filter: EnvFilter) {
59 let subscriber = tracing_subscriber::fmt()
60 .with_env_filter(env_filter)
61 .with_thread_ids(true)
62 .with_thread_names(true)
63 .with_level(true)
64 .with_target(true);
65
66 let subscriber = subscriber.finish();
67 let _ = tracing::subscriber::set_global_default(subscriber);
68}
69
70pub struct LogRecord {
71 pub target: String,
72 pub level: tracing::Level,
73 pub file: Option<&'static str>,
74 pub line: Option<u32>,
75 pub thread_id: ThreadId,
76 pub thread_name: Option<String>,
77 pub message: Option<String>,
78 pub attributes: Vec<(&'static str, String)>,
79}
80
81#[derive(Clone)]
82struct SpanFields(Vec<(&'static str, String)>);
83
84struct Layer<Enabled, Callback> {
85 enabled: Enabled,
86 callback: Callback,
87}
88
89impl<S, E, C> tracing_subscriber::Layer<S> for Layer<E, C>
90where
91 S: Subscriber + for<'a> LookupSpan<'a>,
92 E: Fn(&Metadata) -> bool + 'static,
93 C: Fn(LogRecord) + 'static,
94{
95 fn enabled(&self, metadata: &Metadata<'_>, _: Context<'_, S>) -> bool {
96 (self.enabled)(metadata)
97 }
98
99 fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
100 let span = ctx.span(id).unwrap();
101 let mut extensions = span.extensions_mut();
102 let mut fields = vec![];
103 attrs.record(&mut |field: &Field, value: &dyn fmt::Debug| {
104 fields.push((field.name(), format!("{value:?}")))
105 });
106 extensions.insert(SpanFields(fields));
107 }
108
109 fn on_record(&self, id: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
110 let span = ctx.span(id).unwrap();
111 let mut extensions = span.extensions_mut();
112 let fields = extensions.get_mut::<SpanFields>().unwrap();
113 values.record(&mut |field: &Field, value: &dyn fmt::Debug| {
114 fields.0.push((field.name(), format!("{value:?}")))
115 });
116 }
117
118 fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
119 let thread = thread::current();
120 let mut record = LogRecord {
121 target: event.metadata().target().into(),
122 level: *event.metadata().level(),
123 file: event.metadata().file(),
124 line: event.metadata().line(),
125 thread_id: thread.id(),
126 thread_name: thread.name().map(Into::into),
127 message: None,
128 attributes: vec![],
129 };
130 if let Some(scope) = ctx.event_scope(event) {
131 for span in scope.from_root() {
132 let extensions = span.extensions();
133 let fields = extensions.get::<SpanFields>().unwrap();
134 record.attributes.extend(fields.0.iter().cloned());
135 }
136 }
137 event.record(&mut |field: &Field, value: &dyn fmt::Debug| {
138 if field.name() == "message" {
139 record.message = Some(format!("{value:?}"));
140 } else {
141 record.attributes.push((field.name(), format!("{value:?}")))
142 }
143 });
144 (self.callback)(record);
145 }
146}
147
148pub fn init_log_with_callback(
149 enabled: impl Fn(&Metadata) -> bool + Send + Sync + 'static,
150 callback: impl Fn(LogRecord) + Send + Sync + 'static,
151) {
152 let subscriber = tracing_subscriber::registry().with(Layer { enabled, callback });
153 let _ = tracing::subscriber::set_global_default(subscriber);
154}
155
156#[cfg(feature = "test")]
157pub fn init_log_test() {
161 let subscriber = tracing_subscriber::fmt()
162 .with_max_level(tracing::Level::INFO)
163 .with_thread_ids(true)
164 .with_thread_names(true)
165 .with_level(true)
166 .with_target(true);
167
168 let subscriber = subscriber.finish();
169 let _ = tracing::subscriber::set_global_default(subscriber);
170}