Skip to main content

rings_node/
logging.rs

1//! Logging configuration contains both `node` and `browser`.
2use std::fmt;
3use std::panic::Location;
4use std::panic::PanicHookInfo;
5
6use backtrace::Backtrace;
7use clap::ValueEnum;
8use tracing::Level;
9use tracing_log::LogTracer;
10use tracing_subscriber::layer::SubscriberExt;
11use tracing_subscriber::Registry;
12
13#[cfg(all(feature = "browser", target_family = "wasm"))]
14pub use self::browser::init_logging;
15#[cfg(feature = "node")]
16pub use self::node::rings_node_init_logging as init_logging;
17use crate::prelude::wasm_export;
18
19/// Logging verbosity accepted by native CLI and browser bindings.
20#[repr(C)]
21#[wasm_export]
22#[derive(ValueEnum, Debug, Clone, Copy, Default, Eq, PartialEq)]
23pub enum LogLevel {
24    /// Debug-level diagnostic output.
25    Debug,
26    /// Informational runtime output.
27    Info,
28    /// Warning-level runtime output.
29    Warn,
30    /// Error-level runtime output.
31    #[default]
32    Error,
33    /// Trace-level diagnostic output.
34    Trace,
35}
36
37impl From<LogLevel> for Level {
38    fn from(val: LogLevel) -> Self {
39        match val {
40            LogLevel::Trace => Level::TRACE,
41            LogLevel::Debug => Level::DEBUG,
42            LogLevel::Info => Level::INFO,
43            LogLevel::Warn => Level::WARN,
44            LogLevel::Error => Level::ERROR,
45        }
46    }
47}
48
49impl std::str::FromStr for LogLevel {
50    type Err = crate::error::Error;
51    fn from_str(s: &str) -> Result<Self, Self::Err> {
52        match s.to_uppercase().as_str() {
53            "TRACE" => Ok(LogLevel::Trace),
54            "DEBUG" => Ok(LogLevel::Debug),
55            "INFO" => Ok(LogLevel::Info),
56            "WARN" => Ok(LogLevel::Warn),
57            "ERROR" => Ok(LogLevel::Error),
58            x => Err(crate::error::Error::InvalidLoggingLevel(x.to_string())),
59        }
60    }
61}
62
63/// Panic location
64#[derive(Debug, Clone)]
65pub struct PanicLocation {
66    file: String,
67    line: String,
68    column: String,
69}
70
71impl<'a, T> From<T> for PanicLocation
72where T: Into<Location<'a>>
73{
74    fn from(lo: T) -> Self {
75        let lo: Location = lo.into();
76        Self {
77            file: lo.file().to_string(),
78            line: lo.line().to_string(),
79            column: lo.file().to_string(),
80        }
81    }
82}
83
84/// Necessary information for recording panic
85#[derive(Debug, Clone)]
86pub struct PanicData<'a> {
87    message: &'a PanicHookInfo<'a>,
88    backtrace: String,
89    location: Option<PanicLocation>,
90}
91
92impl<'a, T> From<T> for PanicData<'a>
93where T: Into<&'a PanicHookInfo<'a>>
94{
95    fn from(panic: T) -> PanicData<'a> {
96        let panic = panic.into();
97        let backtrace = Backtrace::new();
98        let backtrace = format!("{backtrace:?}");
99        let location: Option<PanicLocation> = panic.location().map(|l| PanicLocation::from(*l));
100        PanicData {
101            message: panic,
102            backtrace,
103            location,
104        }
105    }
106}
107
108impl fmt::Display for PanicLocation {
109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110        write!(f, "{}:{}:{}", self.file, self.line, self.column)
111    }
112}
113
114impl<'a> fmt::Display for PanicData<'a> {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        match &self.location {
117            Some(l) => write!(f, "{}, {} \n\n {}", self.message, l, self.backtrace),
118            None => write!(f, "{} \n\n {}", self.message, self.backtrace),
119        }
120    }
121}
122
123fn log_panic(panic: &PanicHookInfo) {
124    let data: PanicData = panic.into();
125    tracing::error!("{}", data)
126}
127
128/// Setup hooks for panic, this function works for both wasm and native.
129pub fn set_panic_hook() {
130    // Set a panic hook that records the panic as a `tracing` event at the
131    // `ERROR` verbosity level.
132    //
133    // If we are currently in a span when the panic occurred, the logged event
134    // will include the current span, allowing the context in which the panic
135    // occurred to be recorded.
136    std::panic::set_hook(Box::new(|panic| {
137        log_panic(panic);
138    }));
139}
140
141#[cfg(feature = "node")]
142/// logging configuration about node.
143pub mod node {
144    use tracing_subscriber::filter;
145    use tracing_subscriber::fmt;
146    use tracing_subscriber::EnvFilter;
147    use tracing_subscriber::Layer;
148
149    use super::*;
150
151    /// Initializes native tracing and panic logging for the requested level.
152    #[no_mangle]
153    pub extern "C" fn rings_node_init_logging(level: LogLevel) {
154        set_panic_hook();
155
156        let subscriber = Registry::default();
157        let level_filter = filter::LevelFilter::from_level(level.into());
158        let filter = match std::env::var("RINGS_LOG_FILTER") {
159            Ok(spec) if !spec.trim().is_empty() => {
160                EnvFilter::try_new(spec.trim()).unwrap_or_else(|err| {
161                    eprintln!(
162                        "invalid RINGS_LOG_FILTER '{}': {}; falling back to {}",
163                        spec, err, level_filter
164                    );
165                    EnvFilter::new(level_filter.to_string())
166                })
167            }
168            _ => EnvFilter::new(level_filter.to_string()),
169        };
170
171        // Stderr
172        let subscriber = subscriber.with(
173            fmt::layer()
174                .with_writer(std::io::stderr)
175                .with_filter(filter),
176        );
177        // Enable log compatible layer to convert log record to tracing span.
178        // We will ignore any errors that returned by this functions.
179        let _ = LogTracer::init();
180
181        // Ignore errors returned by set_global_default.
182        let _ = tracing::subscriber::set_global_default(subscriber);
183    }
184}
185
186#[cfg(all(feature = "browser", target_family = "wasm"))]
187/// Browser console logging configuration.
188pub mod browser {
189    use tracing_wasm::ConsoleConfig;
190    use tracing_wasm::WASMLayer;
191    use tracing_wasm::WASMLayerConfigBuilder;
192
193    use super::*;
194    /// Initializes browser console tracing and panic logging for the requested level.
195    #[wasm_export]
196    pub fn init_logging(level: LogLevel) {
197        set_panic_hook();
198
199        let subscriber = Registry::default();
200
201        // Browser console and profiler
202        let subscriber = subscriber.with(WASMLayer::new(
203            WASMLayerConfigBuilder::new()
204                .set_max_level(level.into())
205                .set_console_config(ConsoleConfig::ReportWithoutConsoleColor)
206                .build(),
207        ));
208
209        // Enable log compatible layer to convert log record to tracing span.
210        // We will ignore any errors that returned by this functions.
211        let _ = LogTracer::init();
212
213        // Ignore errors returned by set_global_default.
214        let _ = tracing::subscriber::set_global_default(subscriber);
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use super::LogLevel;
221
222    #[test]
223    fn test_default_log_level_is_error() {
224        assert_eq!(LogLevel::default(), LogLevel::Error);
225    }
226}