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