Skip to main content

mysten_common/
logging.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::in_test_configuration;
5use once_cell::sync::Lazy;
6
7#[macro_export]
8macro_rules! fatal {
9    ($msg:literal $(, $arg:expr)*) => {{
10        if $crate::in_antithesis() {
11            let full_msg = format!($msg $(, $arg)*);
12            let json = $crate::logging::json!({ "message": full_msg });
13            $crate::logging::assert_unreachable_antithesis!($msg, &json);
14        }
15        tracing::error!(fatal = true, $msg $(, $arg)*);
16        panic!($msg $(, $arg)*);
17    }};
18}
19
20pub use antithesis_sdk::assert_reachable as assert_reachable_antithesis;
21pub use antithesis_sdk::assert_sometimes as assert_sometimes_antithesis;
22pub use antithesis_sdk::assert_unreachable as assert_unreachable_antithesis;
23
24pub use serde_json::json;
25
26#[inline(always)]
27pub fn crash_on_debug() -> bool {
28    static CRASH_ON_DEBUG: Lazy<bool> = Lazy::new(|| {
29        in_test_configuration() || std::env::var("SUI_ENABLE_DEBUG_ASSERTIONS").is_ok()
30    });
31
32    *CRASH_ON_DEBUG
33}
34
35#[cfg(msim)]
36pub mod intercept_debug_fatal {
37    use std::sync::{Arc, Mutex};
38
39    #[derive(Clone)]
40    pub struct DebugFatalCallback {
41        pub pattern: String,
42        pub callback: Arc<dyn Fn() + Send + Sync>,
43    }
44
45    static INTERCEPT_DEBUG_FATAL: Mutex<Option<DebugFatalCallback>> = Mutex::new(None);
46
47    pub fn register_callback(message: &str, f: impl Fn() + Send + Sync + 'static) {
48        *INTERCEPT_DEBUG_FATAL.lock().unwrap() = Some(DebugFatalCallback {
49            pattern: message.to_string(),
50            callback: Arc::new(f),
51        });
52    }
53
54    pub fn get_callback() -> Option<DebugFatalCallback> {
55        INTERCEPT_DEBUG_FATAL.lock().unwrap().clone()
56    }
57}
58
59#[macro_export]
60macro_rules! register_debug_fatal_handler {
61    ($message:literal, $f:expr) => {
62        #[cfg(msim)]
63        $crate::logging::intercept_debug_fatal::register_callback($message, $f);
64
65        #[cfg(not(msim))]
66        {
67            // silence unused variable warnings from the body of the callback
68            let _ = $f;
69        }
70    };
71}
72
73/// Like `debug_fatal!`, but records the violation on a metric of the caller's choosing instead of
74/// `system_invariant_violations`: `$record` is invoked with `&mysten_metrics::Metrics` when metrics
75/// are initialized. Use this when a violation has its own counter, and its own alert.
76#[macro_export]
77macro_rules! debug_fatal_with_metric {
78    ($record:expr, $msg:literal $(, $arg:expr)*) => {{
79        loop {
80            #[cfg(msim)]
81            {
82                if let Some(cb) = $crate::logging::intercept_debug_fatal::get_callback() {
83                    tracing::error!($msg $(, $arg)*);
84                    let msg = format!($msg $(, $arg)*);
85                    if msg.contains(&cb.pattern) {
86                        (cb.callback)();
87                    }
88                    break;
89                }
90            }
91
92            // In antithesis, rather than crashing, we will use the assert_unreachable_antithesis
93            // macro to catch the signal that something has gone wrong.
94            if !$crate::in_antithesis() && $crate::logging::crash_on_debug() {
95                $crate::fatal!($msg $(, $arg)*);
96            } else {
97                let stacktrace = std::backtrace::Backtrace::capture();
98                tracing::error!(debug_fatal = true, stacktrace = ?stacktrace, $msg $(, $arg)*);
99                if let Some(metrics) = mysten_metrics::get_metrics() {
100                    ($record)(metrics);
101                }
102                if $crate::in_antithesis() {
103                    // antithesis requires a literal for first argument. pass the formatted argument
104                    // as a string.
105                    let full_msg = format!($msg $(, $arg)*);
106                    let json = $crate::logging::json!({ "message": full_msg });
107                    $crate::logging::assert_unreachable_antithesis!($msg, &json);
108                }
109            }
110            break;
111        }
112    }};
113}
114
115/// Like `debug_fatal!`, but records `$location` (a `&str`) as the
116/// `system_invariant_violations` metric label instead of the macro's own
117/// `file!():line!()`. Use this when forwarding a caller-supplied location
118/// (e.g. from `#[track_caller]` + `Location::caller()`) so the metric points
119/// at the user's call site rather than the wrapper.
120#[macro_export]
121macro_rules! debug_fatal_at {
122    ($location:expr, $msg:literal $(, $arg:expr)*) => {{
123        $crate::debug_fatal_with_metric!(
124            |metrics: &mysten_metrics::Metrics| {
125                let location: &str = $location;
126                metrics.system_invariant_violations.with_label_values(&[location]).inc();
127            },
128            $msg $(, $arg)*
129        );
130    }};
131}
132
133#[macro_export]
134macro_rules! debug_fatal {
135    //($msg:literal $(, $arg:expr)* $(,)?)
136    ($msg:literal $(, $arg:expr)*) => {{
137        $crate::debug_fatal_at!(concat!(file!(), ':', line!()), $msg $(, $arg)*);
138    }};
139}
140
141#[macro_export]
142macro_rules! debug_fatal_no_invariant {
143    ($msg:literal $(, $arg:expr)*) => {{
144        loop {
145            #[cfg(msim)]
146            {
147                if let Some(cb) = $crate::logging::intercept_debug_fatal::get_callback() {
148                    tracing::error!($msg $(, $arg)*);
149                    let msg = format!($msg $(, $arg)*);
150                    if msg.contains(&cb.pattern) {
151                        (cb.callback)();
152                    }
153                    break;
154                }
155            }
156
157            if !$crate::in_antithesis() && $crate::logging::crash_on_debug() {
158                $crate::fatal!($msg $(, $arg)*);
159            } else {
160                tracing::error!($msg $(, $arg)*);
161                if $crate::in_antithesis() {
162                    let full_msg = format!($msg $(, $arg)*);
163                    let json = $crate::logging::json!({ "message": full_msg });
164                    $crate::logging::assert_unreachable_antithesis!($msg, &json);
165                }
166            }
167            break;
168        }
169    }};
170}
171
172#[macro_export]
173macro_rules! assert_reachable {
174    () => {
175        $crate::logging::assert_reachable!("");
176    };
177    ($message:literal) => {{
178        // calling in to antithesis sdk breaks determinisim in simtests (on linux only)
179        if !cfg!(msim) {
180            $crate::logging::assert_reachable_antithesis!($message);
181        } else {
182            $crate::assert_reachable_simtest!($message);
183        }
184    }};
185}
186
187#[macro_export]
188macro_rules! assert_sometimes {
189    ($expr:expr, $message:literal) => {{
190        // calling in to antithesis sdk breaks determinisim in simtests (on linux only)
191        if !cfg!(msim) {
192            $crate::logging::assert_sometimes_antithesis!($expr, $message);
193        } else {
194            $crate::assert_sometimes_simtest!($expr, $message);
195        }
196    }};
197}
198
199mod tests {
200    #[test]
201    #[should_panic]
202    fn test_fatal() {
203        fatal!("This is a fatal error");
204    }
205
206    #[test]
207    #[should_panic]
208    fn test_debug_fatal() {
209        if cfg!(debug_assertions) {
210            debug_fatal!("This is a debug fatal error");
211        } else {
212            // pass in release mode as well
213            fatal!("This is a fatal error");
214        }
215    }
216
217    #[cfg(not(debug_assertions))]
218    #[test]
219    fn test_debug_fatal_release_mode() {
220        debug_fatal!("This is a debug fatal error");
221    }
222
223    #[test]
224    fn test_assert_sometimes_side_effects() {
225        let mut x = 0;
226
227        let mut inc = || {
228            x += 1;
229            true
230        };
231
232        assert_sometimes!(inc(), "");
233        assert_eq!(x, 1);
234    }
235}