mysten_common/
logging.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use crate::in_test_configuration;
use once_cell::sync::Lazy;

#[macro_export]
macro_rules! fatal {
    ($msg:literal $(, $arg:expr)*) => {{
        if $crate::in_antithesis() {
            let full_msg = format!($msg $(, $arg)*);
            let json = $crate::logging::json!({ "message": full_msg });
            $crate::logging::assert_unreachable_antithesis!($msg, &json);
        }
        tracing::error!(fatal = true, $msg $(, $arg)*);
        panic!($msg $(, $arg)*);
    }};
}

pub use antithesis_sdk::assert_reachable as assert_reachable_antithesis;
pub use antithesis_sdk::assert_unreachable as assert_unreachable_antithesis;

pub use serde_json::json;

#[inline(always)]
pub fn crash_on_debug() -> bool {
    static CRASH_ON_DEBUG: Lazy<bool> = Lazy::new(|| {
        in_test_configuration() || std::env::var("SUI_ENABLE_DEBUG_ASSERTIONS").is_ok()
    });

    *CRASH_ON_DEBUG
}

#[cfg(msim)]
pub mod intercept_debug_fatal {
    use std::sync::{Arc, Mutex};

    #[derive(Clone)]
    pub struct DebugFatalCallback {
        pub pattern: String,
        pub callback: Arc<dyn Fn() + Send + Sync>,
    }

    thread_local! {
        static INTERCEPT_DEBUG_FATAL: Mutex<Option<DebugFatalCallback>> = Mutex::new(None);
    }

    pub fn register_callback(message: &str, f: impl Fn() + Send + Sync + 'static) {
        INTERCEPT_DEBUG_FATAL.with(|m| {
            *m.lock().unwrap() = Some(DebugFatalCallback {
                pattern: message.to_string(),
                callback: Arc::new(f),
            });
        });
    }

    pub fn get_callback() -> Option<DebugFatalCallback> {
        INTERCEPT_DEBUG_FATAL.with(|m| m.lock().unwrap().clone())
    }
}

#[macro_export]
macro_rules! register_debug_fatal_handler {
    ($message:literal, $f:expr) => {
        #[cfg(msim)]
        $crate::logging::intercept_debug_fatal::register_callback($message, $f);

        #[cfg(not(msim))]
        {
            // silence unused variable warnings from the body of the callback
            let _ = $f;
        }
    };
}

#[macro_export]
macro_rules! debug_fatal {
    //($msg:literal $(, $arg:expr)* $(,)?)
    ($msg:literal $(, $arg:expr)*) => {{
        loop {
            #[cfg(msim)]
            {
                if let Some(cb) = $crate::logging::intercept_debug_fatal::get_callback() {
                    tracing::error!($msg $(, $arg)*);
                    let msg = format!($msg $(, $arg)*);
                    if msg.contains(&cb.pattern) {
                        (cb.callback)();
                    }
                    break;
                }
            }

            // In antithesis, rather than crashing, we will use the assert_unreachable_antithesis
            // macro to catch the signal that something has gone wrong.
            if !$crate::in_antithesis() && $crate::logging::crash_on_debug() {
                $crate::fatal!($msg $(, $arg)*);
            } else {
                let stacktrace = std::backtrace::Backtrace::capture();
                tracing::error!(debug_fatal = true, stacktrace = ?stacktrace, $msg $(, $arg)*);
                let location = concat!(file!(), ':', line!());
                if let Some(metrics) = mysten_metrics::get_metrics() {
                    metrics.system_invariant_violations.with_label_values(&[location]).inc();
                }
                if $crate::in_antithesis() {
                    // antithesis requires a literal for first argument. pass the formatted argument
                    // as a string.
                    let full_msg = format!($msg $(, $arg)*);
                    let json = $crate::logging::json!({ "message": full_msg });
                    $crate::logging::assert_unreachable_antithesis!($msg, &json);
                }
            }
            break;
        }
    }};
}

#[macro_export]
macro_rules! assert_reachable {
    () => {
        $crate::logging::assert_reachable!("");
    };
    ($message:literal) => {{
        // calling in to antithesis sdk breaks determinisim in simtests (on linux only)
        if !cfg!(msim) {
            $crate::logging::assert_reachable_antithesis!($message);
        }
    }};
}

mod tests {
    #[test]
    #[should_panic]
    fn test_fatal() {
        fatal!("This is a fatal error");
    }

    #[test]
    #[should_panic]
    fn test_debug_fatal() {
        if cfg!(debug_assertions) {
            debug_fatal!("This is a debug fatal error");
        } else {
            // pass in release mode as well
            fatal!("This is a fatal error");
        }
    }

    #[cfg(not(debug_assertions))]
    #[test]
    fn test_debug_fatal_release_mode() {
        debug_fatal!("This is a debug fatal error");
    }
}