Skip to main content

iota_common/
logging.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use once_cell::sync::Lazy;
6
7use crate::in_test_configuration;
8
9#[macro_export]
10macro_rules! fatal {
11    ($($arg:tt)*) => {{
12        tracing::error!(fatal = true, $($arg)*);
13        panic!($($arg)*);
14    }};
15}
16
17#[inline(always)]
18pub fn crash_on_debug() -> bool {
19    static CRASH_ON_DEBUG: Lazy<bool> = Lazy::new(|| {
20        in_test_configuration() || std::env::var("IOTA_ENABLE_DEBUG_ASSERTIONS").is_ok()
21    });
22
23    *CRASH_ON_DEBUG
24}
25
26#[cfg(msim)]
27pub mod intercept_debug_fatal {
28    use std::sync::{Arc, Mutex};
29
30    #[derive(Clone)]
31    pub struct DebugFatalCallback {
32        pub pattern: String,
33        pub callback: Arc<dyn Fn() + Send + Sync>,
34    }
35
36    thread_local! {
37        static INTERCEPT_DEBUG_FATAL: Mutex<Option<DebugFatalCallback>> = Mutex::new(None);
38    }
39
40    pub fn register_callback(message: &str, f: impl Fn() + Send + Sync + 'static) {
41        INTERCEPT_DEBUG_FATAL.with(|m| {
42            *m.lock().unwrap() = Some(DebugFatalCallback {
43                pattern: message.to_string(),
44                callback: Arc::new(f),
45            });
46        });
47    }
48
49    pub fn get_callback() -> Option<DebugFatalCallback> {
50        INTERCEPT_DEBUG_FATAL.with(|m| m.lock().unwrap().clone())
51    }
52}
53
54#[macro_export]
55macro_rules! register_debug_fatal_handler {
56    ($message:literal, $f:expr) => {
57        #[cfg(msim)]
58        $crate::logging::intercept_debug_fatal::register_callback($message, $f);
59
60        #[cfg(not(msim))]
61        {
62            // silence unused variable warnings from the body of the callback
63            let _ = $f;
64        }
65    };
66}
67
68#[cfg(not(target_arch = "wasm32"))]
69#[macro_export]
70macro_rules! debug_fatal {
71    ($($arg:tt)*) => {{
72        loop {
73            #[cfg(msim)]
74            {
75                if let Some(cb) = $crate::logging::intercept_debug_fatal::get_callback() {
76                    tracing::error!($($arg)*);
77                    let msg = format!($($arg)*);
78                    if msg.contains(&cb.pattern) {
79                        (cb.callback)();
80                    }
81                    break;
82                }
83            }
84
85            if $crate::logging::crash_on_debug() {
86                $crate::fatal!($($arg)*);
87            } else {
88                let stacktrace = std::backtrace::Backtrace::capture();
89                tracing::error!(debug_fatal = true, stacktrace = ?stacktrace, $($arg)*);
90                let location = concat!(file!(), ':', line!());
91                if let Some(metrics) = iota_metrics::get_metrics() {
92                    metrics.system_invariant_violations.with_label_values(&[location]).inc();
93                }
94            }
95            break;
96        }
97    }};
98}
99
100// `iota-metrics` isn't available on wasm32; same macro without the metrics
101// callout.
102#[cfg(target_arch = "wasm32")]
103#[macro_export]
104macro_rules! debug_fatal {
105    ($($arg:tt)*) => {{
106        if $crate::logging::crash_on_debug() {
107            $crate::fatal!($($arg)*);
108        } else {
109            tracing::error!(debug_fatal = true, $($arg)*);
110        }
111    }};
112}
113
114mod tests {
115    #[test]
116    #[should_panic]
117    fn test_fatal() {
118        fatal!("This is a fatal error");
119    }
120
121    #[test]
122    #[should_panic]
123    fn test_debug_fatal() {
124        if cfg!(debug_assertions) {
125            debug_fatal!("This is a debug fatal error");
126        } else {
127            // pass in release mode as well
128            fatal!("This is a fatal error");
129        }
130    }
131
132    #[cfg(not(debug_assertions))]
133    #[test]
134    fn test_debug_fatal_release_mode() {
135        debug_fatal!("This is a debug fatal error");
136    }
137}