winlog2/
lib.rs

1//! A simple [Rust log](https://docs.rs/log/latest/log/) backend
2//! to send messages to [Windows event log](https://docs.microsoft.com/en-us/windows/desktop/eventlog/event-logging).
3
4#![warn(missing_docs)]
5
6use log::{Level, LevelFilter, Metadata, Record, SetLoggerError};
7use widestring::U16CString;
8use windows_sys::Win32::{
9    Foundation::HANDLE,
10    System::EventLog::{
11        DeregisterEventSource, RegisterEventSourceW, ReportEventW, EVENTLOG_ERROR_TYPE,
12        EVENTLOG_INFORMATION_TYPE, EVENTLOG_WARNING_TYPE,
13    },
14};
15use winreg::{enums::*, RegKey};
16
17// Generated from MC.
18const MSG_ERROR: u32 = 0xC0000001;
19const MSG_WARNING: u32 = 0x80000002;
20const MSG_INFO: u32 = 0x40000003;
21const MSG_DEBUG: u32 = 0x40000004;
22const MSG_TRACE: u32 = 0x40000005;
23
24const REG_BASEKEY: &str = "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application";
25
26/// Error type of methods in this crate.
27#[derive(Debug, thiserror::Error)]
28pub enum Error {
29    /// System error.
30    #[error("IO error: {0}")]
31    Io(#[from] std::io::Error),
32    /// String convertion error.
33    #[error("String convertion failed")]
34    StringConvertionFailed,
35    /// Calling [`log::set_boxed_logger`] failed.
36    #[error("Set logger failed: {0}")]
37    SetLoggerFailed(#[from] SetLoggerError),
38}
39
40#[cfg(not(feature = "env_logger"))]
41struct Filter {}
42#[cfg(not(feature = "env_logger"))]
43impl Filter {
44    fn enabled(&self, _metadata: &Metadata) -> bool {
45        true
46    }
47    fn matches(&self, _record: &Record) -> bool {
48        true
49    }
50}
51#[cfg(not(feature = "env_logger"))]
52fn make_filter() -> Filter {
53    Filter {}
54}
55
56#[cfg(feature = "env_logger")]
57use env_logger::Logger as Filter;
58#[cfg(feature = "env_logger")]
59fn make_filter() -> Filter {
60    use env_logger::Builder;
61    let mut builder = Builder::from_env("RUST_LOG");
62    builder.build()
63}
64
65struct WinLogger {
66    handle: HANDLE,
67    filter: Filter,
68}
69
70/// Initialize the global logger as the windows event logger.
71/// See document of [`register`].
72pub fn init(name: &str) -> Result<(), Error> {
73    log::set_boxed_logger(Box::new(WinLogger::new(name)?))?;
74    log::set_max_level(LevelFilter::Trace);
75    Ok(())
76}
77
78/// Attempt to remove the event source registry.
79/// See document of [`register`].
80pub fn deregister(name: &str) -> Result<(), Error> {
81    let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
82    let cur_ver = hklm.open_subkey(REG_BASEKEY)?;
83    cur_ver.delete_subkey(name).map_err(From::from)
84}
85
86/// Attempt to add the event source registry.
87///
88/// Any event source sould be registried first.
89/// You need to call [`register`] when installing the program,
90/// and call [`deregister`] when uninstalling the program.
91pub fn register(name: &str) -> Result<(), Error> {
92    let current_exe = ::std::env::current_exe()?;
93    let exe_path = current_exe.to_str().ok_or(Error::StringConvertionFailed)?;
94
95    let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
96    let cur_ver = hklm.open_subkey(REG_BASEKEY)?;
97    let (app_key, _) = cur_ver.create_subkey(name)?;
98    app_key.set_value("EventMessageFile", &exe_path)?;
99    app_key.set_value("TypesSupported", &7u32)?;
100    Ok(())
101}
102
103impl WinLogger {
104    pub fn new(name: &str) -> Result<WinLogger, Error> {
105        let name = U16CString::from_str(name).map_err(|_| Error::StringConvertionFailed)?;
106        let handle = unsafe { RegisterEventSourceW(std::ptr::null_mut(), name.as_ptr()) };
107
108        if handle.is_null() {
109            Err(Error::Io(std::io::Error::last_os_error()))
110        } else {
111            Ok(WinLogger {
112                handle,
113                filter: make_filter(),
114            })
115        }
116    }
117}
118
119impl Drop for WinLogger {
120    fn drop(&mut self) {
121        unsafe { DeregisterEventSource(self.handle) };
122    }
123}
124
125// SAFETY: event source should be thread safe
126unsafe impl Send for WinLogger {}
127unsafe impl Sync for WinLogger {}
128
129impl log::Log for WinLogger {
130    fn enabled(&self, metadata: &Metadata) -> bool {
131        self.filter.enabled(metadata)
132    }
133
134    fn log(&self, record: &Record) {
135        if self.filter.matches(record) {
136            let level = record.level();
137            let (wtype, dweventid) = match level {
138                Level::Error => (EVENTLOG_ERROR_TYPE, MSG_ERROR),
139                Level::Warn => (EVENTLOG_WARNING_TYPE, MSG_WARNING),
140                Level::Info => (EVENTLOG_INFORMATION_TYPE, MSG_INFO),
141                Level::Debug => (EVENTLOG_INFORMATION_TYPE, MSG_DEBUG),
142                Level::Trace => (EVENTLOG_INFORMATION_TYPE, MSG_TRACE),
143            };
144
145            let msg = U16CString::from_str_truncate(format!("{}", record.args()));
146            let msg_ptr = msg.as_ptr();
147
148            unsafe {
149                ReportEventW(
150                    self.handle,
151                    wtype,     // type
152                    0,         // category
153                    dweventid, // event id == resource msg id
154                    std::ptr::null_mut(),
155                    1,
156                    0,
157                    &msg_ptr,
158                    std::ptr::null_mut(),
159                )
160            };
161        }
162    }
163
164    fn flush(&self) {}
165}