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}