1
//! Configure tracing subscribers for Arti
2

            
3
use anyhow::{Context, Result, anyhow};
4
use derive_deftly::Deftly;
5
use fs_mistrust::Mistrust;
6
use serde::{Deserialize, Serialize};
7
use std::io::IsTerminal as _;
8
use std::path::Path;
9
use std::str::FromStr;
10
use std::time::Duration;
11
use tor_basic_utils::PathExt as _;
12
use tor_config::ConfigBuildError;
13
use tor_config::derive::prelude::*;
14
use tor_config_path::{CfgPath, CfgPathResolver};
15
use tor_error::warn_report;
16
use tracing::{Subscriber, error};
17
use tracing_appender::non_blocking::WorkerGuard;
18
use tracing_subscriber::layer::SubscriberExt;
19
use tracing_subscriber::prelude::*;
20
use tracing_subscriber::{Layer, filter::Targets, fmt, registry};
21

            
22
mod fields;
23
#[cfg(feature = "opentelemetry")]
24
mod otlp_file_exporter;
25
mod 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")))]
32
pub(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
    /// Configuration for logging spans with OpenTelemetry.
51
    #[deftly(tor_config(
52
        sub_builder,
53
        cfg = r#" feature = "opentelemetry" "#,
54
        cfg_desc = "with opentelemetry support"
55
    ))]
56
    opentelemetry: OpentelemetryConfig,
57

            
58
    /// Configuration for passing information to tokio-console.
59
    #[deftly(tor_config(
60
        sub_builder,
61
        cfg = r#" feature = "tokio-console" "#,
62
        cfg_desc = "with tokio-console support"
63
    ))]
64
    tokio_console: TokioConsoleConfig,
65

            
66
    /// Configuration for one or more logfiles.
67
    ///
68
    /// The default is not to log to any files.
69
    #[deftly(tor_config(list(element(build), listtype = "LogfileList"), default = "vec![]"))]
70
    files: Vec<LogfileConfig>,
71

            
72
    /// If set to true, we disable safe logging on _all logs_, and store
73
    /// potentially sensitive information at level `info` or higher.
74
    ///
75
    /// This can be useful for debugging, but it increases the value of your
76
    /// logs to an attacker.  Do not turn this on in production unless you have
77
    /// a good log rotation mechanism.
78
    //
79
    // TODO: Eventually we might want to make this more complex, and add a
80
    // per-log mechanism to turn off unsafe logging. Alternatively, we might do
81
    // that by extending the filter syntax implemented by `tracing` to have an
82
    // "unsafe" flag on particular lines.
83
    #[deftly(tor_config(default))]
84
    log_sensitive_information: bool,
85

            
86
    /// If set to true, promote Tor protocol-violation reports to warning level.
87
    #[deftly(tor_config(default))]
88
    protocol_warnings: bool,
89

            
90
    /// An approximate granularity with which log times should be displayed.
91
    ///
92
    /// This value controls every log time that arti outputs; it doesn't have any
93
    /// effect on times written by other logging programs like `journald`.
94
    ///
95
    /// We may round this value up for convenience: For example, if you say
96
    /// "2.5s", we may treat it as if you had said "3s."
97
    ///
98
    /// The default is "1s", or one second.
99
    #[deftly(tor_config(default = "std::time::Duration::new(1,0)"))]
100
    time_granularity: std::time::Duration,
101
}
102

            
103
/// Return a default tracing filter value for `logging.console`.
104
#[allow(clippy::unnecessary_wraps)]
105
70
fn default_console_filter() -> Option<String> {
106
70
    Some("info".to_owned())
107
70
}
108

            
109
/// Configuration information for an (optionally rotating) logfile.
110
#[derive(Debug, Deftly, Clone, Eq, PartialEq)]
111
#[derive_deftly(TorConfig)]
112
#[deftly(tor_config(no_default_trait))]
113
#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
114
#[cfg_attr(feature = "experimental-api", deftly(tor_config(vis = "pub")))]
115
pub(crate) struct LogfileConfig {
116
    /// How often to rotate the file?
117
    #[deftly(tor_config(default))]
118
    rotate: LogRotation,
119
    /// Where to write the files?
120
    #[deftly(tor_config(no_default))]
121
    path: CfgPath,
122
    /// Filter to apply before writing
123
    #[deftly(tor_config(no_default))]
124
    filter: String,
125
}
126

            
127
/// How often to rotate a log file
128
#[derive(Debug, Default, Clone, Serialize, Deserialize, Copy, Eq, PartialEq)]
129
#[non_exhaustive]
130
#[serde(rename_all = "lowercase")]
131
#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
132
pub(crate) enum LogRotation {
133
    /// Rotate logs daily
134
    Daily,
135
    /// Rotate logs hourly
136
    Hourly,
137
    /// Never rotate the log
138
    #[default]
139
    Never,
140
}
141

            
142
/// Configuration for exporting spans with OpenTelemetry.
143
#[derive(Debug, Deftly, Clone, Eq, PartialEq, Serialize, Deserialize)]
144
#[derive_deftly(TorConfig)]
145
#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
146
#[cfg_attr(feature = "experimental-api", deftly(tor_config(vis = "pub")))]
147
pub(crate) struct OpentelemetryConfig {
148
    /// Write spans to a file in OTLP JSON format.
149
    #[deftly(tor_config(default))]
150
    file: Option<OpentelemetryFileExporterConfig>,
151
    /// Export spans via HTTP.
152
    #[deftly(tor_config(default))]
153
    http: Option<OpentelemetryHttpExporterConfig>,
154
}
155

            
156
/// Configuration for the OpenTelemetry HTTP exporter.
157
#[derive(Debug, Deftly, Clone, Eq, PartialEq, Serialize, Deserialize)]
158
#[derive_deftly(TorConfig)]
159
#[deftly(tor_config(no_default_trait))]
160
#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
161
#[cfg_attr(feature = "experimental-api", deftly(tor_config(vis = "pub")))]
162
pub(crate) struct OpentelemetryHttpExporterConfig {
163
    /// HTTP(S) endpoint to send spans to.
164
    ///
165
    /// For Jaeger, this should be something like: `http://localhost:4318/v1/traces`
166
    #[deftly(tor_config(no_default))]
167
    endpoint: String,
168
    /// Configuration for how to batch exports.
169
    #[deftly(tor_config(sub_builder))]
170
    batch: OpentelemetryBatchConfig,
171
    /// Timeout for sending data.
172
    ///
173
    /// If this is set to [`None`], it will be left at the OpenTelemetry default, which is
174
    /// currently 10 seconds unless overridden with a environment variable.
175
    //
176
    // NOTE: there is no way to actually override this with None, so we have to say
177
    // "no magic" to tell dd(TorConfig) not to worry about that.
178
    #[deftly(tor_config(no_magic, default))]
179
    timeout: Option<Duration>,
180
    // TODO: Once opentelemetry-otlp supports more than one protocol over HTTP, add a config option
181
    // to choose protocol here.
182
}
183

            
184
/// Configuration for the OpenTelemetry HTTP exporter.
185
#[derive(Debug, Deftly, Clone, Eq, PartialEq, Serialize, Deserialize)]
186
#[derive_deftly(TorConfig)]
187
#[deftly(tor_config(no_default_trait))]
188
#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
189
#[cfg_attr(feature = "experimental-api", deftly(tor_config(vis = "pub")))]
190
pub(crate) struct OpentelemetryFileExporterConfig {
191
    /// The path to write the JSON file to.
192
    #[deftly(tor_config(no_default))]
193
    path: CfgPath,
194
    /// Configuration for how to batch writes.
195
    #[deftly(tor_config(sub_builder))]
196
    batch: OpentelemetryBatchConfig,
197
}
198

            
199
/// Configuration for the Opentelemetry batch exporting.
200
///
201
/// This is a copy of [`opentelemetry_sdk::trace::BatchConfig`].
202
#[derive(Debug, Deftly, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
203
#[derive_deftly(TorConfig)]
204
#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
205
#[cfg_attr(feature = "experimental-api", deftly(tor_config(vis = "pub")))]
206
pub(crate) struct OpentelemetryBatchConfig {
207
    /// Maximum queue size. See [`opentelemetry_sdk::trace::BatchConfig::max_queue_size`].
208
    #[deftly(tor_config(default))]
209
    max_queue_size: Option<usize>,
210
    /// Maximum export batch size. See [`opentelemetry_sdk::trace::BatchConfig::max_export_batch_size`].
211
    #[deftly(tor_config(default))]
212
    max_export_batch_size: Option<usize>,
213
    /// Scheduled delay. See [`opentelemetry_sdk::trace::BatchConfig::scheduled_delay`].
214
    #[deftly(tor_config(no_magic, default))]
215
    scheduled_delay: Option<Duration>,
216
}
217

            
218
#[cfg(feature = "opentelemetry")]
219
impl From<OpentelemetryBatchConfig> for opentelemetry_sdk::trace::BatchConfig {
220
    fn from(config: OpentelemetryBatchConfig) -> opentelemetry_sdk::trace::BatchConfig {
221
        let batch_config = opentelemetry_sdk::trace::BatchConfigBuilder::default();
222

            
223
        let batch_config = if let Some(max_queue_size) = config.max_queue_size {
224
            batch_config.with_max_queue_size(max_queue_size)
225
        } else {
226
            batch_config
227
        };
228

            
229
        let batch_config = if let Some(max_export_batch_size) = config.max_export_batch_size {
230
            batch_config.with_max_export_batch_size(max_export_batch_size)
231
        } else {
232
            batch_config
233
        };
234

            
235
        let batch_config = if let Some(scheduled_delay) = config.scheduled_delay {
236
            batch_config.with_scheduled_delay(scheduled_delay)
237
        } else {
238
            batch_config
239
        };
240

            
241
        batch_config.build()
242
    }
243
}
244

            
245
/// Configuration for logging to the tokio console.
246
#[derive(Debug, Deftly, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
247
#[derive_deftly(TorConfig)]
248
#[cfg(feature = "tokio-console")]
249
#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
250
#[cfg_attr(feature = "experimental-api", deftly(tor_config(vis = "pub")))]
251
pub(crate) struct TokioConsoleConfig {
252
    /// If true, the tokio console subscriber should be enabled.
253
    ///
254
    /// This requires that tokio (and hence arti) is built with `--cfg tokio_unstable`
255
    /// in RUSTFLAGS.
256
    #[deftly(tor_config(default))]
257
    enabled: bool,
258
}
259

            
260
/// Placeholder for unused tokio console config.
261
#[cfg(not(feature = "tokio-console"))]
262
type TokioConsoleConfig = ();
263

            
264
/// As [`Targets::from_str`], but wrapped in an [`anyhow::Result`].
265
//
266
// (Note that we have to use `Targets`, not `EnvFilter`: see comment in
267
// `setup_logging()`.)
268
306
fn filt_from_str_verbose(s: &str, source: &str) -> Result<Targets> {
269
306
    Targets::from_str(s).with_context(|| format!("in {}", source))
270
306
}
271

            
272
/// As filt_from_str_verbose, but treat an absent filter (or an empty string) as
273
/// None.
274
612
fn filt_from_opt_str(s: &Option<String>, source: &str) -> Result<Option<Targets>> {
275
306
    Ok(match s {
276
306
        Some(s) if !s.is_empty() => Some(filt_from_str_verbose(s, source)?),
277
306
        _ => None,
278
    })
279
612
}
280

            
281
/// Try to construct a tracing [`Layer`] for logging to stderr.
282
306
fn console_layer<S>(config: &LoggingConfig, cli: Option<&str>) -> Result<impl Layer<S> + use<S>>
283
306
where
284
306
    S: Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>,
285
{
286
306
    let timer = time::new_formatter(config.time_granularity);
287
306
    let filter = cli
288
306
        .map(|s| filt_from_str_verbose(s, "--log-level command line parameter"))
289
306
        .or_else(|| filt_from_opt_str(&config.console, "logging.console").transpose())
290
306
        .unwrap_or_else(|| Ok(Targets::from_str("debug").expect("bad default")))?;
291
306
    let use_color = std::io::stderr().is_terminal();
292
    // We used to suppress safe-logging on the console, but we removed that
293
    // feature: we cannot be certain that the console really is volatile. Even
294
    // if isatty() returns true on the console, we can't be sure that the
295
    // terminal isn't saving backlog to disk or something like that.
296
306
    Ok(fmt::Layer::default()
297
306
        // we apply custom field formatting so that error fields are listed last
298
306
        .fmt_fields(fields::ErrorsLastFieldFormatter)
299
306
        .with_ansi(use_color)
300
306
        .with_timer(timer)
301
306
        .with_writer(std::io::stderr) // we make this explicit, to match with use_color.
302
306
        .with_filter(filter))
303
306
}
304

            
305
/// Try to construct a tracing [`Layer`] for logging to journald, if one is
306
/// configured.
307
#[cfg(feature = "journald")]
308
306
fn journald_layer<S>(config: &LoggingConfig) -> Result<impl Layer<S>>
309
306
where
310
306
    S: Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>,
311
{
312
306
    if let Some(filter) = filt_from_opt_str(&config.journald, "logging.journald")? {
313
        Ok(Some(tracing_journald::layer()?.with_filter(filter)))
314
    } else {
315
        // Fortunately, Option<Layer> implements Layer, so we can just return None here.
316
306
        Ok(None)
317
    }
318
306
}
319

            
320
/// Try to construct a tracing [`Layer`] for exporting spans via OpenTelemetry.
321
///
322
/// This doesn't allow for filtering, since most of our spans are exported at the trace level
323
/// anyways, and filtering can easily be done when viewing the data.
324
#[cfg(feature = "opentelemetry")]
325
306
fn otel_layer<S>(config: &LoggingConfig, path_resolver: &CfgPathResolver) -> Result<impl Layer<S>>
326
306
where
327
306
    S: Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>,
328
{
329
    use opentelemetry::trace::TracerProvider;
330
    use opentelemetry_otlp::WithExportConfig;
331

            
332
306
    if config.opentelemetry.file.is_some() && config.opentelemetry.http.is_some() {
333
        return Err(ConfigBuildError::Invalid {
334
            field: "logging.opentelemetry".into(),
335
            problem: "Only one OpenTelemetry exporter can be enabled at once.".into(),
336
        }
337
        .into());
338
306
    }
339

            
340
306
    let resource = opentelemetry_sdk::Resource::builder()
341
306
        .with_service_name("arti")
342
306
        .build();
343

            
344
306
    let span_processor = if let Some(otel_file_config) = &config.opentelemetry.file {
345
        let file = std::fs::File::options()
346
            .create(true)
347
            .append(true)
348
            .open(otel_file_config.path.path(path_resolver)?)?;
349

            
350
        let exporter = otlp_file_exporter::FileExporter::new(file, resource.clone());
351

            
352
        opentelemetry_sdk::trace::BatchSpanProcessor::builder(exporter)
353
            .with_batch_config(otel_file_config.batch.into())
354
            .build()
355
306
    } else if let Some(otel_http_config) = &config.opentelemetry.http {
356
        if otel_http_config.endpoint.starts_with("http://")
357
            && !(otel_http_config.endpoint.starts_with("http://localhost")
358
                || otel_http_config.endpoint.starts_with("http://127.0.0.1"))
359
        {
360
            return Err(ConfigBuildError::Invalid {
361
                field: "logging.opentelemetry.http.endpoint".into(),
362
                problem: "OpenTelemetry endpoint is set to HTTP on a non-localhost address! For security reasons, this is not supported.".into(),
363
            }
364
            .into());
365
        }
366
        let exporter = opentelemetry_otlp::SpanExporter::builder()
367
            .with_http()
368
            .with_endpoint(otel_http_config.endpoint.clone());
369

            
370
        let exporter = if let Some(timeout) = otel_http_config.timeout {
371
            exporter.with_timeout(timeout)
372
        } else {
373
            exporter
374
        };
375

            
376
        let exporter = exporter.build()?;
377

            
378
        opentelemetry_sdk::trace::BatchSpanProcessor::builder(exporter)
379
            .with_batch_config(otel_http_config.batch.into())
380
            .build()
381
    } else {
382
306
        return Ok(None);
383
    };
384

            
385
    let tracer_provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
386
        .with_resource(resource.clone())
387
        .with_span_processor(span_processor)
388
        .build();
389

            
390
    let tracer = tracer_provider.tracer("otel_file_tracer");
391

            
392
    Ok(Some(tracing_opentelemetry::layer().with_tracer(tracer)))
393
306
}
394

            
395
/// Try to construct a non-blocking tracing [`Layer`] for writing data to an
396
/// optionally rotating logfile.
397
///
398
/// On success, return that layer, along with a WorkerGuard that needs to be
399
/// dropped when the program exits, to flush buffered messages.
400
fn logfile_layer<S>(
401
    config: &LogfileConfig,
402
    granularity: std::time::Duration,
403
    mistrust: &Mistrust,
404
    path_resolver: &CfgPathResolver,
405
) -> Result<(impl Layer<S> + Send + Sync + Sized + use<S>, WorkerGuard)>
406
where
407
    S: Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span> + Send + Sync,
408
{
409
    use tracing_appender::{
410
        non_blocking,
411
        rolling::{RollingFileAppender, Rotation},
412
    };
413
    let timer = time::new_formatter(granularity);
414

            
415
    let filter = filt_from_str_verbose(&config.filter, "logging.files.filter")?;
416
    let rotation = match config.rotate {
417
        LogRotation::Daily => Rotation::DAILY,
418
        LogRotation::Hourly => Rotation::HOURLY,
419
        _ => Rotation::NEVER,
420
    };
421
    let path = config.path.path(path_resolver)?;
422

            
423
    let directory = match path.parent() {
424
        None => {
425
            return Err(anyhow!(
426
                "Logfile path \"{}\" did not have a parent directory",
427
                path.display_lossy()
428
            ));
429
        }
430
        Some(p) if p == Path::new("") => Path::new("."),
431
        Some(d) => d,
432
    };
433
    mistrust.make_directory(directory).with_context(|| {
434
        format!(
435
            "Unable to create parent directory for logfile \"{}\"",
436
            path.display_lossy()
437
        )
438
    })?;
439
    let fname = path
440
        .file_name()
441
        .ok_or_else(|| anyhow!("No path for log file"))
442
        .map(Path::new)?;
443

            
444
    let appender = RollingFileAppender::new(rotation, directory, fname);
445
    let (nonblocking, guard) = non_blocking(appender);
446
    let layer = fmt::layer()
447
        // we apply custom field formatting so that error fields are listed last
448
        .fmt_fields(fields::ErrorsLastFieldFormatter)
449
        .with_ansi(false)
450
        .with_writer(nonblocking)
451
        .with_timer(timer)
452
        .with_filter(filter);
453
    Ok((layer, guard))
454
}
455

            
456
/// Try to construct a tracing [`Layer`] for all of the configured logfiles.
457
///
458
/// On success, return that layer along with a list of [`WorkerGuard`]s that
459
/// need to be dropped when the program exits.
460
306
fn logfile_layers<S>(
461
306
    config: &LoggingConfig,
462
306
    mistrust: &Mistrust,
463
306
    path_resolver: &CfgPathResolver,
464
306
) -> Result<(impl Layer<S> + use<S>, Vec<WorkerGuard>)>
465
306
where
466
306
    S: Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span> + Send + Sync,
467
{
468
306
    let mut guards = Vec::new();
469
306
    if config.files.is_empty() {
470
        // As above, we have Option<Layer> implements Layer, so we can return
471
        // None in this case.
472
306
        return Ok((None, guards));
473
    }
474

            
475
    let (layer, guard) = logfile_layer(
476
        &config.files[0],
477
        config.time_granularity,
478
        mistrust,
479
        path_resolver,
480
    )?;
481
    guards.push(guard);
482

            
483
    // We have to use a dyn pointer here so we can build up linked list of
484
    // arbitrary depth.
485
    let mut layer: Box<dyn Layer<S> + Send + Sync + 'static> = Box::new(layer);
486

            
487
    for logfile in &config.files[1..] {
488
        let (new_layer, guard) =
489
            logfile_layer(logfile, config.time_granularity, mistrust, path_resolver)?;
490
        layer = Box::new(layer.and_then(new_layer));
491
        guards.push(guard);
492
    }
493

            
494
    Ok((Some(layer), guards))
495
306
}
496

            
497
/// Configure a panic handler to send everything to tracing, in addition to our
498
/// default panic behavior.
499
306
fn install_panic_handler() {
500
    // TODO library support: There's a library called `tracing-panic` that
501
    // provides a hook we could use instead, but that doesn't have backtrace
502
    // support.  We should consider using it if it gets backtrace support in the
503
    // future.  We should also keep an eye on `tracing` to see if it learns how
504
    // to do this for us.
505
306
    let default_handler = std::panic::take_hook();
506
306
    std::panic::set_hook(Box::new(move |panic_info| {
507
        // Note that if we were ever to _not_ call this handler,
508
        // we would want to abort on nested panics and !can_unwind cases.
509
        default_handler(panic_info);
510

            
511
        // This statement is copied from stdlib.
512
        let msg = match panic_info.payload().downcast_ref::<&'static str>() {
513
            Some(s) => *s,
514
            None => match panic_info.payload().downcast_ref::<String>() {
515
                Some(s) => &s[..],
516
                None => "Box<dyn Any>",
517
            },
518
        };
519

            
520
        let backtrace = std::backtrace::Backtrace::force_capture();
521
        match panic_info.location() {
522
            Some(location) => error!("Panic at {}: {}\n{}", location, msg, backtrace),
523
            None => error!("Panic at ???: {}\n{}", msg, backtrace),
524
        };
525
    }));
526
306
}
527

            
528
/// Opaque structure that gets dropped when the program is shutting down,
529
/// after logs are no longer needed.  The `Drop` impl flushes buffered messages.
530
#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
531
pub(crate) struct LogGuards {
532
    /// The actual list of guards we're returning.
533
    #[allow(unused)]
534
    guards: Vec<WorkerGuard>,
535

            
536
    /// A safelog guard, for use if we have decided to disable safe logging.
537
    #[allow(unused)]
538
    safelog_guard: Option<safelog::Guard>,
539
}
540

            
541
/// Set up logging.
542
///
543
/// Note that the returned LogGuard must be dropped precisely when the program
544
/// quits; they're used to ensure that all the log messages are flushed.
545
306
#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
546
306
#[cfg_attr(docsrs, doc(cfg(feature = "experimental-api")))]
547
306
pub(crate) fn setup_logging(
548
306
    config: &LoggingConfig,
549
306
    mistrust: &Mistrust,
550
306
    path_resolver: &CfgPathResolver,
551
306
    cli: Option<&str>,
552
306
) -> Result<LogGuards> {
553
    // Important: We have to make sure that the individual layers we add here
554
    // are not filters themselves.  That means, for example, that we can't add
555
    // an `EnvFilter` layer unless we want it to apply globally to _all_ layers.
556
    //
557
    // For a bit of discussion on the difference between per-layer filters and filters
558
    // that apply to the entire registry, see
559
    // https://docs.rs/tracing-subscriber/0.3.5/tracing_subscriber/layer/index.html#global-filtering
560

            
561
306
    let registry = registry().with(console_layer(config, cli)?);
562

            
563
    #[cfg(feature = "journald")]
564
306
    let registry = registry.with(journald_layer(config)?);
565

            
566
    #[cfg(feature = "opentelemetry")]
567
306
    let registry = registry.with(otel_layer(config, path_resolver)?);
568

            
569
    #[cfg(feature = "tokio-console")]
570
306
    let registry = {
571
        // Note 1: We can't enable console_subscriber unconditionally when the `tokio-console`
572
        // feature is enabled, since it panics unless tokio is built with  `--cfg tokio_unstable`,
573
        // but we want arti to work with --all-features without any special --cfg.
574
        //
575
        // Note 2: We have to use an `Option` here, since the type of the registry changes
576
        // with whatever you add to it.
577
306
        let tokio_layer = if config.tokio_console.enabled {
578
            Some(console_subscriber::spawn())
579
        } else {
580
306
            None
581
        };
582
306
        registry.with(tokio_layer)
583
    };
584

            
585
306
    let (layer, guards) = logfile_layers(config, mistrust, path_resolver)?;
586
306
    let registry = registry.with(layer);
587

            
588
306
    registry.init();
589

            
590
306
    let safelog_guard = if config.log_sensitive_information {
591
        match safelog::disable_safe_logging() {
592
            Ok(guard) => Some(guard),
593
            Err(e) => {
594
                // We don't need to propagate this error; it isn't the end of
595
                // the world if we were unable to disable safe logging.
596
                warn_report!(e, "Unable to disable safe logging");
597
                None
598
            }
599
        }
600
    } else {
601
306
        None
602
    };
603

            
604
306
    let mode = if config.protocol_warnings {
605
        tor_error::tracing::ProtocolWarningMode::Warn
606
    } else {
607
306
        tor_error::tracing::ProtocolWarningMode::Off
608
    };
609
306
    tor_error::tracing::set_protocol_warning_mode(mode);
610

            
611
306
    install_panic_handler();
612

            
613
306
    Ok(LogGuards {
614
306
        guards,
615
306
        safelog_guard,
616
306
    })
617
306
}