init_tracing_opentelemetry/
formats.rs

1//! Format-specific layer builders for tracing output.
2//!
3//! Provides implementations for different log formats (Pretty, JSON, Compact, Logfmt)
4//! using the strategy pattern with the [`LayerBuilder`] trait.
5
6use tracing::Subscriber;
7use tracing_subscriber::fmt;
8use tracing_subscriber::fmt::format::FmtSpan;
9use tracing_subscriber::fmt::time::{time, uptime, Uptime};
10use tracing_subscriber::{registry::LookupSpan, Layer};
11
12use crate::config::{LogTimer, TracingConfig, WriterConfig};
13use crate::{Error, FeatureSet};
14
15/// Trait for building format-specific tracing layers
16pub trait LayerBuilder: Send + Sync {
17    fn build_layer<S>(
18        &self,
19        config: &TracingConfig,
20    ) -> Result<Box<dyn Layer<S> + Send + Sync + 'static>, Error>
21    where
22        S: Subscriber + for<'a> LookupSpan<'a>;
23}
24
25fn configure_layer<S, N, L, T, W>(
26    mut layer: fmt::Layer<S, N, fmt::format::Format<L, T>, W>,
27    config: &TracingConfig,
28) -> Result<Box<dyn Layer<S> + Send + Sync + 'static>, Error>
29where
30    S: Subscriber + for<'a> LookupSpan<'a>,
31    N: for<'writer> fmt::FormatFields<'writer> + Send + Sync + 'static,
32    L: Send + Sync + 'static,
33    fmt::format::Format<L, ()>: fmt::FormatEvent<S, N>,
34    fmt::format::Format<L, Uptime>: fmt::FormatEvent<S, N>,
35    fmt::format::Format<L>: fmt::FormatEvent<S, N>,
36    W: for<'writer> fmt::MakeWriter<'writer> + Send + Sync + 'static,
37{
38    // NOTE: Destructure to make sure we don’t miss a feature
39    let FeatureSet {
40        file_names,
41        line_numbers,
42        thread_names,
43        thread_ids,
44        timer,
45        span_events,
46        target_display,
47    } = &config.features;
48    let span_events = span_events
49        .as_ref()
50        .map_or(FmtSpan::NONE, ToOwned::to_owned);
51
52    // Configure features
53    layer = layer
54        .with_file(*file_names)
55        .with_line_number(*line_numbers)
56        .with_thread_names(*thread_names)
57        .with_thread_ids(*thread_ids)
58        .with_span_events(span_events)
59        .with_target(*target_display);
60
61    // Configure timer and writer
62    match timer {
63        LogTimer::None => configure_writer(layer.without_time(), &config.writer),
64        LogTimer::Time => configure_writer(layer.with_timer(time()), &config.writer),
65        LogTimer::Uptime => configure_writer(layer.with_timer(uptime()), &config.writer),
66    }
67}
68
69fn configure_writer<S, N, L, T, W>(
70    layer: fmt::Layer<S, N, fmt::format::Format<L, T>, W>,
71    writer: &WriterConfig,
72) -> Result<Box<dyn Layer<S> + Send + Sync + 'static>, Error>
73where
74    S: Subscriber + for<'a> LookupSpan<'a>,
75    N: for<'writer> fmt::FormatFields<'writer> + Send + Sync + 'static,
76    L: Send + Sync + 'static,
77    T: Send + Sync + 'static,
78    fmt::format::Format<L, T>: fmt::FormatEvent<S, N>,
79{
80    match writer {
81        WriterConfig::Stdout => Ok(Box::new(layer.with_writer(std::io::stdout))),
82        WriterConfig::Stderr => Ok(Box::new(layer.with_writer(std::io::stderr))),
83        WriterConfig::File(path) => {
84            let file = std::fs::OpenOptions::new()
85                .create(true)
86                .append(true)
87                .open(path)?;
88            Ok(Box::new(layer.with_writer(file)))
89        }
90    }
91}
92
93/// Builder for pretty-formatted logs (development style)
94#[derive(Debug, Default, Clone)]
95pub struct PrettyLayerBuilder;
96
97impl LayerBuilder for PrettyLayerBuilder {
98    fn build_layer<S>(
99        &self,
100        config: &TracingConfig,
101    ) -> Result<Box<dyn Layer<S> + Send + Sync + 'static>, Error>
102    where
103        S: Subscriber + for<'a> LookupSpan<'a>,
104    {
105        let layer = tracing_subscriber::fmt::layer().pretty();
106
107        configure_layer(layer, config)
108    }
109}
110
111/// Builder for JSON-formatted logs (production style)
112#[derive(Debug, Default, Clone)]
113pub struct JsonLayerBuilder;
114
115impl LayerBuilder for JsonLayerBuilder {
116    fn build_layer<S>(
117        &self,
118        config: &TracingConfig,
119    ) -> Result<Box<dyn Layer<S> + Send + Sync + 'static>, Error>
120    where
121        S: Subscriber + for<'a> LookupSpan<'a>,
122    {
123        let layer = tracing_subscriber::fmt::layer().json();
124
125        configure_layer(layer, config)
126    }
127}
128
129/// Builder for full-formatted logs (default `tracing` style)
130#[derive(Debug, Default, Clone)]
131pub struct FullLayerBuilder;
132
133impl LayerBuilder for FullLayerBuilder {
134    fn build_layer<S>(
135        &self,
136        config: &TracingConfig,
137    ) -> Result<Box<dyn Layer<S> + Send + Sync + 'static>, Error>
138    where
139        S: Subscriber + for<'a> LookupSpan<'a>,
140    {
141        let layer = tracing_subscriber::fmt::layer();
142
143        configure_layer(layer, config)
144    }
145}
146
147/// Builder for compact-formatted logs (minimal style)
148#[derive(Debug, Default, Clone)]
149pub struct CompactLayerBuilder;
150
151impl LayerBuilder for CompactLayerBuilder {
152    fn build_layer<S>(
153        &self,
154        config: &TracingConfig,
155    ) -> Result<Box<dyn Layer<S> + Send + Sync + 'static>, Error>
156    where
157        S: Subscriber + for<'a> LookupSpan<'a>,
158    {
159        let layer = tracing_subscriber::fmt::layer().compact();
160
161        configure_layer(layer, config)
162    }
163}
164
165/// Builder for logfmt-formatted logs
166#[cfg(feature = "logfmt")]
167#[derive(Debug, Default, Clone)]
168pub struct LogfmtLayerBuilder;
169
170#[cfg(feature = "logfmt")]
171impl LayerBuilder for LogfmtLayerBuilder {
172    fn build_layer<S>(
173        &self,
174        config: &TracingConfig,
175    ) -> Result<Box<dyn Layer<S> + Send + Sync + 'static>, Error>
176    where
177        S: Subscriber + for<'a> LookupSpan<'a>,
178    {
179        // Note: tracing_logfmt doesn't support the same configuration options
180        // as the standard fmt layer, so we have limited configuration ability
181
182        match &config.writer {
183            WriterConfig::Stderr => {
184                // For stderr, we need to use the builder pattern since layer() doesn't support with_writer
185                // However, the current tracing_logfmt version may not support this
186                // For now, we'll fall back to the basic layer
187                Ok(Box::new(tracing_logfmt::layer()))
188            }
189            _ => {
190                // Default behavior uses stdout
191                Ok(Box::new(tracing_logfmt::layer()))
192            }
193        }
194    }
195}