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
    /// 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)]
115
70
fn default_console_filter() -> Option<String> {
116
70
    Some("info".to_owned())
117
70
}
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")))]
125
pub(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))]
142
pub(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")))]
157
pub(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")))]
172
pub(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")))]
200
pub(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")))]
216
pub(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")]
229
impl 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")))]
261
pub(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"))]
272
type 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()`.)
278
330
fn filt_from_str_verbose(s: &str, source: &str) -> Result<Targets> {
279
330
    Targets::from_str(s).with_context(|| format!("in {}", source))
280
330
}
281

            
282
/// As filt_from_str_verbose, but treat an absent filter (or an empty string) as
283
/// None.
284
990
fn filt_from_opt_str(s: &Option<String>, source: &str) -> Result<Option<Targets>> {
285
660
    Ok(match s {
286
660
        Some(s) if !s.is_empty() => Some(filt_from_str_verbose(s, source)?),
287
660
        _ => None,
288
    })
289
990
}
290

            
291
/// Try to construct a tracing [`Layer`] for logging to stderr.
292
330
fn console_layer<S>(config: &LoggingConfig, cli: Option<&str>) -> Result<impl Layer<S> + use<S>>
293
330
where
294
330
    S: Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>,
295
{
296
330
    let timer = time::new_formatter(config.time_granularity);
297
330
    let filter = cli
298
330
        .map(|s| filt_from_str_verbose(s, "--log-level command line parameter"))
299
330
        .or_else(|| filt_from_opt_str(&config.console, "logging.console").transpose())
300
330
        .unwrap_or_else(|| Ok(Targets::from_str("debug").expect("bad default")))?;
301
330
    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
330
    Ok(fmt::Layer::default()
307
330
        // we apply custom field formatting so that error fields are listed last
308
330
        .fmt_fields(fields::ErrorsLastFieldFormatter)
309
330
        .with_ansi(use_color)
310
330
        .with_timer(timer)
311
330
        .with_writer(std::io::stderr) // we make this explicit, to match with use_color.
312
330
        .with_filter(filter))
313
330
}
314

            
315
/// Try to construct a tracing [`Layer`] for logging to journald, if one is
316
/// configured.
317
#[cfg(feature = "journald")]
318
330
fn journald_layer<S>(config: &LoggingConfig) -> Result<impl Layer<S>>
319
330
where
320
330
    S: Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>,
321
{
322
330
    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
330
        Ok(None)
327
    }
328
330
}
329

            
330
/// Try to construct a tracing [`Layer`] for logging to syslog, if one is
331
/// configured.
332
#[cfg(all(feature = "syslog", unix))]
333
330
fn syslog_layer<S>(config: &LoggingConfig) -> Result<impl Layer<S>>
334
330
where
335
330
    S: Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>,
336
{
337
    use syslog_tracing::{Facility, Options, Syslog};
338

            
339
330
    let identity = c"arti";
340

            
341
330
    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
330
        Ok(None)
360
    }
361
330
}
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")]
368
330
fn otel_layer<S>(config: &LoggingConfig, path_resolver: &CfgPathResolver) -> Result<impl Layer<S>>
369
330
where
370
330
    S: Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>,
371
{
372
    use opentelemetry::trace::TracerProvider;
373
    use opentelemetry_otlp::WithExportConfig;
374

            
375
330
    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
330
    }
382

            
383
330
    let resource = opentelemetry_sdk::Resource::builder()
384
330
        .with_service_name("arti")
385
330
        .build();
386

            
387
330
    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
330
    } 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
330
        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
330
}
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.
443
fn 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)>
449
where
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.
503
330
fn logfile_layers<S>(
504
330
    config: &LoggingConfig,
505
330
    mistrust: &Mistrust,
506
330
    path_resolver: &CfgPathResolver,
507
330
) -> Result<(impl Layer<S> + use<S>, Vec<WorkerGuard>)>
508
330
where
509
330
    S: Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span> + Send + Sync,
510
{
511
330
    let mut guards = Vec::new();
512
330
    if config.files.is_empty() {
513
        // As above, we have Option<Layer> implements Layer, so we can return
514
        // None in this case.
515
330
        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
330
}
539

            
540
/// Configure a panic handler to send everything to tracing, in addition to our
541
/// default panic behavior.
542
#[allow(clippy::string_slice)] // TODO
543
330
fn install_panic_handler() {
544
    // TODO library support: There's a library called `tracing-panic` that
545
    // provides a hook we could use instead, but that doesn't have backtrace
546
    // support.  We should consider using it if it gets backtrace support in the
547
    // future.  We should also keep an eye on `tracing` to see if it learns how
548
    // to do this for us.
549
330
    let default_handler = std::panic::take_hook();
550
330
    std::panic::set_hook(Box::new(move |panic_info| {
551
        // Note that if we were ever to _not_ call this handler,
552
        // we would want to abort on nested panics and !can_unwind cases.
553
        default_handler(panic_info);
554

            
555
        // This statement is copied from stdlib.
556
        let msg = match panic_info.payload().downcast_ref::<&'static str>() {
557
            Some(s) => *s,
558
            None => match panic_info.payload().downcast_ref::<String>() {
559
                Some(s) => &s[..],
560
                None => "Box<dyn Any>",
561
            },
562
        };
563

            
564
        let backtrace = std::backtrace::Backtrace::force_capture();
565
        match panic_info.location() {
566
            Some(location) => error!("Panic at {}: {}\n{}", location, msg, backtrace),
567
            None => error!("Panic at ???: {}\n{}", msg, backtrace),
568
        };
569
    }));
570
330
}
571

            
572
/// Opaque structure that gets dropped when the program is shutting down,
573
/// after logs are no longer needed.  The `Drop` impl flushes buffered messages.
574
#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
575
pub(crate) struct LogGuards {
576
    /// The actual list of guards we're returning.
577
    #[allow(unused)]
578
    guards: Vec<WorkerGuard>,
579

            
580
    /// A safelog guard, for use if we have decided to disable safe logging.
581
    #[allow(unused)]
582
    safelog_guard: Option<safelog::Guard>,
583
}
584

            
585
/// Set up logging.
586
///
587
/// Note that the returned LogGuard must be dropped precisely when the program
588
/// quits; they're used to ensure that all the log messages are flushed.
589
330
#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
590
330
#[cfg_attr(docsrs, doc(cfg(feature = "experimental-api")))]
591
330
pub(crate) fn setup_logging(
592
330
    config: &LoggingConfig,
593
330
    mistrust: &Mistrust,
594
330
    path_resolver: &CfgPathResolver,
595
330
    cli: Option<&str>,
596
330
) -> Result<LogGuards> {
597
    // Important: We have to make sure that the individual layers we add here
598
    // are not filters themselves.  That means, for example, that we can't add
599
    // an `EnvFilter` layer unless we want it to apply globally to _all_ layers.
600
    //
601
    // For a bit of discussion on the difference between per-layer filters and filters
602
    // that apply to the entire registry, see
603
    // https://docs.rs/tracing-subscriber/0.3.5/tracing_subscriber/layer/index.html#global-filtering
604

            
605
330
    let registry = registry().with(console_layer(config, cli)?);
606

            
607
    #[cfg(feature = "journald")]
608
330
    let registry = registry.with(journald_layer(config)?);
609

            
610
    #[cfg(all(feature = "syslog", unix))]
611
330
    let registry = registry.with(syslog_layer(config)?);
612

            
613
    #[cfg(feature = "opentelemetry")]
614
330
    let registry = registry.with(otel_layer(config, path_resolver)?);
615

            
616
    #[cfg(feature = "tokio-console")]
617
330
    let registry = {
618
        // Note 1: We can't enable console_subscriber unconditionally when the `tokio-console`
619
        // feature is enabled, since it panics unless tokio is built with  `--cfg tokio_unstable`,
620
        // but we want arti to work with --all-features without any special --cfg.
621
        //
622
        // Note 2: We have to use an `Option` here, since the type of the registry changes
623
        // with whatever you add to it.
624
330
        let tokio_layer = if config.tokio_console.enabled {
625
            Some(console_subscriber::spawn())
626
        } else {
627
330
            None
628
        };
629
330
        registry.with(tokio_layer)
630
    };
631

            
632
330
    let (layer, guards) = logfile_layers(config, mistrust, path_resolver)?;
633
330
    let registry = registry.with(layer);
634

            
635
330
    registry.init();
636

            
637
330
    let safelog_guard = if config.log_sensitive_information {
638
        match safelog::disable_safe_logging() {
639
            Ok(guard) => Some(guard),
640
            Err(e) => {
641
                // We don't need to propagate this error; it isn't the end of
642
                // the world if we were unable to disable safe logging.
643
                warn_report!(e, "Unable to disable safe logging");
644
                None
645
            }
646
        }
647
    } else {
648
330
        None
649
    };
650

            
651
330
    let mode = if config.protocol_warnings {
652
        tor_error::tracing::ProtocolWarningMode::Warn
653
    } else {
654
330
        tor_error::tracing::ProtocolWarningMode::Off
655
    };
656
330
    tor_error::tracing::set_protocol_warning_mode(mode);
657

            
658
330
    install_panic_handler();
659

            
660
330
    Ok(LogGuards {
661
330
        guards,
662
330
        safelog_guard,
663
330
    })
664
330
}