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
//! A logger for use with Windows debuggers.
//!
//! Windows allows applications to output a string directly to debuggers. This is
//! very useful in situations where other forms of logging are not available.
//! For example, stderr is not available for GUI apps.
//!
//! Windows provides the `OutputDebugString` entry point, which allows apps to
//! print a debug string. Internally, `OutputDebugString` is implemented by
//! raising an SEH exception, which the debugger catches and handles.
//!
//! Raising an exception has a significant cost, when run under a debugger,
//! because the debugger halts all threads in the target process. So you should
//! avoid using this logger for high rates of output, because doing so will
//! slow down your app.
//!
//! Like many Windows entry points, `OutputDebugString` is actually two entry
//! points: `OutputDebugStringA` (multi-byte encodings) and
//! `OutputDebugStringW` (UTF-16). In most cases, the `*A` version is implemented
//! using a "thunk" which converts its arguments to UTF-16 and then calls the `*W`
//! version. However, `OutputDebugStringA` is one of the few entry points where
//! the opposite is true.
//!
//! This crate can be compiled and used on non-Windows platforms, but it does
//! nothing. This is intended to minimize the impact on code that takes a
//! dependency on this crate.

use log::{Level, LevelFilter, Metadata, Record};

/// This implements `log::Log`, and so can be used as a logging provider.
/// It forwards log messages to the Windows `OutputDebugString` API.
pub struct DebuggerLogger;

/// This is a static instance of `DebuggerLogger`. Since `DebuggerLogger`
/// contains no state, this can be directly registered using `log::set_logger`.
///
/// Example:
///
/// ```
/// // During initialization:
/// log::set_logger(&win_dbg_logger::DEBUGGER_LOGGER).unwrap();
/// log::set_max_level(log::LevelFilter::Debug);
///
/// // Throughout your code:
/// use log::{info, debug};
///
/// info!("Hello, world!");
/// debug!("Hello, world, in detail!");
/// ```
pub static DEBUGGER_LOGGER: DebuggerLogger = DebuggerLogger;

impl log::Log for DebuggerLogger {
    fn enabled(&self, metadata: &Metadata) -> bool {
        metadata.level() <= Level::Debug
    }

    fn log(&self, record: &Record) {
        if self.enabled(record.metadata()) && is_debugger_present() {
            let s = format!(
                "{}({}): {} - {}\r\n",
                record.file().unwrap_or("<unknown>"),
                record.line().unwrap_or(0),
                record.level(),
                record.args()
            );
            output_debug_string(&s);
        }
    }

    fn flush(&self) {}
}

/// Calls the `OutputDebugString` API to log a string.
///
/// On non-Windows platforms, this function does nothing.
///
/// See [`OutputDebugStringW`](https://docs.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-outputdebugstringw).
pub fn output_debug_string(s: &str) {
    #[cfg(windows)]
    {
        let len = s.encode_utf16().count() + 1;
        let mut s_utf16: Vec<u16> = Vec::with_capacity(len + 1);
        s_utf16.extend(s.encode_utf16());
        s_utf16.push(0);
        unsafe {
            OutputDebugStringW(&s_utf16[0]);
        }
    }
}

#[cfg(windows)]
extern "stdcall" {
    fn OutputDebugStringW(chars: *const u16);
    fn IsDebuggerPresent() -> i32;
}

/// Checks whether a debugger is attached to the current process.
///
/// On non-Windows platforms, this function always returns `false`.
///
/// See [`IsDebuggerPresent`](https://docs.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-isdebuggerpresent).
pub fn is_debugger_present() -> bool {
    #[cfg(windows)]
    {
        unsafe { IsDebuggerPresent() != 0 }
    }
    #[cfg(not(windows))]
    {
        false
    }
}

/// Sets the `DebuggerLogger` as the currently-active logger.
///
/// If an error occurs when registering `DebuggerLogger` as the current logger,
/// this function will output a warning and will return normally. It will not panic.
/// This behavior was chosen because `DebuggerLogger` is intended for use in debugging.
/// Panicking would disrupt debugging and introduce new failure modes. It would also
/// create problems for mixed-mode debugging, where Rust code is linked with C/C++ code.
pub fn init() {
    match log::set_logger(&DEBUGGER_LOGGER) {
        Ok(()) => {}
        Err(_) => {
            // There's really nothing we can do about it.
            output_debug_string(
                "Warning: Failed to register DebuggerLogger as the current Rust logger.\r\n",
            );
        }
    }
}

macro_rules! define_init_at_level {
    ($func:ident, $level:ident) => {
        /// This can be called from C/C++ code to register the debug logger.
        ///
        /// For Windows DLLs that have statically linked an instance of `win_dbg_logger` into them,
        /// `DllMain` should call `win_dbg_logger_init_<level>()` from the `DLL_PROCESS_ATTACH` handler.
        /// For example:
        ///
        /// ```ignore
        /// // Calls into Rust code.
        /// extern "C" void __cdecl rust_win_dbg_logger_init_debug();
        ///
        /// BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD reason, LPVOID reserved) {
        ///     switch (reason) {
        ///         case DLL_PROCESS_ATTACH:
        ///             rust_win_dbg_logger_init_debug();
        ///             // ...
        ///     }
        ///     // ...
        /// }
        /// ```
        ///
        /// For Windows executables that have statically linked an instance of `win_dbg_logger` into
        /// them, call `win_dbg_logger_init_<level>()` during app startup.
        #[no_mangle]
        pub extern "C" fn $func() {
            init();
            log::set_max_level(LevelFilter::$level);
        }
    };
}

define_init_at_level!(rust_win_dbg_logger_init_info, Info);
define_init_at_level!(rust_win_dbg_logger_init_trace, Trace);
define_init_at_level!(rust_win_dbg_logger_init_debug, Debug);
define_init_at_level!(rust_win_dbg_logger_init_warn, Warn);
define_init_at_level!(rust_win_dbg_logger_init_error, Error);