aboutsummaryrefslogtreecommitdiff
path: root/src/rust/tor_log/tor_log.rs
blob: 98fccba5a97870233f4ea5eae00f6d6cc4a665ed (plain)
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
// Copyright (c) 2016-2019, The Tor Project, Inc. */
// See LICENSE for licensing information */

// Note that these functions are untested due to the fact that there are no
// return variables to test and they are calling into a C API.

/// The related domain which the logging message is relevant. For example,
/// log messages relevant to networking would use LogDomain::LdNet, whereas
/// general messages can use LdGeneral.
#[derive(Eq, PartialEq)]
pub enum LogDomain {
    Net,
    General,
}

/// The severity level at which to log messages.
#[derive(Eq, PartialEq)]
pub enum LogSeverity {
    Notice,
    Warn,
}

/// Main entry point for Rust modules to log messages.
///
/// # Inputs
///
/// * A `severity` of type LogSeverity, which defines the level of severity the
/// message will be logged.
/// * A `domain` of type LogDomain, which defines the domain the log message
/// will be associated with.
/// * A `function` of type &str, which defines the name of the function where
/// the message is being logged. There is a current RFC for a macro that
/// defines function names. When it is, we should use it. See
/// https://github.com/rust-lang/rfcs/pull/1719
/// * A `message` of type &str, which is the log message itself.
#[macro_export]
macro_rules! tor_log_msg {
    ($severity: path,
     $domain: path,
     $function: expr,
     $($message:tt)*) =>
    {
        {
            let msg = format!($($message)*);
            $crate::tor_log_msg_impl($severity, $domain, $function, msg)
        }
    };
}

#[inline]
pub fn tor_log_msg_impl(severity: LogSeverity, domain: LogDomain, function: &str, message: String) {
    use std::ffi::CString;

    /// Default function name to log in case of errors when converting
    /// a function name to a CString
    const ERR_LOG_FUNCTION: &str = "tor_log_msg";

    /// Default message to log in case of errors when converting a log
    /// message to a CString
    const ERR_LOG_MSG: &str = "Unable to log message from Rust \
                               module due to error when converting to CString";

    let func = match CString::new(function) {
        Ok(n) => n,
        Err(_) => CString::new(ERR_LOG_FUNCTION).unwrap(),
    };

    let msg = match CString::new(message) {
        Ok(n) => n,
        Err(_) => CString::new(ERR_LOG_MSG).unwrap(),
    };

    // Bind to a local variable to preserve ownership. This is essential so
    // that ownership is guaranteed until these local variables go out of scope
    let func_ptr = func.as_ptr();
    let msg_ptr = msg.as_ptr();

    let c_severity = unsafe { log::translate_severity(severity) };
    let c_domain = unsafe { log::translate_domain(domain) };

    unsafe { log::tor_log_string(c_severity, c_domain, func_ptr, msg_ptr) }
}

/// This implementation is used when compiling for actual use, as opposed to
/// testing.
#[cfg(not(test))]
pub mod log {
    use super::LogDomain;
    use super::LogSeverity;
    use libc::{c_char, c_int};

    /// Severity log types. These mirror definitions in src/lib/log/log.h
    /// C_RUST_COUPLED: src/lib/log/log.c, log domain types
    extern "C" {
        static LOG_WARN_: c_int;
        static LOG_NOTICE_: c_int;
    }

    /// Domain log types. These mirror definitions in src/lib/log/log.h
    /// C_RUST_COUPLED: src/lib/log/log.c, log severity types
    extern "C" {
        static LD_NET_: u32;
        static LD_GENERAL_: u32;
    }

    /// Translate Rust defintions of log domain levels to C. This exposes a 1:1
    /// mapping between types.
    #[inline]
    pub unsafe fn translate_domain(domain: LogDomain) -> u32 {
        match domain {
            LogDomain::Net => LD_NET_,
            LogDomain::General => LD_GENERAL_,
        }
    }

    /// Translate Rust defintions of log severity levels to C. This exposes a
    /// 1:1 mapping between types.
    #[inline]
    pub unsafe fn translate_severity(severity: LogSeverity) -> c_int {
        match severity {
            LogSeverity::Warn => LOG_WARN_,
            LogSeverity::Notice => LOG_NOTICE_,
        }
    }

    /// The main entry point into Tor's logger. When in non-test mode, this
    /// will link directly with `tor_log_string` in torlog.c
    extern "C" {
        pub fn tor_log_string(
            severity: c_int,
            domain: u32,
            function: *const c_char,
            string: *const c_char,
        );
    }
}

/// This module exposes no-op functionality for testing other Rust modules
/// without linking to C.
#[cfg(test)]
pub mod log {
    use super::LogDomain;
    use super::LogSeverity;
    use libc::{c_char, c_int};

    pub static mut LAST_LOGGED_FUNCTION: *mut String = 0 as *mut String;
    pub static mut LAST_LOGGED_MESSAGE: *mut String = 0 as *mut String;

    pub unsafe fn tor_log_string(
        _severity: c_int,
        _domain: u32,
        function: *const c_char,
        message: *const c_char,
    ) {
        use std::ffi::CStr;

        let f = CStr::from_ptr(function);
        let fct = match f.to_str() {
            Ok(n) => n,
            Err(_) => "",
        };
        LAST_LOGGED_FUNCTION = Box::into_raw(Box::new(String::from(fct)));

        let m = CStr::from_ptr(message);
        let msg = match m.to_str() {
            Ok(n) => n,
            Err(_) => "",
        };
        LAST_LOGGED_MESSAGE = Box::into_raw(Box::new(String::from(msg)));
    }

    pub unsafe fn translate_domain(_domain: LogDomain) -> u32 {
        1
    }

    pub unsafe fn translate_severity(_severity: LogSeverity) -> c_int {
        1
    }
}

#[cfg(test)]
mod test {
    use tor_log::log::{LAST_LOGGED_FUNCTION, LAST_LOGGED_MESSAGE};
    use tor_log::*;

    #[test]
    fn test_get_log_message() {
        {
            fn test_macro() {
                tor_log_msg!(
                    LogSeverity::Warn,
                    LogDomain::Net,
                    "test_macro",
                    "test log message {}",
                    "a",
                );
            }

            test_macro();

            let function = unsafe { Box::from_raw(LAST_LOGGED_FUNCTION) };
            assert_eq!("test_macro", *function);

            let message = unsafe { Box::from_raw(LAST_LOGGED_MESSAGE) };
            assert_eq!("test log message a", *message);
        }

        // test multiple inputs into the log message
        {
            fn test_macro() {
                tor_log_msg!(
                    LogSeverity::Warn,
                    LogDomain::Net,
                    "next_test_macro",
                    "test log message {} {} {} {} {}",
                    1,
                    2,
                    3,
                    4,
                    5
                );
            }

            test_macro();

            let function = unsafe { Box::from_raw(LAST_LOGGED_FUNCTION) };
            assert_eq!("next_test_macro", *function);

            let message = unsafe { Box::from_raw(LAST_LOGGED_MESSAGE) };
            assert_eq!("test log message 1 2 3 4 5", *message);
        }

        // test how a long log message will be formatted
        {
            fn test_macro() {
                tor_log_msg!(
                    LogSeverity::Warn,
                    LogDomain::Net,
                    "test_macro",
                    "{}",
                    "All the world's a stage, and all the men and women \
                     merely players: they have their exits and their \
                     entrances; and one man in his time plays many parts, his \
                     acts being seven ages."
                );
            }

            test_macro();

            let expected_string = "All the world's a \
                                   stage, and all the men \
                                   and women merely players: \
                                   they have their exits and \
                                   their entrances; and one man \
                                   in his time plays many parts, \
                                   his acts being seven ages.";

            let function = unsafe { Box::from_raw(LAST_LOGGED_FUNCTION) };
            assert_eq!("test_macro", *function);

            let message = unsafe { Box::from_raw(LAST_LOGGED_MESSAGE) };
            assert_eq!(expected_string, *message);
        }
    }
}