linera_service/tracing/
mod.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! This module provides unified handling for tracing subscribers within Linera binaries.
5
6pub mod chrome;
7pub mod opentelemetry;
8
9use std::{
10    env,
11    fs::{File, OpenOptions},
12    path::Path,
13    sync::Arc,
14};
15
16use is_terminal::IsTerminal as _;
17use tracing::Subscriber;
18use tracing_subscriber::{
19    fmt::{
20        self,
21        format::{FmtSpan, Format, Full},
22        time::FormatTime,
23        FormatFields, MakeWriter,
24    },
25    layer::{Layer, SubscriberExt as _},
26    registry::LookupSpan,
27    util::SubscriberInitExt,
28    EnvFilter,
29};
30
31pub(crate) struct EnvConfig {
32    pub(crate) env_filter: EnvFilter,
33    span_events: FmtSpan,
34    format: Option<String>,
35    color_output: bool,
36    log_name: String,
37}
38
39impl EnvConfig {
40    pub(crate) fn stderr_layer<S>(&self) -> Box<dyn Layer<S> + Send + Sync>
41    where
42        S: Subscriber + for<'span> LookupSpan<'span>,
43    {
44        prepare_formatted_layer(
45            self.format.as_deref(),
46            fmt::layer()
47                .with_span_events(self.span_events.clone())
48                .with_writer(std::io::stderr)
49                .with_ansi(self.color_output),
50        )
51    }
52
53    pub(crate) fn maybe_log_file_layer<S>(&self) -> Option<Box<dyn Layer<S> + Send + Sync>>
54    where
55        S: Subscriber + for<'span> LookupSpan<'span>,
56    {
57        open_log_file(&self.log_name).map(|file_writer| {
58            prepare_formatted_layer(
59                self.format.as_deref(),
60                fmt::layer()
61                    .with_span_events(self.span_events.clone())
62                    .with_writer(Arc::new(file_writer))
63                    .with_ansi(false),
64            )
65        })
66    }
67}
68
69/// Initializes tracing in a standard way.
70///
71/// The environment variables `RUST_LOG`, `RUST_LOG_SPAN_EVENTS`, and `RUST_LOG_FORMAT`
72/// can be used to control the verbosity, the span event verbosity, and the output format,
73/// respectively.
74///
75/// The `LINERA_LOG_DIR` environment variable can be used to configure a directory to
76/// store log files. If it is set, a file named `log_name` with the `log` extension is
77/// created in the directory.
78pub fn init(log_name: &str) {
79    let config = get_env_config(log_name);
80    let maybe_log_file_layer = config.maybe_log_file_layer();
81    let stderr_layer = config.stderr_layer();
82
83    tracing_subscriber::registry()
84        .with(config.env_filter)
85        .with(maybe_log_file_layer)
86        .with(stderr_layer)
87        .init();
88}
89
90pub(crate) fn get_env_config(log_name: &str) -> EnvConfig {
91    let env_filter = EnvFilter::builder()
92        .with_default_directive(tracing_subscriber::filter::LevelFilter::INFO.into())
93        .from_env_lossy();
94
95    let span_events = std::env::var("RUST_LOG_SPAN_EVENTS")
96        .ok()
97        .map_or(FmtSpan::NONE, |s| fmt_span_from_str(&s));
98
99    let format = std::env::var("RUST_LOG_FORMAT").ok();
100    let color_output =
101        !std::env::var("NO_COLOR").is_ok_and(|x| !x.is_empty()) && std::io::stderr().is_terminal();
102
103    EnvConfig {
104        env_filter,
105        span_events,
106        format,
107        color_output,
108        log_name: log_name.to_string(),
109    }
110}
111
112/// Opens a log file for writing.
113///
114/// The location of the file is determined by the `LINERA_LOG_DIR` environment variable,
115/// and its name by the `log_name` parameter.
116///
117/// Returns [`None`] if the `LINERA_LOG_DIR` environment variable is not set.
118pub(crate) fn open_log_file(log_name: &str) -> Option<File> {
119    let log_directory = env::var_os("LINERA_LOG_DIR")?;
120    let mut log_file_path = Path::new(&log_directory).join(log_name);
121    log_file_path.set_extension("log");
122
123    Some(
124        OpenOptions::new()
125            .append(true)
126            .create(true)
127            .open(log_file_path)
128            .expect("Failed to open log file for writing"),
129    )
130}
131
132/// Applies a requested `formatting` to the log output of the provided `layer`.
133///
134/// Returns a boxed [`Layer`] with the formatting applied to the original `layer`.
135pub(crate) fn prepare_formatted_layer<S, N, W, T>(
136    formatting: Option<&str>,
137    layer: fmt::Layer<S, N, Format<Full, T>, W>,
138) -> Box<dyn Layer<S> + Send + Sync>
139where
140    S: Subscriber + for<'span> LookupSpan<'span>,
141    N: for<'writer> FormatFields<'writer> + Send + Sync + 'static,
142    W: for<'writer> MakeWriter<'writer> + Send + Sync + 'static,
143    T: FormatTime + Send + Sync + 'static,
144{
145    match formatting.unwrap_or("plain") {
146        "json" => layer.json().boxed(),
147        "pretty" => layer.pretty().boxed(),
148        "plain" => layer.boxed(),
149        format => {
150            panic!("Invalid RUST_LOG_FORMAT: `{format}`.  Valid values are `json` or `pretty`.")
151        }
152    }
153}
154
155pub(crate) fn fmt_span_from_str(events: &str) -> FmtSpan {
156    let mut fmt_span = FmtSpan::NONE;
157    for event in events.split(',') {
158        fmt_span |= match event {
159            "new" => FmtSpan::NEW,
160            "enter" => FmtSpan::ENTER,
161            "exit" => FmtSpan::EXIT,
162            "close" => FmtSpan::CLOSE,
163            "active" => FmtSpan::ACTIVE,
164            "full" => FmtSpan::FULL,
165            _ => FmtSpan::NONE,
166        };
167    }
168    fmt_span
169}