arti/
logging.rs

1//! Configure tracing subscribers for Arti
2
3use anyhow::{Context, Result, anyhow};
4use derive_builder::Builder;
5use fs_mistrust::Mistrust;
6use serde::{Deserialize, Serialize};
7use std::io::IsTerminal as _;
8use std::path::Path;
9use std::str::FromStr;
10use std::time::Duration;
11use tor_basic_utils::PathExt as _;
12use tor_config::ConfigBuildError;
13use tor_config::impl_standard_builder;
14use tor_config::{define_list_builder_accessors, define_list_builder_helper};
15use tor_config_path::{CfgPath, CfgPathResolver};
16use tor_error::warn_report;
17use tracing::{Subscriber, error};
18use tracing_appender::non_blocking::WorkerGuard;
19use tracing_subscriber::layer::SubscriberExt;
20use tracing_subscriber::prelude::*;
21use tracing_subscriber::{Layer, filter::Targets, fmt, registry};
22
23mod fields;
24#[cfg(feature = "opentelemetry")]
25mod otlp_file_exporter;
26mod time;
27
28/// Structure to hold our logging configuration options
29#[derive(Debug, Clone, Builder, Eq, PartialEq)]
30#[non_exhaustive] // TODO(nickm) remove public elements when I revise this.
31#[builder(build_fn(private, name = "build_unvalidated", error = "ConfigBuildError"))]
32#[builder(derive(Debug, Serialize, Deserialize))]
33#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
34#[cfg_attr(feature = "experimental-api", builder(public))]
35pub(crate) struct LoggingConfig {
36    /// Filtering directives that determine tracing levels as described at
37    /// <https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/targets/struct.Targets.html#impl-FromStr>
38    ///
39    /// You can override this setting with the -l, --log-level command line parameter.
40    ///
41    /// Example: "info,tor_proto::channel=trace"
42    #[builder(default = "default_console_filter()", setter(into, strip_option))]
43    console: Option<String>,
44
45    /// Filtering directives for the journald logger.
46    ///
47    /// Only takes effect if Arti is built with the `journald` filter.
48    #[builder(
49        setter(into),
50        field(build = r#"tor_config::resolve_option(&self.journald, || None)"#)
51    )]
52    journald: Option<String>,
53
54    /// Configuration for logging spans with OpenTelemetry.
55    #[cfg(feature = "opentelemetry")]
56    #[builder_field_attr(serde(default))]
57    #[builder(default)]
58    opentelemetry: OpentelemetryConfig,
59
60    /// Configuration for opentelemetry (disabled)
61    //
62    // (See comments on crate::cfg::ArtiConfig::rpc for an explanation of this pattern.)
63    #[cfg(not(feature = "opentelemetry"))]
64    #[builder_field_attr(serde(default))]
65    #[builder(field(type = "Option<toml::Value>", build = "()"), private)]
66    opentelemetry: (),
67
68    /// Configuration for passing information to tokio-console.
69    #[cfg(feature = "tokio-console")]
70    #[builder(sub_builder(fn_name = "build"))]
71    #[builder_field_attr(serde(default))]
72    tokio_console: TokioConsoleConfig,
73
74    /// Configuration for tokio-console (disabled)
75    //
76    // (See comments on crate::cfg::ArtiConfig::rpc for an explanation of this pattern.)
77    #[cfg(not(feature = "tokio-console"))]
78    #[builder_field_attr(serde(default))]
79    #[builder(field(type = "Option<toml::Value>", build = "()"), private)]
80    tokio_console: (),
81
82    /// Configuration for one or more logfiles.
83    ///
84    /// The default is not to log to any files.
85    #[builder_field_attr(serde(default))]
86    #[builder(sub_builder(fn_name = "build"), setter(custom))]
87    files: LogfileListConfig,
88
89    /// If set to true, we disable safe logging on _all logs_, and store
90    /// potentially sensitive information at level `info` or higher.
91    ///
92    /// This can be useful for debugging, but it increases the value of your
93    /// logs to an attacker.  Do not turn this on in production unless you have
94    /// a good log rotation mechanism.
95    //
96    // TODO: Eventually we might want to make this more complex, and add a
97    // per-log mechanism to turn off unsafe logging. Alternatively, we might do
98    // that by extending the filter syntax implemented by `tracing` to have an
99    // "unsafe" flag on particular lines.
100    #[builder_field_attr(serde(default))]
101    #[builder(default)]
102    log_sensitive_information: bool,
103
104    /// An approximate granularity with which log times should be displayed.
105    ///
106    /// This value controls every log time that arti outputs; it doesn't have any
107    /// effect on times written by other logging programs like `journald`.
108    ///
109    /// We may round this value up for convenience: For example, if you say
110    /// "2.5s", we may treat it as if you had said "3s."
111    ///
112    /// The default is "1s", or one second.
113    #[builder(default = "std::time::Duration::new(1,0)")]
114    #[builder_field_attr(serde(default, with = "humantime_serde::option"))]
115    time_granularity: std::time::Duration,
116}
117impl_standard_builder! { LoggingConfig }
118
119impl LoggingConfigBuilder {
120    /// Build the [`LoggingConfig`].
121    #[cfg_attr(feature = "experimental-api", visibility::make(pub))]
122    pub(crate) fn build(&self) -> Result<LoggingConfig, ConfigBuildError> {
123        let config = self.build_unvalidated()?;
124
125        #[cfg(not(feature = "tokio-console"))]
126        if self.tokio_console.is_some() {
127            tracing::warn!(
128                "tokio-console options were set, but Arti was built without support for tokio-console."
129            );
130        }
131
132        #[cfg(not(feature = "opentelemetry"))]
133        if self.opentelemetry.is_some() {
134            tracing::warn!(
135                "opentelemetry options were set, but Arti was built without support for opentelemetry."
136            );
137        }
138
139        Ok(config)
140    }
141}
142
143/// Return a default tracing filter value for `logging.console`.
144#[allow(clippy::unnecessary_wraps)]
145fn default_console_filter() -> Option<String> {
146    Some("info".to_owned())
147}
148
149/// Local type alias, mostly helpful for derive_builder to DTRT
150type LogfileListConfig = Vec<LogfileConfig>;
151
152define_list_builder_helper! {
153    struct LogfileListConfigBuilder {
154        files: [LogfileConfigBuilder],
155    }
156    built: LogfileListConfig = files;
157    default = vec![];
158}
159
160define_list_builder_accessors! {
161    struct LoggingConfigBuilder {
162        pub files: [LogfileConfigBuilder],
163    }
164}
165
166/// Configuration information for an (optionally rotating) logfile.
167#[derive(Debug, Builder, Clone, Eq, PartialEq)]
168#[builder(derive(Debug, Serialize, Deserialize))]
169#[builder(build_fn(error = "ConfigBuildError"))]
170#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
171#[cfg_attr(feature = "experimental-api", builder(public))]
172pub(crate) struct LogfileConfig {
173    /// How often to rotate the file?
174    #[builder(default)]
175    rotate: LogRotation,
176    /// Where to write the files?
177    path: CfgPath,
178    /// Filter to apply before writing
179    filter: String,
180}
181
182impl_standard_builder! { LogfileConfig: !Default }
183
184/// How often to rotate a log file
185#[derive(Debug, Default, Clone, Serialize, Deserialize, Copy, Eq, PartialEq)]
186#[non_exhaustive]
187#[serde(rename_all = "lowercase")]
188#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
189pub(crate) enum LogRotation {
190    /// Rotate logs daily
191    Daily,
192    /// Rotate logs hourly
193    Hourly,
194    /// Never rotate the log
195    #[default]
196    Never,
197}
198
199/// Configuration for exporting spans with OpenTelemetry.
200#[derive(Debug, Builder, Clone, Eq, PartialEq, Serialize, Deserialize)]
201#[builder(derive(Debug, Serialize, Deserialize))]
202#[builder(build_fn(error = "ConfigBuildError"))]
203#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
204#[cfg_attr(feature = "experimental-api", builder(public))]
205pub(crate) struct OpentelemetryConfig {
206    /// Write spans to a file in OTLP JSON format.
207    #[builder(default)]
208    file: Option<OpentelemetryFileExporterConfig>,
209    /// Export spans via HTTP.
210    #[builder(default)]
211    http: Option<OpentelemetryHttpExporterConfig>,
212}
213impl_standard_builder! { OpentelemetryConfig }
214
215/// Configuration for the OpenTelemetry HTTP exporter.
216#[derive(Debug, Builder, Clone, Eq, PartialEq, Serialize, Deserialize)]
217#[builder(derive(Debug, Serialize, Deserialize))]
218#[builder(build_fn(error = "ConfigBuildError"))]
219#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
220#[cfg_attr(feature = "experimental-api", builder(public))]
221pub(crate) struct OpentelemetryHttpExporterConfig {
222    /// HTTP(S) endpoint to send spans to.
223    ///
224    /// For Jaeger, this should be something like: `http://localhost:4318/v1/traces`
225    endpoint: String,
226    /// Configuration for how to batch exports.
227    // TODO: If we can figure out the right macro invocations, this shouldn't need to be a Option.
228    batch: Option<OpentelemetryBatchConfig>,
229    /// Timeout for sending data.
230    ///
231    /// If this is set to [`None`], it will be left at the OpenTelemetry default, which is
232    /// currently 10 seconds unless overrided with a environment variable.
233    #[serde(default)]
234    #[serde(with = "humantime_serde")]
235    timeout: Option<Duration>,
236    // TODO: Once opentelemetry-otlp supports more than one protocol over HTTP, add a config option
237    // to choose protocol here.
238}
239impl_standard_builder! { OpentelemetryHttpExporterConfig: !Default }
240
241/// Configuration for the OpenTelemetry HTTP exporter.
242#[derive(Debug, Builder, Clone, Eq, PartialEq, Serialize, Deserialize)]
243#[builder(derive(Debug, Serialize, Deserialize))]
244#[builder(build_fn(error = "ConfigBuildError"))]
245#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
246#[cfg_attr(feature = "experimental-api", builder(public))]
247pub(crate) struct OpentelemetryFileExporterConfig {
248    /// The path to write the JSON file to.
249    path: CfgPath,
250    /// Configuration for how to batch writes.
251    // TODO: If we can figure out the right macro invocations, this shouldn't need to be a Option.
252    batch: Option<OpentelemetryBatchConfig>,
253}
254impl_standard_builder! { OpentelemetryFileExporterConfig: !Default }
255
256/// Configuration for the Opentelemetry batch exporting.
257///
258/// This is a copy of [`opentelemetry_sdk::trace::BatchConfig`].
259#[derive(Debug, Builder, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
260#[builder(derive(Debug, Serialize, Deserialize))]
261#[builder(build_fn(error = "ConfigBuildError"))]
262#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
263#[cfg_attr(feature = "experimental-api", builder(public))]
264pub(crate) struct OpentelemetryBatchConfig {
265    /// Maximum queue size. See [`opentelemetry_sdk::trace::BatchConfig::max_queue_size`].
266    #[builder(default)]
267    max_queue_size: Option<usize>,
268    /// Maximum export batch size. See [`opentelemetry_sdk::trace::BatchConfig::max_export_batch_size`].
269    #[builder(default)]
270    max_export_batch_size: Option<usize>,
271    /// Scheduled delay. See [`opentelemetry_sdk::trace::BatchConfig::scheduled_delay`].
272    #[builder(default)]
273    #[serde(with = "humantime_serde")]
274    scheduled_delay: Option<Duration>,
275}
276impl_standard_builder! { OpentelemetryBatchConfig }
277
278#[cfg(feature = "opentelemetry")]
279impl From<OpentelemetryBatchConfig> for opentelemetry_sdk::trace::BatchConfig {
280    fn from(config: OpentelemetryBatchConfig) -> opentelemetry_sdk::trace::BatchConfig {
281        let batch_config = opentelemetry_sdk::trace::BatchConfigBuilder::default();
282
283        let batch_config = if let Some(max_queue_size) = config.max_queue_size {
284            batch_config.with_max_queue_size(max_queue_size)
285        } else {
286            batch_config
287        };
288
289        let batch_config = if let Some(max_export_batch_size) = config.max_export_batch_size {
290            batch_config.with_max_export_batch_size(max_export_batch_size)
291        } else {
292            batch_config
293        };
294
295        let batch_config = if let Some(scheduled_delay) = config.scheduled_delay {
296            batch_config.with_scheduled_delay(scheduled_delay)
297        } else {
298            batch_config
299        };
300
301        batch_config.build()
302    }
303}
304
305/// Configuration for logging to the tokio console.
306#[derive(Debug, Builder, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
307#[builder(derive(Debug, Serialize, Deserialize))]
308#[builder(build_fn(error = "ConfigBuildError"))]
309#[cfg(feature = "tokio-console")]
310#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
311#[cfg_attr(feature = "experimental-api", builder(public))]
312pub(crate) struct TokioConsoleConfig {
313    /// If true, the tokio console subscriber should be enabled.
314    ///
315    /// This requires that tokio (and hence arti) is built with `--cfg tokio_unstable`
316    /// in RUSTFLAGS.
317    #[builder(default)]
318    enabled: bool,
319}
320
321/// As [`Targets::from_str`], but wrapped in an [`anyhow::Result`].
322//
323// (Note that we have to use `Targets`, not `EnvFilter`: see comment in
324// `setup_logging()`.)
325fn filt_from_str_verbose(s: &str, source: &str) -> Result<Targets> {
326    Targets::from_str(s).with_context(|| format!("in {}", source))
327}
328
329/// As filt_from_str_verbose, but treat an absent filter (or an empty string) as
330/// None.
331fn filt_from_opt_str(s: &Option<String>, source: &str) -> Result<Option<Targets>> {
332    Ok(match s {
333        Some(s) if !s.is_empty() => Some(filt_from_str_verbose(s, source)?),
334        _ => None,
335    })
336}
337
338/// Try to construct a tracing [`Layer`] for logging to stderr.
339fn console_layer<S>(config: &LoggingConfig, cli: Option<&str>) -> Result<impl Layer<S> + use<S>>
340where
341    S: Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>,
342{
343    let timer = time::new_formatter(config.time_granularity);
344    let filter = cli
345        .map(|s| filt_from_str_verbose(s, "--log-level command line parameter"))
346        .or_else(|| filt_from_opt_str(&config.console, "logging.console").transpose())
347        .unwrap_or_else(|| Ok(Targets::from_str("debug").expect("bad default")))?;
348    let use_color = std::io::stderr().is_terminal();
349    // We used to suppress safe-logging on the console, but we removed that
350    // feature: we cannot be certain that the console really is volatile. Even
351    // if isatty() returns true on the console, we can't be sure that the
352    // terminal isn't saving backlog to disk or something like that.
353    Ok(fmt::Layer::default()
354        // we apply custom field formatting so that error fields are listed last
355        .fmt_fields(fields::ErrorsLastFieldFormatter)
356        .with_ansi(use_color)
357        .with_timer(timer)
358        .with_writer(std::io::stderr) // we make this explicit, to match with use_color.
359        .with_filter(filter))
360}
361
362/// Try to construct a tracing [`Layer`] for logging to journald, if one is
363/// configured.
364#[cfg(feature = "journald")]
365fn journald_layer<S>(config: &LoggingConfig) -> Result<impl Layer<S>>
366where
367    S: Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>,
368{
369    if let Some(filter) = filt_from_opt_str(&config.journald, "logging.journald")? {
370        Ok(Some(tracing_journald::layer()?.with_filter(filter)))
371    } else {
372        // Fortunately, Option<Layer> implements Layer, so we can just return None here.
373        Ok(None)
374    }
375}
376
377/// Try to construct a tracing [`Layer`] for exporting spans via OpenTelemetry.
378///
379/// This doesn't allow for filtering, since most of our spans are exported at the trace level
380/// anyways, and filtering can easily be done when viewing the data.
381#[cfg(feature = "opentelemetry")]
382fn otel_layer<S>(config: &LoggingConfig, path_resolver: &CfgPathResolver) -> Result<impl Layer<S>>
383where
384    S: Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>,
385{
386    use opentelemetry::trace::TracerProvider;
387    use opentelemetry_otlp::WithExportConfig;
388
389    if config.opentelemetry.file.is_some() && config.opentelemetry.http.is_some() {
390        return Err(ConfigBuildError::Invalid {
391            field: "logging.opentelemetry".into(),
392            problem: "Only one OpenTelemetry exporter can be enabled at once.".into(),
393        }
394        .into());
395    }
396
397    let resource = opentelemetry_sdk::Resource::builder()
398        .with_service_name("arti")
399        .build();
400
401    let span_processor = if let Some(otel_file_config) = &config.opentelemetry.file {
402        let file = std::fs::File::options()
403            .create(true)
404            .append(true)
405            .open(otel_file_config.path.path(path_resolver)?)?;
406
407        let exporter = otlp_file_exporter::FileExporter::new(file, resource.clone());
408
409        opentelemetry_sdk::trace::BatchSpanProcessor::builder(exporter)
410            .with_batch_config(otel_file_config.batch.unwrap_or_default().into())
411            .build()
412    } else if let Some(otel_http_config) = &config.opentelemetry.http {
413        if otel_http_config.endpoint.starts_with("http://")
414            && !(otel_http_config.endpoint.starts_with("http://localhost")
415                || otel_http_config.endpoint.starts_with("http://127.0.0.1"))
416        {
417            return Err(ConfigBuildError::Invalid {
418                field: "logging.opentelemetry.http.endpoint".into(),
419                problem: "OpenTelemetry endpoint is set to HTTP on a non-localhost address! For security reasons, this is not supported.".into(),
420            }
421            .into());
422        }
423        let exporter = opentelemetry_otlp::SpanExporter::builder()
424            .with_http()
425            .with_endpoint(otel_http_config.endpoint.clone());
426
427        let exporter = if let Some(timeout) = otel_http_config.timeout {
428            exporter.with_timeout(timeout)
429        } else {
430            exporter
431        };
432
433        let exporter = exporter.build()?;
434
435        opentelemetry_sdk::trace::BatchSpanProcessor::builder(exporter)
436            .with_batch_config(otel_http_config.batch.unwrap_or_default().into())
437            .build()
438    } else {
439        return Ok(None);
440    };
441
442    let tracer_provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
443        .with_resource(resource.clone())
444        .with_span_processor(span_processor)
445        .build();
446
447    let tracer = tracer_provider.tracer("otel_file_tracer");
448
449    Ok(Some(tracing_opentelemetry::layer().with_tracer(tracer)))
450}
451
452/// Try to construct a non-blocking tracing [`Layer`] for writing data to an
453/// optionally rotating logfile.
454///
455/// On success, return that layer, along with a WorkerGuard that needs to be
456/// dropped when the program exits, to flush buffered messages.
457fn logfile_layer<S>(
458    config: &LogfileConfig,
459    granularity: std::time::Duration,
460    mistrust: &Mistrust,
461    path_resolver: &CfgPathResolver,
462) -> Result<(impl Layer<S> + Send + Sync + Sized + use<S>, WorkerGuard)>
463where
464    S: Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span> + Send + Sync,
465{
466    use tracing_appender::{
467        non_blocking,
468        rolling::{RollingFileAppender, Rotation},
469    };
470    let timer = time::new_formatter(granularity);
471
472    let filter = filt_from_str_verbose(&config.filter, "logging.files.filter")?;
473    let rotation = match config.rotate {
474        LogRotation::Daily => Rotation::DAILY,
475        LogRotation::Hourly => Rotation::HOURLY,
476        _ => Rotation::NEVER,
477    };
478    let path = config.path.path(path_resolver)?;
479
480    let directory = match path.parent() {
481        None => {
482            return Err(anyhow!(
483                "Logfile path \"{}\" did not have a parent directory",
484                path.display_lossy()
485            ));
486        }
487        Some(p) if p == Path::new("") => Path::new("."),
488        Some(d) => d,
489    };
490    mistrust.make_directory(directory).with_context(|| {
491        format!(
492            "Unable to create parent directory for logfile \"{}\"",
493            path.display_lossy()
494        )
495    })?;
496    let fname = path
497        .file_name()
498        .ok_or_else(|| anyhow!("No path for log file"))
499        .map(Path::new)?;
500
501    let appender = RollingFileAppender::new(rotation, directory, fname);
502    let (nonblocking, guard) = non_blocking(appender);
503    let layer = fmt::layer()
504        // we apply custom field formatting so that error fields are listed last
505        .fmt_fields(fields::ErrorsLastFieldFormatter)
506        .with_ansi(false)
507        .with_writer(nonblocking)
508        .with_timer(timer)
509        .with_filter(filter);
510    Ok((layer, guard))
511}
512
513/// Try to construct a tracing [`Layer`] for all of the configured logfiles.
514///
515/// On success, return that layer along with a list of [`WorkerGuard`]s that
516/// need to be dropped when the program exits.
517fn logfile_layers<S>(
518    config: &LoggingConfig,
519    mistrust: &Mistrust,
520    path_resolver: &CfgPathResolver,
521) -> Result<(impl Layer<S> + use<S>, Vec<WorkerGuard>)>
522where
523    S: Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span> + Send + Sync,
524{
525    let mut guards = Vec::new();
526    if config.files.is_empty() {
527        // As above, we have Option<Layer> implements Layer, so we can return
528        // None in this case.
529        return Ok((None, guards));
530    }
531
532    let (layer, guard) = logfile_layer(
533        &config.files[0],
534        config.time_granularity,
535        mistrust,
536        path_resolver,
537    )?;
538    guards.push(guard);
539
540    // We have to use a dyn pointer here so we can build up linked list of
541    // arbitrary depth.
542    let mut layer: Box<dyn Layer<S> + Send + Sync + 'static> = Box::new(layer);
543
544    for logfile in &config.files[1..] {
545        let (new_layer, guard) =
546            logfile_layer(logfile, config.time_granularity, mistrust, path_resolver)?;
547        layer = Box::new(layer.and_then(new_layer));
548        guards.push(guard);
549    }
550
551    Ok((Some(layer), guards))
552}
553
554/// Configure a panic handler to send everything to tracing, in addition to our
555/// default panic behavior.
556fn install_panic_handler() {
557    // TODO library support: There's a library called `tracing-panic` that
558    // provides a hook we could use instead, but that doesn't have backtrace
559    // support.  We should consider using it if it gets backtrace support in the
560    // future.  We should also keep an eye on `tracing` to see if it learns how
561    // to do this for us.
562    let default_handler = std::panic::take_hook();
563    std::panic::set_hook(Box::new(move |panic_info| {
564        // Note that if we were ever to _not_ call this handler,
565        // we would want to abort on nested panics and !can_unwind cases.
566        default_handler(panic_info);
567
568        // This statement is copied from stdlib.
569        let msg = match panic_info.payload().downcast_ref::<&'static str>() {
570            Some(s) => *s,
571            None => match panic_info.payload().downcast_ref::<String>() {
572                Some(s) => &s[..],
573                None => "Box<dyn Any>",
574            },
575        };
576
577        let backtrace = std::backtrace::Backtrace::force_capture();
578        match panic_info.location() {
579            Some(location) => error!("Panic at {}: {}\n{}", location, msg, backtrace),
580            None => error!("Panic at ???: {}\n{}", msg, backtrace),
581        };
582    }));
583}
584
585/// Opaque structure that gets dropped when the program is shutting down,
586/// after logs are no longer needed.  The `Drop` impl flushes buffered messages.
587#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
588pub(crate) struct LogGuards {
589    /// The actual list of guards we're returning.
590    #[allow(unused)]
591    guards: Vec<WorkerGuard>,
592
593    /// A safelog guard, for use if we have decided to disable safe logging.
594    #[allow(unused)]
595    safelog_guard: Option<safelog::Guard>,
596}
597
598/// Set up logging.
599///
600/// Note that the returned LogGuard must be dropped precisely when the program
601/// quits; they're used to ensure that all the log messages are flushed.
602#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
603#[cfg_attr(docsrs, doc(cfg(feature = "experimental-api")))]
604pub(crate) fn setup_logging(
605    config: &LoggingConfig,
606    mistrust: &Mistrust,
607    path_resolver: &CfgPathResolver,
608    cli: Option<&str>,
609) -> Result<LogGuards> {
610    // Important: We have to make sure that the individual layers we add here
611    // are not filters themselves.  That means, for example, that we can't add
612    // an `EnvFilter` layer unless we want it to apply globally to _all_ layers.
613    //
614    // For a bit of discussion on the difference between per-layer filters and filters
615    // that apply to the entire registry, see
616    // https://docs.rs/tracing-subscriber/0.3.5/tracing_subscriber/layer/index.html#global-filtering
617
618    let registry = registry().with(console_layer(config, cli)?);
619
620    #[cfg(feature = "journald")]
621    let registry = registry.with(journald_layer(config)?);
622
623    #[cfg(feature = "opentelemetry")]
624    let registry = registry.with(otel_layer(config, path_resolver)?);
625
626    #[cfg(feature = "tokio-console")]
627    let registry = {
628        // Note 1: We can't enable console_subscriber unconditionally when the `tokio-console`
629        // feature is enabled, since it panics unless tokio is built with  `--cfg tokio_unstable`,
630        // but we want arti to work with --all-features without any special --cfg.
631        //
632        // Note 2: We have to use an `Option` here, since the type of the registry changes
633        // with whatever you add to it.
634        let tokio_layer = if config.tokio_console.enabled {
635            Some(console_subscriber::spawn())
636        } else {
637            None
638        };
639        registry.with(tokio_layer)
640    };
641
642    let (layer, guards) = logfile_layers(config, mistrust, path_resolver)?;
643    let registry = registry.with(layer);
644
645    registry.init();
646
647    let safelog_guard = if config.log_sensitive_information {
648        match safelog::disable_safe_logging() {
649            Ok(guard) => Some(guard),
650            Err(e) => {
651                // We don't need to propagate this error; it isn't the end of
652                // the world if we were unable to disable safe logging.
653                warn_report!(e, "Unable to disable safe logging");
654                None
655            }
656        }
657    } else {
658        None
659    };
660
661    install_panic_handler();
662
663    Ok(LogGuards {
664        guards,
665        safelog_guard,
666    })
667}