1use 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#[repr(C)]
21#[wasm_export]
22#[derive(ValueEnum, Debug, Clone, Copy, Default, Eq, PartialEq)]
23pub enum LogLevel {
24 Debug,
26 Info,
28 Warn,
30 #[default]
32 Error,
33 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#[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#[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
128pub fn set_panic_hook() {
130 std::panic::set_hook(Box::new(|panic| {
137 log_panic(panic);
138 }));
139}
140
141#[cfg(feature = "node")]
142pub 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 #[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 let subscriber = subscriber.with(
173 fmt::layer()
174 .with_writer(std::io::stderr)
175 .with_filter(filter),
176 );
177 let _ = LogTracer::init();
180
181 let _ = tracing::subscriber::set_global_default(subscriber);
183 }
184}
185
186#[cfg(all(feature = "browser", target_family = "wasm"))]
187pub mod browser {
189 use tracing_wasm::ConsoleConfig;
190 use tracing_wasm::WASMLayer;
191 use tracing_wasm::WASMLayerConfigBuilder;
192
193 use super::*;
194 #[wasm_export]
196 pub fn init_logging(level: LogLevel) {
197 set_panic_hook();
198
199 let subscriber = Registry::default();
200
201 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 let _ = LogTracer::init();
212
213 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}