Skip to main content

tracing_config/
config.rs

1//! The config module contains the data model for the configuration file and [`tracing`][mod@tracing] initialization routines.
2//!
3//! This uses the [`serde`][mod@serde] and [`toml`][mod@toml] crates to serialize and deserialize the configuration file.
4//! Once the configuration is read from disk, environment variables are then resolved.
5//!
6//! See the [`interpolate`][mod@crate::interpolate] module for an understanding of how the variables are resolved.
7//!
8//! The [`model`][mod@model] submodule simply contains the configuration data model, start from there to understand how to create your tracing.toml.
9//!
10//! This module contains the tracing initialization routine [`initialize`][fn@initialize].
11
12#[path = "config.model.rs"]
13pub mod model;
14
15use serde::{Deserialize, Serialize};
16
17use std::convert::{From, Into};
18use std::path::PathBuf;
19use std::{fs, io::Write as _, path::Path};
20use std::sync::{Arc, Weak, Mutex};
21
22use crate::interpolate::resolve_from_env_recursive;
23use crate::tracing::{SpanRecordLayer, JsonLayer, SiftingLayer, SiftingLayerSelector};
24
25use ::tracing_subscriber::layer::SubscriberExt as _;
26use ::tracing_subscriber::Layer as _;
27use ::tracing_subscriber as ts;
28use ::tracing_appender as ta;
29use ::tracing as t;
30
31use crate::error::*;
32
33/// println macro when no other framework is available.
34macro_rules! emit {
35    (TRACE, $verbosity:expr, $($arg:tt)*) => {{
36        emit!($verbosity, $crate::config::model::Level::Trace, $($arg)*);
37    }};
38    (DEBUG, $verbosity:expr, $($arg:tt)*) => {{
39        emit!($verbosity, $crate::config::model::Level::Debug, $($arg)*);
40    }};
41    (INFO, $verbosity:expr, $($arg:tt)*) => {{
42        emit!($verbosity, $crate::config::model::Level::Info, $($arg)*);
43    }};
44    (WARN, $verbosity:expr, $($arg:tt)*) => {{
45        emit!($verbosity, $crate::config::model::Level::Warn, $($arg)*);
46    }};
47    (ERROR, $verbosity:expr, $($arg:tt)*) => {{
48        emit!($verbosity, $crate::config::model::Level::Error, $($arg)*);
49    }};
50    ( $verbosity:expr, $level:expr, $($arg:tt)* ) => {{
51        if let Some(verbosity) = $verbosity {
52            if verbosity <= $level {
53                use ::tracing_subscriber::fmt::time::FormatTime as _;
54                let mut now = String::new();
55                let mut now_writer = ::tracing_subscriber::fmt::format::Writer::new(&mut now);
56                ::tracing_subscriber::fmt::time::time().format_time(&mut now_writer).expect("unable to format_time");
57                let module_path = module_path!();
58                static ANSI_RESET : &str = "\x1B[0m";
59                static ANSI_DIM : &str = "\x1B[2m";
60                let (level, ansi_color, ansi_color_bold) = match $level {
61                    $crate::config::model::Level::Trace =>("TRACE","\x1B[35m", "\x1B[1;35m",),
62                    $crate::config::model::Level::Debug =>("DEBUG","\x1B[34m", "\x1B[1;34m",),
63                    $crate::config::model::Level::Info => (" INFO","\x1B[32m", "\x1B[1;32m",),
64                    $crate::config::model::Level::Warn => (" WARN","\x1B[33m", "\x1B[1;33m",),
65                    $crate::config::model::Level::Error =>("ERROR","\x1B[31m", "\x1B[1;31m",),
66                };
67                println!("{ANSI_DIM}{now}{ANSI_RESET} \
68                    {ansi_color}{level}{ANSI_RESET} \
69                    {ansi_color_bold}{module_path}{ANSI_RESET}\
70                    {ansi_color}: {}{ANSI_RESET}", format!($($arg)*));
71
72            }
73        }
74    }};
75}
76
77static ENV_TRACING_CONFIG: &str = "tracing_config";
78static ENV_TRACING_CONFIG_PARENT: &str = "tracing_config_parent";
79static ENV_TRACING_CONFIG_TEST: &str = "tracing_config_test";
80static ENV_TRACING_CONFIG_TEST_PARENT: &str = "tracing_config_test_parent";
81
82/// The value is set in stone at `25`
83///
84/// This represents the `depth` with which [`resolve_from_env_recursive`][fn@resolve_from_env_recursive] is called by this module.
85pub const RESOLVE_FROM_ENV_DEPTH: u8 = 25;
86
87/// A [`boxed`][fn@ts::Layer::boxed] [`tracing subscriber`][mod@ts] [`Layer`][trait@ts::layer::Layer]
88type BoxDynLayer<S> = Box<dyn ts::Layer<S> + Send + Sync>;
89/// Represents the stack of layers this crate creates from a configuration file excluding the final `Vec` of dyn Layer.
90type LayeredSubscriber =
91    ts::layer::Layered<SpanRecordLayer, ts::layer::Layered<ts::EnvFilter, ts::Registry>>;
92/// Represents a full [`tracing`][mod@t] [`Subscriber`][trait@t::Subscriber] implemented by [`Layered`][struct@ts::layer::Layered]
93type TracingConfigSubscriber =
94    ts::layer::Layered<Vec<BoxDynLayer<LayeredSubscriber>>, LayeredSubscriber>;
95
96/// A [`TracingConfigGuard`] wrapped in an `Arc`<`Mutex`>
97type ArcMutexGuard = Arc<Mutex<Guard>>;
98/// A [`TracingConfigGuard`] wrapped in an `Weak`<`Mutex`>
99type WeakMutexGuard = Weak<Mutex<Guard>>;
100
101trait PushGuard<T> {
102    fn push(&self, guard: T) -> Result<(), TracingConfigError>;
103}
104
105/// This guard contains data that must live for the entire lifetime of the program
106/// on drop() it will flush all remaining tracing events to their destination before closing the program.
107#[must_use = "Your program will randomly panic if you don't use the returned guard, do this : `let _tcg = tracing_config::init!{...};`"]
108pub struct TracingConfigGuard {
109    #[allow(dead_code)] // It's fine, we only use this for it's drop() and the "must_use"
110    arc_mutex_guard: ArcMutexGuard,
111}
112
113impl TracingConfigGuard {
114    fn new(arc_mutex_guard: ArcMutexGuard) -> Self {
115        Self { arc_mutex_guard }
116    }
117}
118
119/// A guard that flushes spans/events associated to asynchronous operations on a drop.
120/// It should only be dropped in main, dropping this early will result in a panic depending on how tracing is configured.
121#[must_use]
122struct Guard {
123    ta: Vec<ta::non_blocking::WorkerGuard>,
124}
125impl Guard {
126    fn new() -> Self {
127        Self { ta: Vec::new() }
128    }
129    fn new_arc_mutex() -> ArcMutexGuard {
130        Arc::new(Mutex::new(Self::new()))
131    }
132}
133
134impl PushGuard<ta::non_blocking::WorkerGuard> for WeakMutexGuard {
135    fn push(&self, guard: ta::non_blocking::WorkerGuard) -> Result<(), TracingConfigError> {
136        match self.upgrade() {
137            Some(tc) => match tc.lock() {
138                Ok(mut tc) => tc.ta.push(guard),
139                Err(_e) => {
140                    return Err(TracingConfigError::PoisonError(
141                        "TracingConfigGuard".to_owned(),
142                    ));
143                }
144            },
145            None => return Err(TracingConfigError::TracingConfigGuardDropped),
146        };
147        Ok(())
148    }
149}
150
151/// Helper macro to set layer.with_
152macro_rules! set_conf {
153    ($layer:ident, $cfg_layer:ident, $prop:ident, $with_fn:ident) => {
154        match $cfg_layer.$prop {
155            Some(val) => $layer.$with_fn(val),
156            None => $layer,
157        }
158    };
159}
160
161// String representation for data model Level
162impl AsRef<str> for model::Level {
163    fn as_ref(&self) -> &str {
164        match self {
165            model::Level::Trace => "trace",
166            model::Level::Debug => "debug",
167            model::Level::Info => "info",
168            model::Level::Warn => "warn",
169            model::Level::Error => "error",
170        }
171    }
172}
173
174impl TryFrom<&str> for model::Level {
175    type Error = TracingConfigError;
176
177    fn try_from(value: &str) -> Result<Self, TracingConfigError> {
178        match value.to_lowercase().as_str() {
179            "trace" => Ok(model::Level::Trace),
180            "debug" => Ok(model::Level::Debug),
181            "info" => Ok(model::Level::Info),
182            "warn" => Ok(model::Level::Warn),
183            "error" => Ok(model::Level::Error),
184            _ => Err(TracingConfigError::InvalidLevel {
185                level: value.to_owned(),
186            }),
187        }
188    }
189}
190
191/// Convert data model FileRotation to tracing appender
192impl From<model::FileRotation> for ta::rolling::Rotation {
193    fn from(value: model::FileRotation) -> Self {
194        match value {
195            model::FileRotation::Minutely => ta::rolling::Rotation::MINUTELY,
196            model::FileRotation::Hourly => ta::rolling::Rotation::HOURLY,
197            model::FileRotation::Daily => ta::rolling::Rotation::DAILY,
198            model::FileRotation::Never => ta::rolling::Rotation::NEVER,
199        }
200    }
201}
202
203/// Convert data model SpanEvents to tracing subscriber
204impl From<model::SpanEvents> for ts::fmt::format::FmtSpan {
205    fn from(value: model::SpanEvents) -> Self {
206        match value {
207            model::SpanEvents::New => ts::fmt::format::FmtSpan::NEW,
208            model::SpanEvents::Enter => ts::fmt::format::FmtSpan::ENTER,
209            model::SpanEvents::Exit => ts::fmt::format::FmtSpan::EXIT,
210            model::SpanEvents::Close => ts::fmt::format::FmtSpan::CLOSE,
211            model::SpanEvents::None => ts::fmt::format::FmtSpan::NONE,
212            model::SpanEvents::Active => ts::fmt::format::FmtSpan::ACTIVE,
213            model::SpanEvents::Full => ts::fmt::format::FmtSpan::FULL,
214        }
215    }
216}
217
218/// Add functionality to data model FileWriter
219impl model::FileWriter {
220    /// Creates a tracing appender blocking [`RollingFileAppender`][struct@ta::rolling::RollingFileAppender].
221    /// Whether enabled or not [`non_blocking : NonBlockingOptions`][struct@model::NonBlockingOptions] are ignored.
222    fn create_rolling_file_appender(
223        &self,
224    ) -> Result<ta::rolling::RollingFileAppender, ta::rolling::InitError> {
225        let mut builder =
226            ta::rolling::RollingFileAppender::builder().filename_prefix(self.file_name.clone());
227
228        if let Some(file_ext) = &self.file_ext {
229            builder = builder.filename_suffix(file_ext);
230        } else {
231            builder = builder.filename_suffix("log".to_owned());
232        }
233        if let Some(max_log_files) = self.max_log_files {
234            builder = builder.max_log_files(max_log_files)
235        }
236        if let Some(rotation) = &self.rotation {
237            builder = builder.rotation((*rotation).into());
238        } else {
239            builder = builder.rotation(ta::rolling::Rotation::DAILY);
240        }
241
242        builder.build(self.directory_path.clone())
243    }
244    /// Creates a tracing appender [`NonBlocking`][struct@ta::non_blocking::NonBlocking] writer.
245    /// If [`non_blocking : NonBlockingOptions`][struct@model::NonBlockingOptions] is disabled this still returns a non blocking writer with default non blocking properties.
246    fn create_non_blocking(
247        &self,
248    ) -> Result<
249        (ta::non_blocking::NonBlocking, ta::non_blocking::WorkerGuard),
250        ta::rolling::InitError,
251    > {
252        let writer = self.create_rolling_file_appender()?;
253        let mut nbb = ta::non_blocking::NonBlockingBuilder::default();
254
255        if self.non_blocking.enabled {
256            if let Some(buffered_lines_limit) = self.non_blocking.buffered_lines_limit {
257                nbb = nbb.buffered_lines_limit(buffered_lines_limit);
258            }
259            if let Some(lossy) = self.non_blocking.lossy {
260                nbb = nbb.lossy(lossy)
261            } else {
262                nbb = nbb.lossy(false)
263            }
264            if let Some(thread_name) = &self.non_blocking.thread_name {
265                nbb = nbb.thread_name(thread_name.as_str());
266            }
267        }
268
269        Ok(nbb.finish(writer))
270    }
271}
272
273// Add functionality to data model SiftingLayer
274impl model::SiftingLayer {
275    // Creates a [`Selector`][struct@sifting_layer::Selector]
276    fn get_sift_selector(&self) -> SiftingLayerSelector {
277        let mut ss = SiftingLayerSelector::new();
278        for sift_on in &self.sift_on {
279            ss = match sift_on.as_str() {
280                "${meta:level}" => ss.level(),
281                "${meta:name}" => ss.name(),
282                "${meta:target}" => ss.target(),
283                "${meta:module_path}" => ss.module_path(),
284                "${meta:file}" => ss.file(),
285                "${meta:line}" => ss.line(),
286                field => ss.field(field),
287            }
288        }
289
290        ss
291    }
292}
293
294/// Creates an [`EnvFilter`][struct@ts::filter::EnvFilter] given it's `cfg_filter_name` in the `tracing_config`
295///
296/// # Parameters
297/// * `tracing_config` - A [`TracingConfig`][struct@model::TracingConfig] object containing the filter definition.
298/// * `cfg_layer_name` - This is used solely for error reporting.
299/// * `cfg_filter_name` - The name of the filter, if `None` returns Ok(None)
300///
301/// # Returns
302///
303/// If `cfg_filter_name` is `None` it returns `Ok(None)` otherwise returns `Some(EnvFilter)` or an [`FsTracingError`][enum@TracingConfigError] in case filter is missing or there are parse errors.
304fn create_env_filter(
305    tracing_config: &model::TracingConfig,
306    cfg_layer_name: &str,
307    cfg_filter_name: &Option<String>,
308) -> Result<Option<ts::filter::EnvFilter>, TracingConfigError> {
309    let cfg_filter_name = match cfg_filter_name {
310        Some(cfg_filter_name) => cfg_filter_name,
311        None => {
312            return Ok(None);
313        }
314    };
315
316    let cfg_filter = tracing_config.filters.get(cfg_filter_name).ok_or_else(|| {
317        TracingConfigError::FilterNotFound {
318            filter: cfg_filter_name.clone(),
319            layer: cfg_layer_name.to_owned(),
320        }
321    })?;
322
323    let mut env_filter = match ts::filter::EnvFilter::builder().parse(cfg_filter.level) {
324        Ok(env_filter) => env_filter,
325        Err(parse_error) => {
326            return Err(TracingConfigError::FilterParseError {
327                filter: cfg_filter_name.clone(),
328                layer: cfg_layer_name.to_owned(),
329                error: parse_error,
330            });
331        }
332    };
333
334    if let Some(directives) = &cfg_filter.directives {
335        for directive in directives {
336            let directive = match directive.parse() {
337                Ok(directive) => directive,
338                Err(parse_error) => {
339                    return Err(TracingConfigError::FilterParseError {
340                        filter: cfg_filter_name.clone(),
341                        layer: cfg_layer_name.to_owned(),
342                        error: parse_error,
343                    });
344                }
345            };
346            env_filter = env_filter.add_directive(directive);
347        }
348    }
349
350    Ok(Some(env_filter))
351}
352
353/// Creates a [`boxed`][fn@ts::Layer::boxed] [`fmt::Layer`][struct@ts::fmt::Layer] given it's `cfg_layer_name` in the `tracing_config`.
354///
355/// # Parameters
356/// * `tracing_config` - A [`TracingConfig`][struct@model::TracingConfig] object containing the layer and filter definitions.
357/// * `cfg_layer_name` - The name of the layer as declared in `tracing_config`
358/// * `cfg_layer` - The actual [`FmtLayer`][struct@model::FmtLayer] borrowed from the `tracing_config` object.
359/// * `cfg_writer` - A writer override, if `None` the writer will be retrieved from the `tracing_config` object given `cfg_layer.writer` and will error out with an [`TracingConfigError::WriterNotFound`][type@TracingConfigError::WriterNotFound] if not found.
360/// * `guard` - If the writer is async the tracing appender worker guard will be pushed here
361///
362/// # Returns
363/// A [`boxed`][fn@ts::Layer::boxed] [`fmt::Layer`][struct@ts::fmt::Layer] configured given `cfg_layer` or an [`TracingConfigError`][enum@TracingConfigError].
364fn create_fmt_layer<S>(
365    tracing_config: &model::TracingConfig,
366    cfg_layer_name: &str,
367    cfg_layer: &model::FmtLayer,
368    cfg_writer: Option<&model::Writer>,
369    guard: WeakMutexGuard,
370) -> Result<BoxDynLayer<S>, TracingConfigError>
371where
372    S: t::Subscriber,
373    S: for<'lookup> ts::registry::LookupSpan<'lookup>,
374    S: Send + Sync,
375{
376    use ts::fmt::format::Compact as FormatCompact;
377    use ts::fmt::format::Format as TsFormat;
378    use ts::fmt::format::Full as FormatFull;
379    use ts::fmt::format::Json as FormatJson;
380    use ts::fmt::format::JsonFields as FormatJsonFields;
381    use ts::fmt::format::Pretty as FormatPretty;
382    use ts::fmt::Layer as FmtLayer;
383    enum LayerFormatted<S, F, W, T> {
384        // S = subscriber lookup span,
385        // F = format fields
386        // W = writer,
387        // T = format fields time
388        Full(FmtLayer<S, F, TsFormat<FormatFull, T>, W>),
389        Compact(FmtLayer<S, F, TsFormat<FormatCompact, T>, W>),
390        Pretty(FmtLayer<S, FormatPretty, TsFormat<FormatPretty, T>, W>),
391        Json(FmtLayer<S, FormatJsonFields, TsFormat<FormatJson, T>, W>),
392    }
393
394    macro_rules! box_layer {
395        ($fmt_layer:expr, $writer:expr, $filter:expr) => {
396            match $filter {
397                Some(filter) => match $fmt_layer {
398                    LayerFormatted::Full(fmt_layer) => {
399                        fmt_layer.with_writer($writer).with_filter(filter).boxed()
400                    }
401                    LayerFormatted::Compact(fmt_layer) => {
402                        fmt_layer.with_writer($writer).with_filter(filter).boxed()
403                    }
404                    LayerFormatted::Pretty(fmt_layer) => {
405                        fmt_layer.with_writer($writer).with_filter(filter).boxed()
406                    }
407                    LayerFormatted::Json(fmt_layer) => {
408                        fmt_layer.with_writer($writer).with_filter(filter).boxed()
409                    }
410                },
411                None => match $fmt_layer {
412                    LayerFormatted::Full(fmt_layer) => fmt_layer.with_writer($writer).boxed(),
413                    LayerFormatted::Compact(fmt_layer) => fmt_layer.with_writer($writer).boxed(),
414                    LayerFormatted::Pretty(fmt_layer) => fmt_layer.with_writer($writer).boxed(),
415                    LayerFormatted::Json(fmt_layer) => fmt_layer.with_writer($writer).boxed(),
416                },
417            }
418        };
419    }
420
421    let cfg_writer = match cfg_writer {
422        Some(cfg_writer) => cfg_writer,
423        None => tracing_config
424            .writers
425            .get(&cfg_layer.writer)
426            .ok_or_else(|| TracingConfigError::WriterNotFound {
427                writer: cfg_layer.writer.clone(),
428                layer: cfg_layer_name.to_owned(),
429            })?,
430    };
431
432    let env_filter = create_env_filter(tracing_config, cfg_layer_name, &cfg_layer.filter)?;
433
434    let mut fmt_layer = ts::fmt::Layer::new().with_ansi(cfg_layer.ansi);
435
436    // TODO !
437    // pub time : Option<bool>,
438
439    fmt_layer = set_conf!(fmt_layer, cfg_layer, level, with_level);
440    fmt_layer = set_conf!(fmt_layer, cfg_layer, target, with_target);
441    fmt_layer = set_conf!(fmt_layer, cfg_layer, file, with_file);
442    fmt_layer = set_conf!(fmt_layer, cfg_layer, line_number, with_line_number);
443    fmt_layer = set_conf!(fmt_layer, cfg_layer, thread_ids, with_thread_ids);
444    fmt_layer = set_conf!(fmt_layer, cfg_layer, thread_names, with_thread_names);
445
446    fmt_layer = fmt_layer.with_span_events(cfg_layer.span_events.into());
447
448    let fmt_layer = match cfg_layer.formatter {
449        model::FmtLayerFormatter::Full => LayerFormatted::Full(fmt_layer),
450        model::FmtLayerFormatter::Compact => LayerFormatted::Compact(fmt_layer.compact()),
451        model::FmtLayerFormatter::Pretty => LayerFormatted::Pretty(fmt_layer.pretty()),
452        model::FmtLayerFormatter::Json => {
453            let mut fmt_json_layer = fmt_layer.json();
454
455            fmt_json_layer = set_conf!(fmt_json_layer, cfg_layer, span_list, with_span_list);
456            fmt_json_layer = set_conf!(fmt_json_layer, cfg_layer, current_span, with_current_span);
457
458            fmt_json_layer = match cfg_layer.flatten_event {
459                Some(flatten_event) => fmt_json_layer.flatten_event(flatten_event),
460                None => fmt_json_layer,
461            };
462            LayerFormatted::Json(fmt_json_layer)
463        }
464    };
465
466    // writer is the second-last to be set, finally the filter
467
468    Ok(match cfg_writer {
469        model::Writer::File(cfg_file_writer) => {
470            if cfg_file_writer.non_blocking.enabled {
471                let (writer, writer_guard) = cfg_file_writer.create_non_blocking()?;
472                guard.push(writer_guard)?;
473                box_layer!(fmt_layer, writer, env_filter)
474            } else {
475                let writer = cfg_file_writer.create_rolling_file_appender()?;
476                box_layer!(fmt_layer, writer, env_filter)
477            }
478        }
479        model::Writer::StandardOutput => {
480            let writer = std::io::stdout;
481            box_layer!(fmt_layer, writer, env_filter)
482        }
483        model::Writer::StandardError => {
484            let writer = std::io::stderr;
485            box_layer!(fmt_layer, writer, env_filter)
486        }
487    })
488}
489
490/// Creates a [`boxed`][fn@ts::Layer::boxed] [`JsonLayer`][struct@JsonLayer] given it's `cfg_layer_name` in the `tracing_config`.
491///
492/// # Parameters
493/// * `tracing_config` - A [`TracingConfig`][struct@model::TracingConfig] object containing the layer and filter definitions.
494/// * `cfg_layer_name` - The name of the layer as declared in `tracing_config`
495/// * `cfg_layer` - The actual [`JsonLayer`][struct@JsonLayer] borrowed from the `tracing_config` object.
496/// * `cfg_writer` - A writer override, if `None` the writer will be retrieved from the `tracing_config` object given `cfg_layer.writer` and will error out with an [`TracingConfigError::WriterNotFound`][type@TracingConfigError::WriterNotFound] if not found.
497/// * `ta_worker_guards` - If the writer is async the tracing appender worker guard will be pushed here
498///
499/// # Returns
500/// A [`boxed`][fn@ts::Layer::boxed] [`JsonLayer`][struct@JsonLayer] configured given `cfg_layer` or an [`TracingError`][enum@TracingConfigError].
501fn create_json_layer<S>(
502    tracing_config: &model::TracingConfig,
503    cfg_layer_name: &str,
504    cfg_layer: &model::JsonLayer,
505    cfg_writer: Option<&model::Writer>,
506    guard: WeakMutexGuard,
507) -> Result<BoxDynLayer<S>, TracingConfigError>
508where
509    S: t::Subscriber,
510    S: for<'lookup> ts::registry::LookupSpan<'lookup>,
511    S: Send + Sync,
512{
513    macro_rules! json_layer_set_conf {
514        ($layer:ident, $cfg_layer:ident) => {
515            $layer = set_conf!($layer, cfg_layer, trailing_comma, with_trailing_comma);
516            $layer = set_conf!($layer, cfg_layer, pretty_json, with_pretty_json);
517            $layer = set_conf!($layer, cfg_layer, span_id, with_span_id);
518            $layer = set_conf!($layer, cfg_layer, span_uuid, with_span_uuid);
519            $layer = set_conf!($layer, cfg_layer, span_timestamp, with_span_timestamp);
520            $layer = set_conf!($layer, cfg_layer, span_level, with_span_level);
521            $layer = set_conf!($layer, cfg_layer, span_name, with_span_name);
522            $layer = set_conf!($layer, cfg_layer, span_target, with_span_target);
523            $layer = set_conf!($layer, cfg_layer, span_module_path, with_span_module_path);
524            $layer = set_conf!($layer, cfg_layer, span_file, with_span_file);
525            $layer = set_conf!($layer, cfg_layer, span_line, with_span_line);
526            $layer = set_conf!($layer, cfg_layer, span_fields, with_span_fields);
527            $layer = set_conf!($layer, cfg_layer, event_timestamp, with_event_timestamp);
528            $layer = set_conf!($layer, cfg_layer, event_level, with_event_level);
529            $layer = set_conf!($layer, cfg_layer, event_name, with_event_name);
530            $layer = set_conf!($layer, cfg_layer, event_target, with_event_target);
531            $layer = set_conf!($layer, cfg_layer, event_module_path, with_event_module_path);
532            $layer = set_conf!($layer, cfg_layer, event_file, with_event_file);
533            $layer = set_conf!($layer, cfg_layer, event_line, with_event_line);
534            $layer = set_conf!($layer, cfg_layer, event_fields, with_event_fields);
535            $layer = set_conf!($layer, cfg_layer, event_span_id, with_event_span_id);
536            $layer = set_conf!($layer, cfg_layer, event_span_uuid, with_event_span_uuid);
537            $layer = set_conf!($layer, cfg_layer, event_spans, with_event_spans);
538        };
539    }
540
541    let cfg_writer = match cfg_writer {
542        Some(cfg_writer) => cfg_writer,
543        None => tracing_config
544            .writers
545            .get(&cfg_layer.writer)
546            .ok_or_else(|| TracingConfigError::WriterNotFound {
547                writer: cfg_layer.writer.clone(),
548                layer: cfg_layer_name.to_owned(),
549            })?,
550    };
551
552    let env_filter = create_env_filter(tracing_config, cfg_layer_name, &cfg_layer.filter)?;
553
554    Ok(match cfg_writer {
555        model::Writer::File(cfg_file_writer) => {
556            if cfg_file_writer.non_blocking.enabled {
557                let (writer, writer_guard) = cfg_file_writer.create_non_blocking()?;
558                guard.push(writer_guard)?;
559                let mut json_layer = JsonLayer::new(writer);
560                json_layer_set_conf!(json_layer, cfg_layer);
561                match env_filter {
562                    Some(env_filter) => json_layer.with_filter(env_filter).boxed(),
563                    None => json_layer.boxed(),
564                }
565            } else {
566                let writer = cfg_file_writer.create_rolling_file_appender()?;
567                let mut json_layer = JsonLayer::new(writer);
568                json_layer_set_conf!(json_layer, cfg_layer);
569                match env_filter {
570                    Some(env_filter) => json_layer.with_filter(env_filter).boxed(),
571                    None => json_layer.boxed(),
572                }
573            }
574        }
575        model::Writer::StandardOutput => {
576            let writer = std::io::stdout;
577            let mut json_layer = JsonLayer::new(writer);
578            json_layer_set_conf!(json_layer, cfg_layer);
579            match env_filter {
580                Some(env_filter) => json_layer.with_filter(env_filter).boxed(),
581                None => json_layer.boxed(),
582            }
583        }
584        model::Writer::StandardError => {
585            let writer = std::io::stderr;
586            let mut json_layer = JsonLayer::new(writer);
587            json_layer_set_conf!(json_layer, cfg_layer);
588            match env_filter {
589                Some(env_filter) => json_layer.with_filter(env_filter).boxed(),
590                None => json_layer.boxed(),
591            }
592        }
593    })
594}
595
596/// Creates a [`boxed`][fn@ts::Layer::boxed] [`SiftingLayer`][struct@SiftingLayer] given it's `cfg_layer_name` in the `tracing_config`.
597///
598/// The implementation is crude but it works.
599///
600/// # Parameters
601/// * `tracing_config` - A [`TracingConfig`][struct@model::TracingConfig] object containing the layer and filter definitions as well as the sifted layer and sifted writer definitions.
602/// * `cfg_layer_name` - The name of the layer as declared in `tracing_config`
603/// * `cfg_layer` - The actual [`SiftingLayer`][struct@model::SiftingLayer] borrowed from the `tracing_config` object.
604/// * `ta_worker_guards` - If the writer is async the tracing appender worker guard will be pushed here
605///
606/// # Returns
607/// A [`boxed`][fn@ts::Layer::boxed] [`SiftingLayer`][struct@SiftingLayer] configured given `cfg_layer` or an [`TracingConfigError`][enum@TracingConfigError] (e.g.: if the sifted layer or writer are not present).
608fn create_sifting_layer<S>(
609    verbosity: Option<model::Level>,
610    tracing_config: &model::TracingConfig,
611    cfg_layer_name: &str,
612    cfg_layer: &model::SiftingLayer,
613    guard: WeakMutexGuard,
614) -> Result<BoxDynLayer<S>, TracingConfigError>
615where
616    S: t::Subscriber,
617    S: for<'lookup> ts::registry::LookupSpan<'lookup>,
618    S: Send + Sync,
619{
620    #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
621    enum SiftedLayer {
622        Fmt(model::FmtLayer),
623        Json(model::JsonLayer),
624    }
625
626    let tracing_config = tracing_config.clone();
627    let cfg_layer_name = cfg_layer_name.to_owned();
628
629    let cfg_sifted_writer = tracing_config
630        .writers
631        .get(&cfg_layer.writer)
632        .ok_or_else(|| TracingConfigError::WriterNotFound {
633            writer: cfg_layer.writer.clone(),
634            layer: cfg_layer_name.to_owned(),
635        })?
636        .clone();
637
638    let cfg_sifted_layer = tracing_config
639        .layers
640        .get(&cfg_layer.layer)
641        .ok_or_else(|| TracingConfigError::LayerNotFound {
642            sifted_layer: cfg_layer.layer.clone(),
643            layer: cfg_layer_name.to_owned(),
644        })?
645        .clone();
646
647    let cfg_sifted_layer = match cfg_sifted_layer {
648        model::Layer::Fmt(cfg_sifted_layer) => SiftedLayer::Fmt(cfg_sifted_layer),
649        model::Layer::Json(cfg_sifted_layer) => SiftedLayer::Json(cfg_sifted_layer),
650        model::Layer::Sifting(_) => {
651            return Err(TracingConfigError::SiftingLayerConf {
652                sifted_layer: cfg_layer.layer.clone(),
653                layer: cfg_layer_name.to_owned(),
654                error_message: "the sifted layer cannot be itself a sifting layer".to_owned(),
655            });
656        }
657    };
658
659    let env_filter = create_env_filter(&tracing_config, &cfg_layer_name, &cfg_layer.filter)?;
660
661    let selector = cfg_layer.get_sift_selector();
662
663    let sift_layer = SiftingLayer::new(selector, move |ss, ssv| {
664        let cfg_writer = match cfg_sifted_writer.clone() {
665            model::Writer::File(mut cfg_writer) => {
666                cfg_writer.directory_path = ss.resolve_variable(&cfg_writer.directory_path, ssv);
667                cfg_writer.file_name = ss.resolve_variable(&cfg_writer.file_name, ssv);
668                cfg_writer.file_ext = cfg_writer
669                    .file_ext
670                    .map(|file_ext| ss.resolve_variable(&file_ext, ssv));
671                // TODO! somehow include this in the config file, or document it
672                const SANITIZE_OPTIONS: sanitise_file_name::Options<Option<char>> =
673                    sanitise_file_name::Options {
674                        most_fs_safe: true,
675                        windows_safe: true,
676                        replace_with: Some('-'),
677                        collapse_replacements: true,
678                        six_measures_of_barley: "none",
679                        ..sanitise_file_name::Options::DEFAULT
680                    };
681                emit!(INFO, verbosity, "cfg_writer = {cfg_writer:?}");
682                // cfg_writer.directory_path = sanitise_file_name::sanitize_with_options(&cfg_writer.directory_path, &SANITIZE_OPTIONS);
683                cfg_writer.file_name = sanitise_file_name::sanitize_with_options(
684                    &cfg_writer.file_name,
685                    &SANITIZE_OPTIONS,
686                );
687                cfg_writer.file_ext = cfg_writer.file_ext.map(|file_ext| {
688                    sanitise_file_name::sanitize_with_options(&file_ext, &SANITIZE_OPTIONS)
689                });
690                emit!(INFO, verbosity, "cfg_writer = {cfg_writer:?}");
691                model::Writer::File(cfg_writer)
692            }
693            model::Writer::StandardOutput => model::Writer::StandardOutput,
694            model::Writer::StandardError => model::Writer::StandardError,
695        };
696
697        match cfg_sifted_layer.clone() {
698            SiftedLayer::Fmt(cfg_sifted_layer) => {
699                let layer = match create_fmt_layer(
700                    &tracing_config,
701                    &cfg_layer_name,
702                    &cfg_sifted_layer,
703                    Some(&cfg_writer),
704                    guard.clone(),
705                ) {
706                    Ok(layer) => layer,
707                    Err(err) => {
708                        emit!(
709                            ERROR,
710                            verbosity,
711                            "Could not create sifted fmt layer `{}`, please review the configuration or open bug report, the error is: {}",
712                            cfg_layer_name,
713                            err
714                        );
715                        // TODO: this needs work,
716                        // a panic here is most likely a configuration file mistake
717                        // or maybe we should modify the sifting layer builder and allow it to return an error ?
718                        // but then the sifting layer would have to cope with it ...
719                        // maybe it should fallback to standard output with a basic tracing subscriber fmt layer ...
720                        panic!(
721                            "Could not create sifted fmt layer `{}`, please review the configuration or open bug report, the error is: {}",
722                            cfg_layer_name, err
723                        );
724                    }
725                };
726                layer.boxed()
727            }
728            SiftedLayer::Json(cfg_sifted_layer) => {
729                let layer = match create_json_layer(
730                    &tracing_config,
731                    &cfg_layer_name,
732                    &cfg_sifted_layer,
733                    Some(&cfg_writer),
734                    guard.clone(),
735                ) {
736                    Ok(layer) => layer,
737                    Err(err) => {
738                        emit!(
739                            ERROR,
740                            verbosity,
741                            "Could not create sifted fmt layer `{}`, please review the configuration or open bug report, the error is: {}",
742                            cfg_layer_name,
743                            err
744                        );
745                        // TODO: this needs work,
746                        // a panic here is most likely a configuration file mistake
747                        panic!(
748                            "Could not create sifted fmt layer `{}`, please review the configuration or open bug report, the error is: {}",
749                            cfg_layer_name, err
750                        );
751                    }
752                };
753                layer.boxed()
754            }
755        }
756    });
757
758    Ok(match env_filter {
759        Some(env_filter) => sift_layer.with_filter(env_filter).boxed(),
760        None => sift_layer.boxed(),
761    })
762}
763
764/// Creates a [`Vec`][struct@Vec] of [`boxed`][fn@ts::Layer::boxed] [`Layer`][trait@ts::layer::Layer] given all the `layers` in `tracing_config` in no particular order.
765///
766/// [`tracing subscriber`][mod@ts] implements [`Layer`][trait@ts::layer::Layer] for [`Vec`][struct@Vec] so the end result is essentially a `Layer`
767///
768/// # Parameters
769/// * `tracing_config` - A [`TracingConfig`][struct@model::TracingConfig] object containing the layer and filter and writer definitions.
770///
771/// # Returns
772/// A tuple of [`Vec`][struct@Vec] of [`boxed`][fn@ts::Layer::boxed] [`Layer`][trait@ts::layer::Layer] and a [`ArcMutexGuard`][type@ArcMutexGuard] or an [`TracingConfigError`][enum@TracingConfigError].
773fn create_layers<S>(
774    verbosity: Option<model::Level>,
775    tracing_config: &model::TracingConfig,
776) -> Result<(Vec<BoxDynLayer<S>>, ArcMutexGuard), TracingConfigError>
777where
778    S: t::Subscriber,
779    S: for<'lookup> ts::registry::LookupSpan<'lookup>,
780    S: Send + Sync,
781{
782    let guard = Guard::new_arc_mutex();
783
784    let mut layers: Vec<BoxDynLayer<S>> = Vec::new();
785
786    for (cfg_layer_name, cfg_layer) in &tracing_config.layers {
787        // skip layers having writer = "${sl:sifted}" because they are sifted layers
788        // return error if they have a filter applied
789        match cfg_layer {
790            model::Layer::Fmt(cfg_layer) => {
791                if cfg_layer.writer == "${sl:sifted}" {
792                    if let Some(filter) = &cfg_layer.filter {
793                        return Err(TracingConfigError::SiftingLayerConf {
794                            sifted_layer: cfg_layer_name.to_owned(),
795                            layer: "not parsed yet".to_owned(),
796                            error_message: format!(
797                                "sifted layers may not have filters, found filter `{filter}`"
798                            ),
799                        });
800                    }
801                    continue;
802                }
803            }
804            model::Layer::Json(cfg_layer) => {
805                if cfg_layer.writer == "${sl:sifted}" {
806                    if let Some(filter) = &cfg_layer.filter {
807                        return Err(TracingConfigError::SiftingLayerConf {
808                            sifted_layer: cfg_layer_name.to_owned(),
809                            layer: "not parsed yet".to_owned(),
810                            error_message: format!(
811                                "sifted layers may not have filters, found filter `{filter}`"
812                            ),
813                        });
814                    }
815                    continue;
816                }
817            }
818            model::Layer::Sifting(_) => (),
819        }
820
821        match cfg_layer {
822            model::Layer::Fmt(cfg_layer) => {
823                layers.push(create_fmt_layer(
824                    tracing_config,
825                    cfg_layer_name,
826                    cfg_layer,
827                    None,
828                    Arc::downgrade(&guard),
829                )?);
830            }
831            model::Layer::Json(cfg_layer) => {
832                layers.push(create_json_layer(
833                    tracing_config,
834                    cfg_layer_name,
835                    cfg_layer,
836                    None,
837                    Arc::downgrade(&guard),
838                )?);
839            }
840            model::Layer::Sifting(cfg_layer) => {
841                layers.push(create_sifting_layer(
842                    verbosity,
843                    tracing_config,
844                    cfg_layer_name,
845                    cfg_layer,
846                    Arc::downgrade(&guard),
847                )?);
848            }
849        }
850    }
851
852    Ok((layers, guard))
853}
854
855/// Creates an [`EnvFilter`][struct@ts::filter::EnvFilter] given the filter name `root` in the `tracing_config`
856///
857/// # Parameters
858/// * `tracing_config` - A [`TracingConfig`][struct@model::TracingConfig] object containing the filter definition.
859///
860/// # Returns
861/// The root `EnvFilter` or an [`TracingError`][enum@TracingConfigError] in case filter is missing or there are parse errors.
862fn create_root_filter(
863    tracing_config: &model::TracingConfig,
864) -> Result<ts::filter::EnvFilter, TracingConfigError> {
865    let root_filter = create_env_filter(tracing_config, "root_registry", &Some("root".to_owned()))?;
866
867    let root_filter = match root_filter {
868        Some(f) => f,
869        None => {
870            return Err(TracingConfigError::FilterNotFound {
871                filter: "root".to_owned(),
872                layer: "root_registry".to_owned(),
873            });
874        }
875    };
876
877    Ok(root_filter)
878}
879
880/// Creates a [`tracing`][mod@t] [`Subscriber`][trait@t::Subscriber] implemented by a [`tracing-subscriber`][mod@ts] [`Layered`][struct@ts::layer::Layered] [`Registry`][struct@ts::registry::Registry]
881///
882/// The layered registry is comprised of the following; starting from the root layer (the first one that is called by the actual tracing subscriber, which in turn will call the next and so on...)
883/// - The root [`EnvFilter`][struct@ts::EnvFilter] configured by the special filter named `root`.
884/// - The [`SpanRecordLayer`][struct@SpanRecordLayer] which will save all (non globally filtered out) span data values in the span's [`Extensions`][struct@ts::registry::Extensions]
885/// - The Vec of all [`boxed`][fn@ts::Layer::boxed] layers declared in the configuration file in no particular order.
886///
887/// # Parameters
888/// * `tracing_config` - A [`TracingConfig`][struct@model::TracingConfig] object containing everything.
889///
890/// # Returns
891/// A `Result`
892/// - The `Ok` variant being a tuple of ([`TracingConfigSubscriber`][type@TracingConfigSubscriber], [`ArcMutexGuard`][type@ArcMutexGuard])
893/// - The `Err` variant being an [`TracingConfigError`][enum@TracingConfigError] in case a subscriber could not be build from the `tracing_config` parameter.
894///
895/// - The [`TracingConfigSubscriber`][type@TracingConfigSubscriber] is the [`Subscriber`][trait@t::Subscriber] that should be passed to to the [`set_global_default`][fn@t::subscriber::set_global_default] function in tracing.
896/// - The [`ArcMutexGuard`][type@ArcMutexGuard] contains [`tracing appender`][mod@ta] [`WorkerGuard`][struct@ta::non_blocking::WorkerGuard]s and should only be dropped at the end of the program.
897///
898/// # Understanding the [TracingConfigSubscriber][type@TracingConfigSubscriber] type
899/// A [`Layer`][ts::layer::Layer]<`S` : `Subscriber`> by itself is not a [`tracing`][mod@t] [`Subscriber`][trait@t::Subscriber], instead an actual `Subscriber` will forward it events coming from tracing.
900/// However a generic type parameter `S` is required for the layer to receive [`Context`][struct@ts::layer::Context] from the actual subscriber, thus we say that a `Layer` "wraps" a `Subscriber`.
901///
902/// A [`Layered`][ts::layer::Layered]<`L`, `I`, `S` = `I`> is such Subscriber; in the diagram `L` is `layer` (a Layer), `I` is `inner` (a Layer) and `S` is a `Subscriber`; `I`=`S` means that `inner` must be both a `Subscriber` and a `Layer`.
903/// A [`Layered`][ts::layer::Layered] will forward messages/events from tracing first to `inner` (the `Subscriber`) and then to `layer`,
904/// this is because [`Layered`][struct@ts::layer::Layered] is not an actual `Subscriber` since `inner` is the actual `Subscriber` and there can only be 1 real `Subscriber` as suggested by the tracing [`set_global_default`][fn@t::subscriber::set_global_default] api.
905///
906/// Both [`EnvFilter`][struct@ts::EnvFilter] and [`SpanRecordLayer`][struct@SpanRecordLayer] are only layers, they do not implements `Subscriber`.
907///
908/// This leaves us with [`Registry`][struct@ts::registry::Registry] which is the actual `Subscriber`.
909///
910/// Following the diagram below we can see that [`TracingConfigSubscriber`][type@TracingConfigSubscriber] is a `Layered` where:
911/// - The `Layer` is a `Vec` (yes `Vec` implements `Layer`) `Vec<Box<dyn ts::Layer<S> + Send + Sync>>` where `S` is the "wrapped" subscriber, that is: it should have the same type as the actual subscriber that embeds it or calls it. In this case the subscriber that embeds it is the `inner` of the top level `Layered`.
912/// - The `inner` is itself another `Layered` adding the `SpanRecordLayer`
913/// - Next is yet another `Layered` having `layer = ts::EnvFilter` and `inner = ts::Registry`
914///
915/// Given that `Layered` is a `Subscriber` we can follow the chain:
916/// - L1 first calls `inner` which is L2
917/// - L2 first calls `inner` which is L3
918/// - L3 first calls `inner` which is `ts::Registry` this is the actual Subscriber
919/// - L3 calls `layer` which is the `ts::EnvFilter` and returns to L2
920/// - L2 calls `layer` which is the `SpanRecordLayer` and returns to L1
921/// - L1 calls `layer` which is the `Vec` (at this level the `Subscriber` is L2) which is why the generic type parameter `S` for `Vec<Box<dyn ts::Layer<S> + Send + Sync>>` must be the same type as L2
922///
923/// - L1 is [`TracingConfigSubscriber`][type@TracingConfigSubscriber]
924/// - L2 is [`LayeredSubscriber`][type@LayeredSubscriber]
925///
926/// This is what it expands to :
927/// ```doc
928/// // ts::layer::Layered< *L1
929/// //     layer = Vec<Box<dyn ts::Layer<
930/// //     |           subscriber = ts::layer::Layered<
931/// //     |           |                layer = SpanRecordLayer,
932/// //     |           |                inner = ts::layer::Layered<
933/// //     |           |                |           layer = ts::EnvFilter,
934/// //     |           |                |           inner = ts::Registry
935/// //     |           |                +------ >
936/// //     |           +----------- >
937/// //     +------ > + Send + Sync>>,
938/// //     inner = ts::layer::Layered< *L2
939/// //     |           layer = SpanRecordLayer,
940/// //     |           inner = ts::layer::Layered< *L3
941/// //     |           |           layer = ts::EnvFilter,
942/// //     |           |           inner = ts::Registry
943/// //     |           +------ >
944/// //     +------ >
945/// // >
946/// ```
947fn create_subscriber(
948    verbosity: Option<model::Level>,
949    tracing_config: &model::TracingConfig,
950) -> Result<(TracingConfigSubscriber, ArcMutexGuard), TracingConfigError> {
951    let (layers, guard) = create_layers(verbosity, tracing_config)?;
952
953    // the final tracing subscriber calls layers from top to bottom as they are defined in source.
954    // registry -> filter -> span values layer -> layers
955    let registry = ts::registry::Registry::default()
956        .with(create_root_filter(tracing_config)?)
957        .with(SpanRecordLayer::new())
958        .with(layers);
959
960    Ok((registry, guard))
961}
962
963/// Writes `config` to `file_path`.
964///
965/// This is useful if you don't want to bother to read the docs, instead you let the compiler guide you on what fields the various data structures have.
966///
967/// If you wish to manually write the config file, read the docs, start from here : [`TracingConfig`][struct@model::TracingConfig]
968///
969/// # Parameters
970///
971/// * `config` - A [`TracingConfig`][struct@model::TracingConfig] object to be written to desk as a toml file.
972/// * `file_path` - The destination file [`Path`][struct@Path]
973///
974/// # Returns
975///
976/// An [`TracingConfigError`][enum@TracingConfigError] in case the operation could not be completed.
977///
978/// # Usage
979/// ```
980/// # use tracing_config::interpolate::resolve_from_env;
981/// # fn f() -> Result<(), Box<dyn std::error::Error>> {
982/// let file_path = Path::new("/home/user/temp/tracing.toml");
983/// let mut filters = HashMap::new();
984/// let mut writers = HashMap::new();
985/// let mut layers = HashMap::new();
986/// // Create a Config object
987/// let config = TracingConfig {
988///     title: "Test Config".into(),
989///     filters,
990///     writers,
991///     layers,
992/// };
993/// write_config(&config, file_path)?;
994/// # Ok(()) }
995/// ```
996pub fn write_config(
997    config: &model::TracingConfig,
998    file_path: &Path,
999) -> Result<(), TracingConfigError> {
1000    // Serialize the config to a TOML string
1001    let toml_string = toml::to_string(config)?;
1002
1003    // Write the TOML string to a file
1004    let mut file = fs::File::create(file_path)?;
1005    file.write_all(toml_string.as_bytes())?;
1006
1007    Ok(())
1008}
1009
1010/// Reads a [`TracingConfig`][struct@model::TracingConfig] from `file_path`.
1011///
1012/// This is useful if you wish to [`initialize`][fn@initialize] yourself.
1013///
1014/// This function also resolves all environment variables `${env:key}` up to `resolve_from_env_depth` for all [`toml String`][type@toml::Value::String] (see [`resolve_from_env_recursive`][fn@crate::interpolate::toml::resolve_from_env_recursive])
1015///
1016/// If you wish to manually read the config file, use the [`toml`][mod@toml] crate.
1017///
1018/// # Parameters
1019///
1020/// * `file_path` - The tracing.toml source file [`Path`][struct@Path]
1021/// * `resolve_from_env_depth` - Replaces all `${env:key}` placeholders in all [`toml String`][type@toml::Value::String] values with the value of the environment variable `key` recursively up to `resolve_from_env_depth` times (see [`resolve_from_env_recursive`][fn@crate::interpolate::toml::resolve_from_env_recursive]).
1022///
1023/// # Returns
1024///
1025/// A [`TracingConfig`][struct@model::TracingConfig] object or an [`TracingConfigError`][enum@TracingConfigError] in case the operation could not be completed.
1026///
1027/// # Usage
1028/// ```
1029/// # use tracing_config::interpolate::resolve_from_env;
1030/// # fn f() -> Result<(), Box<dyn std::error::Error>> {
1031/// let file_path = Path::new("/home/user/temp/tracing.toml");
1032/// let deserialized_config = read_config(file_path, RESOLVE_FROM_ENV_DEPTH)?;
1033/// println!("Deserialized Config: {:#?}", deserialized_config);
1034/// # Ok(()) }
1035/// ```
1036pub fn read_config(
1037    file_path: &Path,
1038    resolve_from_env_depth: u8,
1039) -> Result<model::TracingConfig, TracingConfigError> {
1040    // Read the TOML string from the file
1041    let toml_string = fs::read_to_string(file_path)?;
1042
1043    // Deserialize the TOML string to a Config instance
1044    let mut toml_value: toml::Value = toml::from_str(&toml_string)?;
1045    crate::interpolate::toml::resolve_from_env_recursive(&mut toml_value, resolve_from_env_depth)?;
1046
1047    let deserialized_config: model::TracingConfig = toml_value.try_into()?;
1048
1049    Ok(deserialized_config)
1050}
1051
1052/// Searches for a configuration file and returns it's path.
1053///
1054///
1055/// # Arguments
1056///
1057/// * `qualifier` - See [`ProjectDirs`]
1058/// * `organization` - See [`ProjectDirs`]
1059/// * `name` - See [`ProjectDirs`]
1060/// * `test` - If `true` the `search path` will prioritize `.toml` files that have a `-test` suffix.
1061/// * `verbosity` - How much to print to stdout
1062/// * `env` - Key of additional environment variable
1063/// * `path` - Direct path to configuration file or dir.
1064///
1065/// # Returns
1066///
1067/// * `Option<PathBuf>` - The path to the configuration file if found, otherwise `None`.
1068///
1069#[doc = include_str!("../doc/configuration_file_search_path.md")]
1070///
1071#[doc = include_str!("../doc/reference_links.md")]
1072pub fn find_config_path(
1073    qualifier: &str,
1074    organization: &str,
1075    name: &str,
1076    test: bool,
1077    verbosity: Option<model::Level>,
1078    env: Option<&str>,
1079    path: Option<&Path>,
1080) -> Option<PathBuf> {
1081    use std::env;
1082
1083    fn env_var_with_resolve(key: &str, verbosity: Option<model::Level>) -> Option<String> {
1084        match env::var(key) {
1085            Ok(env_val) => {
1086                match resolve_from_env_recursive(env_val.as_str(), RESOLVE_FROM_ENV_DEPTH, "env") {
1087                    Ok(ok) => Some(ok),
1088                    Err(var_error) => {
1089                        match var_error {
1090                            crate::interpolate::VarError::NotPresent { key: err_key } => {
1091                                emit!(
1092                                    WARN,
1093                                    verbosity,
1094                                    "Could not resolve ${{env:{err_key}}} token, in resolve chain for '{key}'; '{err_key}' is not present."
1095                                );
1096                            }
1097                            crate::interpolate::VarError::NotUnicode {
1098                                key: err_key,
1099                                value: err_value,
1100                            } => {
1101                                emit!(
1102                                    WARN,
1103                                    verbosity,
1104                                    "Could not resolve ${{env:{err_key}}} token, in resolve chain for '{key}'; '{err_key}' \
1105                                    is present but is not unicode : {:?}.",
1106                                    err_value
1107                                );
1108                            }
1109                        }
1110                        Some(env_val)
1111                    }
1112                }
1113            }
1114            Err(var_error) => {
1115                match var_error {
1116                    env::VarError::NotPresent => {
1117                        emit!(
1118                            TRACE,
1119                            verbosity,
1120                            "Environment variable '{key}' is not present."
1121                        )
1122                    }
1123                    env::VarError::NotUnicode(os_string) => {
1124                        emit!(
1125                            WARN,
1126                            verbosity,
1127                            "Environment variable '{key}' is present but is not unicode : {:?}.",
1128                            os_string
1129                        )
1130                    }
1131                }
1132                None
1133            }
1134        }
1135    }
1136
1137    let config_filenames = if test {
1138        vec![
1139            format!("tracing-{}-test.toml", name),
1140            "tracing-test.toml".to_string(),
1141            format!("tracing-{}.toml", name),
1142            "tracing.toml".to_string(),
1143        ]
1144    } else {
1145        vec![format!("tracing-{}.toml", name), "tracing.toml".to_string()]
1146    };
1147
1148    emit!(
1149        DEBUG,
1150        verbosity,
1151        "Searching config paths for {} configuration",
1152        (if test { "test" } else { "prod" })
1153    );
1154
1155    let path = path.map(|path| PathBuf::from(path));
1156    let env = match env {
1157        Some(env) => env_var_with_resolve(env, verbosity).map(|string| PathBuf::from(string)),
1158        None => None,
1159    };
1160    let tracing_config =
1161        env_var_with_resolve(ENV_TRACING_CONFIG, verbosity).map(|string| PathBuf::from(string));
1162    let tracing_config_test = if test {
1163        env_var_with_resolve(ENV_TRACING_CONFIG_TEST, verbosity).map(|string| PathBuf::from(string))
1164    } else {
1165        None
1166    };
1167
1168    let (project_dirs_preference_dir, project_dirs_config_dir, project_dirs_config_local_dir) =
1169        match directories::ProjectDirs::from(qualifier, organization, name) {
1170            Some(project_dirs) => (
1171                Some(project_dirs.preference_dir().to_path_buf()),
1172                Some(project_dirs.config_dir().to_path_buf()),
1173                Some(project_dirs.config_local_dir().to_path_buf()),
1174            ),
1175            None => (None, None, None),
1176        };
1177
1178    let (
1179        base_dirs_preference_dir,
1180        base_dirs_config_dir,
1181        base_dirs_config_local_dir,
1182        base_dirs_home_dir,
1183    ) = match directories::BaseDirs::new() {
1184        Some(base_dirs) => (
1185            Some(base_dirs.preference_dir().to_path_buf()),
1186            Some(base_dirs.config_dir().to_path_buf()),
1187            Some(base_dirs.config_local_dir().to_path_buf()),
1188            Some(base_dirs.home_dir().to_path_buf()),
1189        ),
1190        None => (None, None, None, None),
1191    };
1192
1193    let user_dirs_home_dir = match directories::UserDirs::new() {
1194        Some(user_dirs) => Some(user_dirs.home_dir().to_path_buf()),
1195        None => None,
1196    };
1197
1198    let current_exe_dir = env::current_exe()
1199        .ok()
1200        .and_then(|dir| dir.parent().map(|parent| parent.to_path_buf()));
1201    let current_dir = env::current_dir().ok();
1202
1203    let mut search_path = Vec::new();
1204
1205    search_path.push(("path", path));
1206
1207    if let Some(env) = &env {
1208        if !env.exists() {
1209            search_path.push((
1210                "env_parent",
1211                env.parent().map(|parent| parent.to_path_buf()),
1212            ));
1213        }
1214    }
1215
1216    search_path.push(("env", env));
1217
1218    if test {
1219        if let Some(tracing_config_test) = &tracing_config_test {
1220            if !tracing_config_test.exists() {
1221                search_path.push((
1222                    ENV_TRACING_CONFIG_TEST_PARENT,
1223                    tracing_config_test
1224                        .parent()
1225                        .map(|parent| parent.to_path_buf()),
1226                ));
1227            }
1228        }
1229        search_path.push((ENV_TRACING_CONFIG_TEST, tracing_config_test));
1230    }
1231
1232    if let Some(tracing_config) = &tracing_config {
1233        if !tracing_config.exists() {
1234            search_path.push((
1235                ENV_TRACING_CONFIG_PARENT,
1236                tracing_config.parent().map(|parent| parent.to_path_buf()),
1237            ));
1238        }
1239    }
1240
1241    search_path.push((ENV_TRACING_CONFIG, tracing_config));
1242    search_path.push(("project_dirs_preference_dir", project_dirs_preference_dir));
1243    search_path.push(("project_dirs_config_dir", project_dirs_config_dir));
1244    search_path.push((
1245        "project_dirs_config_local_dir",
1246        project_dirs_config_local_dir,
1247    ));
1248    search_path.push(("base_dirs_preference_dir", base_dirs_preference_dir));
1249    search_path.push(("base_dirs_config_dir", base_dirs_config_dir));
1250    search_path.push(("base_dirs_config_local_dir", base_dirs_config_local_dir));
1251    search_path.push(("user_dirs_home_dir", user_dirs_home_dir));
1252    search_path.push(("base_dirs_home_dir", base_dirs_home_dir));
1253    search_path.push(("current_exe_dir", current_exe_dir));
1254    search_path.push(("current_dir", current_dir));
1255
1256    let search_path: Vec<(&str, PathBuf)> = search_path
1257        .into_iter()
1258        .filter_map(|(name, path)| match path {
1259            None => {
1260                emit!(DEBUG, verbosity, "search_path : {name:<29} => not set");
1261                None
1262            }
1263            Some(path) => {
1264                let path_exists = path.exists();
1265                emit!(
1266                    DEBUG,
1267                    verbosity,
1268                    "search_path : {name:<29} => {} {}",
1269                    (if path_exists { "[v]" } else { "[ ]" }),
1270                    path.display()
1271                );
1272                if path_exists {
1273                    Some((name, path))
1274                } else {
1275                    None
1276                }
1277            }
1278        })
1279        .collect();
1280
1281    for (name, path) in search_path {
1282        emit!(TRACE, verbosity, "checking path => {}", path.display());
1283        if path.is_file() {
1284            emit!(
1285                INFO,
1286                verbosity,
1287                "search_path : found {name} => {}",
1288                path.display()
1289            );
1290            return Some(path);
1291        }
1292
1293        for config_file in &config_filenames {
1294            let config_path = path.join(config_file);
1295            emit!(
1296                TRACE,
1297                verbosity,
1298                "checking path => {}",
1299                config_path.display()
1300            );
1301
1302            if config_path.is_file() {
1303                emit!(
1304                    INFO,
1305                    verbosity,
1306                    "search_path : found {name} => {}",
1307                    config_path.display()
1308                );
1309                return Some(config_path);
1310            }
1311        }
1312    }
1313
1314    None
1315}
1316
1317#[doc = include_str!("../doc/config_initialize.md")]
1318///
1319#[doc = include_str!("../doc/reference_links.md")]
1320pub fn initialize(
1321    qualifier: &str,
1322    organization: &str,
1323    name: &str,
1324    test: bool,
1325    verbosity: Option<model::Level>,
1326    env: Option<&str>,
1327    path: Option<&Path>,
1328    config: Option<model::TracingConfig>,
1329) -> Result<TracingConfigGuard, TracingConfigError> {
1330    static TRACING_INITIALIZED: Mutex<u32> = Mutex::new(0);
1331
1332    let mut tracing_init_count = match TRACING_INITIALIZED.lock() {
1333        Ok(mtx_guard) => mtx_guard,
1334        Err(_poison) => {
1335            emit!(ERROR, verbosity, "Init lock is poisoned, this is a bug!");
1336            return Err(TracingConfigError::PoisonError(
1337                "TRACING_INITIALIZED".to_owned(),
1338            ));
1339        }
1340    };
1341
1342    if *tracing_init_count > 0 {
1343        *tracing_init_count += 1;
1344        if !test {
1345            emit!(
1346                WARN,
1347                verbosity,
1348                "Ignored init, it must be called only once, usually in the main() function, init was called {} times",
1349                *tracing_init_count
1350            );
1351        }
1352        return Err(TracingConfigError::AlreadyInitialized);
1353    }
1354
1355    fn init_config(
1356        verbosity: Option<model::Level>,
1357        tracing_config: model::TracingConfig,
1358    ) -> (bool, Result<TracingConfigGuard, TracingConfigError>) {
1359        let mut is_tracing_initialized = false;
1360
1361        let (subscriber, guards) = match create_subscriber(verbosity, &tracing_config) {
1362            Ok(ok) => ok,
1363            Err(error) => {
1364                emit!(
1365                    ERROR,
1366                    verbosity,
1367                    "Could not create a subscriber : {error:#?}"
1368                );
1369                return (is_tracing_initialized, Err(error));
1370            }
1371        };
1372
1373        match t::subscriber::set_global_default(subscriber) {
1374            Ok(_) => (),
1375            Err(error) => {
1376                emit!(
1377                    ERROR,
1378                    verbosity,
1379                    "Could not set subscriber as global default : {error:#?}"
1380                );
1381                return (
1382                    is_tracing_initialized,
1383                    Err(TracingConfigError::AlreadyInitialized),
1384                );
1385            }
1386        }
1387
1388        is_tracing_initialized = true;
1389
1390        emit!(INFO, verbosity, "Tracing successfully initialized!");
1391
1392        (is_tracing_initialized, Ok(TracingConfigGuard::new(guards)))
1393    }
1394
1395    if let Some(tracing_config) = config {
1396        emit!(
1397            INFO,
1398            verbosity,
1399            "Initializing with => config, title : \"{}\"",
1400            tracing_config.title
1401        );
1402        if path.is_some() {
1403            emit!(WARN, verbosity, "'config' is set, ignoring 'path'");
1404        }
1405        if env.is_some() {
1406            emit!(WARN, verbosity, "'config' is set, ignoring 'env'");
1407        }
1408        let (is_init, result_guard) = init_config(verbosity, tracing_config);
1409        *tracing_init_count += if is_init { 1 } else { 0 };
1410        return result_guard;
1411    }
1412
1413    emit!(
1414        INFO,
1415        verbosity,
1416        "Initializing with => name : \"{}\", qualifier : \"{}\", organization : \"{}\", env = {:?}",
1417        name,
1418        qualifier,
1419        organization,
1420        env
1421    );
1422
1423    let config_path = match find_config_path(
1424        qualifier,
1425        organization,
1426        name,
1427        test,
1428        verbosity,
1429        env,
1430        path,
1431    ) {
1432        Some(config_path) => config_path,
1433        None => {
1434            emit!(
1435                ERROR,
1436                verbosity,
1437                "Could not find the configuration file, please create a tracing.toml file and double check the 'tracing_config' env var"
1438            );
1439            return Err(TracingConfigError::ConfigFileNotFound);
1440        }
1441    };
1442
1443    let tracing_config = match read_config(&config_path, RESOLVE_FROM_ENV_DEPTH) {
1444        Ok(tracing_config) => tracing_config,
1445        Err(read_error) => {
1446            emit!(
1447                ERROR,
1448                verbosity,
1449                "Could not read the config file. path = \"{}\"; error : {read_error:?}",
1450                config_path.display()
1451            );
1452            return Err(read_error);
1453        }
1454    };
1455
1456    emit!(
1457        INFO,
1458        verbosity,
1459        "Loaded configuration file titled : '{}' from : {}",
1460        tracing_config.title,
1461        config_path.display()
1462    );
1463
1464    let (is_init, result_guard) = init_config(verbosity, tracing_config);
1465    *tracing_init_count += if is_init { 1 } else { 0 };
1466    result_guard
1467}