1#[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
33macro_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
82pub const RESOLVE_FROM_ENV_DEPTH: u8 = 25;
86
87type BoxDynLayer<S> = Box<dyn ts::Layer<S> + Send + Sync>;
89type LayeredSubscriber =
91 ts::layer::Layered<SpanRecordLayer, ts::layer::Layered<ts::EnvFilter, ts::Registry>>;
92type TracingConfigSubscriber =
94 ts::layer::Layered<Vec<BoxDynLayer<LayeredSubscriber>>, LayeredSubscriber>;
95
96type ArcMutexGuard = Arc<Mutex<Guard>>;
98type WeakMutexGuard = Weak<Mutex<Guard>>;
100
101trait PushGuard<T> {
102 fn push(&self, guard: T) -> Result<(), TracingConfigError>;
103}
104
105#[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)] 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#[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
151macro_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
161impl 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
191impl 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
203impl 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
218impl model::FileWriter {
220 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 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
273impl model::SiftingLayer {
275 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
294fn 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
353fn 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 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 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 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
490fn 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
596fn 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 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.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 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 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
764fn 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 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
855fn 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
880fn 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 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
963pub fn write_config(
997 config: &model::TracingConfig,
998 file_path: &Path,
999) -> Result<(), TracingConfigError> {
1000 let toml_string = toml::to_string(config)?;
1002
1003 let mut file = fs::File::create(file_path)?;
1005 file.write_all(toml_string.as_bytes())?;
1006
1007 Ok(())
1008}
1009
1010pub fn read_config(
1037 file_path: &Path,
1038 resolve_from_env_depth: u8,
1039) -> Result<model::TracingConfig, TracingConfigError> {
1040 let toml_string = fs::read_to_string(file_path)?;
1042
1043 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#[doc = include_str!("../doc/configuration_file_search_path.md")]
1070#[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#[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}