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