Skip to main content

arti/
logging.rs

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