win_service_logger/
lib.rs

1//! A logger which writes log messages to the Windows Event Viewer
2//!
3//! # Example
4//!
5//! ```
6//! extern crate win_service_logger;
7//! #[macro_use] extern crate log;
8//!
9//! fn main() {
10//!     win_service_logger::init();
11//!     trace!("Hello from Rust!");
12//!
13//!     warn!("This will be a warning in Event Viewer!");
14//!     error!("Bad");
15//! }
16
17use std::cell::UnsafeCell;
18use std::ffi::CString;
19use std::sync::Once;
20
21use log::{Level, Metadata, Record};
22use winapi::um::winnt::HANDLE;
23
24pub struct Logger {
25    handle: UnsafeCell<HANDLE>,
26    handle_init: Once,
27    log_name: &'static str,
28}
29
30unsafe impl Send for Logger {}
31unsafe impl Sync for Logger {}
32
33pub static LOGGER: Logger = Logger::new("Rust Application");
34
35/// Initializes the global logger with a windows service logger
36///
37/// # Panics
38///
39/// This function will panic if a global logger has already been set
40/// Use [`try_init`] for a fallable function
41pub fn init() {
42    try_init().unwrap();
43}
44
45/// Initializes the global logger with a windows service logger
46///
47/// # Errors
48///
49/// This function fails if a global logger has already been set
50pub fn try_init() -> Result<(), log::SetLoggerError> {
51    log::set_logger(&LOGGER).map(|()| log::set_max_level(log::LevelFilter::Debug))
52}
53
54/// Initializes the global logger with a windows service logger.
55///
56/// This function leaks a single `Logger` to the heap in order to give a static reference to log
57///
58/// # Errors
59///
60/// This function fails if a global logger has already been set
61pub fn try_init_with_name(name: &'static str) -> Result<(), log::SetLoggerError> {
62    let logger = Box::leak(Box::new(Logger::new(name)));
63    log::set_logger(logger).map(|()| log::set_max_level(log::LevelFilter::Debug))
64}
65
66/// Initializes the global logger with a windows service logger
67///
68/// This function leaks a single `Logger` to the heap in order to give a static reference to log
69///
70/// # Panics
71///
72/// This function will panic if a global logger has already been set
73pub fn init_with_name(name: &'static str) {
74    let logger = Box::leak(Box::new(Logger::new(name)));
75    log::set_logger(logger)
76        .map(|()| log::set_max_level(log::LevelFilter::Debug))
77        .unwrap();
78}
79
80impl Logger {
81    const fn new(log_name: &'static str) -> Self {
82        Self {
83            handle: UnsafeCell::new(std::ptr::null_mut()),
84            log_name,
85            handle_init: Once::new(),
86        }
87    }
88}
89
90impl log::Log for Logger {
91    fn enabled(&self, metadata: &Metadata) -> bool {
92        metadata.level() <= Level::Debug
93    }
94
95    fn log(&self, record: &Record) {
96        if self.enabled(record.metadata()) {
97            // We use a Once and unsafe cell so that we can lazily initialize `self.handle`
98            // We need to have Self in a static, so new must be const
99            // `self.handle` is initialized once and then read multiple times so doing it this way
100            // means we don't need to acquire a mutex every time to read `self.handle`
101            self.handle_init.call_once(|| {
102                let c_str = CString::new(self.log_name).unwrap();
103                // # Safety:
104                // 1. `c_str` is a valid null terminated string
105                // 2. WinAPI call
106                let handle = unsafe {
107                    winapi::um::winbase::RegisterEventSourceA(std::ptr::null_mut(), c_str.as_ptr())
108                };
109                // # Safety.
110                // We are inside a Once's init block therefore we have exclusive access
111                // to self.handle
112                unsafe { *self.handle.get() = handle };
113            });
114            let msg = format!(
115                "{}({}): {} - {}",
116                record.file().unwrap_or("<unknown>"),
117                record.line().unwrap_or(0),
118                record.level(),
119                record.args()
120            );
121
122            let event_type = match record.metadata().level() {
123                Level::Trace => winapi::um::winnt::EVENTLOG_INFORMATION_TYPE,
124                Level::Debug => winapi::um::winnt::EVENTLOG_INFORMATION_TYPE,
125                Level::Info => winapi::um::winnt::EVENTLOG_INFORMATION_TYPE,
126                Level::Warn => winapi::um::winnt::EVENTLOG_WARNING_TYPE,
127                Level::Error => winapi::um::winnt::EVENTLOG_ERROR_TYPE,
128            };
129            let wide_msg = widestring::U16CString::from_str(msg).unwrap();
130            let mut strings = [wide_msg.as_ptr()];
131
132            // # Safety:
133            // 1. The init block has completed so there are no exclusive references to `self.handle`.
134            // 2. The init block has completed so we have established a happens before relationship
135            //    with the initializing thread. Therefore we will see the initialized value
136            let handle = unsafe { *self.handle.get() };
137
138            // # Safety:
139            // 1. strings is a pointer to a null terminated message utf-16 string
140            // 2. The length of strings is 1 and we pass one as the length
141            // 3. WinAPI call
142            unsafe {
143                winapi::um::winbase::ReportEventW(
144                    handle,
145                    event_type,
146                    0,
147                    0,
148                    std::ptr::null_mut(),
149                    1, //length
150                    0,
151                    &mut strings as *mut *const _,
152                    std::ptr::null_mut(),
153                )
154            };
155        }
156    }
157
158    fn flush(&self) {}
159}
160
161impl Drop for Logger {
162    fn drop(&mut self) {
163        // # Safety:
164        // WinAPI call
165        let handle = *self.handle.get_mut();
166        let _ = unsafe { winapi::um::winbase::DeregisterEventSource(handle) };
167    }
168}