winlog 0.2.6

A simple Rust log backend to send messages to the Windows event log.
extern crate log;
extern crate winapi;
extern crate winreg;

#[cfg(feature = "env_logger")]
extern crate env_logger;

use log::{Level, LevelFilter, Metadata, Record, SetLoggerError};
use std::{error, ffi::OsStr, fmt, io, iter::once, os::windows::ffi::OsStrExt};
use winapi::{
    shared::ntdef::{HANDLE, NULL},
    um::{
        winbase::{DeregisterEventSource, RegisterEventSourceW, ReportEventW},
        winnt::{EVENTLOG_ERROR_TYPE, EVENTLOG_INFORMATION_TYPE, EVENTLOG_WARNING_TYPE},
    },
};
use winreg::{enums::*, RegKey};

mod eventmsgs;
use eventmsgs::{MSG_DEBUG, MSG_ERROR, MSG_INFO, MSG_TRACE, MSG_WARNING};

const REG_BASEKEY: &str = "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application";

#[derive(Debug)]
pub enum Error {
    Io(io::Error),
    ExePathNotFound,
    RegisterSourceFailed,
}

impl From<io::Error> for Error {
    fn from(err: io::Error) -> Error {
        Error::Io(err)
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::Io(ref err) => write!(f, "IO error: {}", err),
            Error::ExePathNotFound => write!(f, "Could not determine executable path"),
            Error::RegisterSourceFailed => write!(f, "Call to RegisterEventSource failed"),
        }
    }
}

impl error::Error for Error {
    fn cause(&self) -> Option<&dyn error::Error> {
        match *self {
            Error::Io(ref err) => Some(err),
            Error::ExePathNotFound => None,
            Error::RegisterSourceFailed => None,
        }
    }
}

#[cfg(not(feature = "env_logger"))]
pub struct Filter {}
#[cfg(not(feature = "env_logger"))]
impl Filter {
    fn enabled(&self, _metadata: &Metadata) -> bool {
        true
    }
    fn matches(&self, _record: &Record) -> bool {
        true
    }
}
#[cfg(not(feature = "env_logger"))]
fn make_filter() -> Filter {
    Filter {}
}

#[cfg(feature = "env_logger")]
use env_logger::filter::Filter;
#[cfg(feature = "env_logger")]
fn make_filter() -> Filter {
    use env_logger::filter::Builder;
    let mut builder = Builder::from_env("RUST_LOG");
    builder.build()
}

pub struct WinLogger {
    handle: HANDLE,
    filter: Filter,
}

unsafe impl Send for WinLogger {}
unsafe impl Sync for WinLogger {}

fn discard_result<R, E>(_result: &Result<R, E>) {
    ()
}

fn win_string(s: &str) -> Vec<u16> {
    OsStr::new(s).encode_wide().chain(once(0)).collect()
}

pub fn deregister(name: &str) {
    discard_result(&try_deregister(name))
}

pub fn init(name: &str) -> Result<(), SetLoggerError> {
    log::set_boxed_logger(Box::new(WinLogger::new(name)))
        .map(|()| log::set_max_level(LevelFilter::Trace))
}

pub fn register(name: &str) {
    discard_result(&try_register(name))
}

pub fn try_deregister(name: &str) -> Result<(), Error> {
    let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
    let cur_ver = hklm.open_subkey(REG_BASEKEY)?;
    cur_ver.delete_subkey(name).map_err(From::from)
}

pub fn try_register(name: &str) -> Result<(), Error> {
    let current_exe = ::std::env::current_exe()?;
    let exe_path = current_exe.to_str().ok_or(Error::ExePathNotFound)?;

    let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
    let cur_ver = hklm.open_subkey(REG_BASEKEY)?;
    let app_key = cur_ver.create_subkey(name)?;
    app_key
        .set_value("EventMessageFile", &exe_path)
        .map_err(From::from)
}

impl WinLogger {
    pub fn new(name: &str) -> WinLogger {
        Self::try_new(name).unwrap_or(WinLogger {
            handle: NULL,
            filter: make_filter(),
        })
    }

    pub fn try_new(name: &str) -> Result<WinLogger, Error> {
        let wide_name = win_string(name);
        let handle = unsafe { RegisterEventSourceW(std::ptr::null_mut(), wide_name.as_ptr()) };

        if handle == NULL {
            Err(Error::RegisterSourceFailed)
        } else {
            Ok(WinLogger {
                handle,
                filter: make_filter(),
            })
        }
    }
}

impl Drop for WinLogger {
    fn drop(&mut self) -> () {
        unsafe { DeregisterEventSource(self.handle) };
    }
}

impl log::Log for WinLogger {
    fn enabled(&self, metadata: &Metadata) -> bool {
        self.filter.enabled(metadata)
    }

    fn log(&self, record: &Record) {
        if self.filter.matches(record) {
            let type_and_msg = match record.level() {
                Level::Error => (EVENTLOG_ERROR_TYPE, MSG_ERROR),
                Level::Warn => (EVENTLOG_WARNING_TYPE, MSG_WARNING),
                Level::Info => (EVENTLOG_INFORMATION_TYPE, MSG_INFO),
                Level::Debug => (EVENTLOG_INFORMATION_TYPE, MSG_DEBUG),
                Level::Trace => (EVENTLOG_INFORMATION_TYPE, MSG_TRACE),
            };

            let msg = win_string(&format!("{:?}", record.args()));
            let mut vec = vec![msg.as_ptr()];

            unsafe {
                ReportEventW(
                    self.handle,
                    type_and_msg.0, // type
                    0,              // category
                    type_and_msg.1, // event id == resource msg id
                    std::ptr::null_mut(),
                    vec.len() as u16,
                    0,
                    vec.as_mut_ptr(),
                    std::ptr::null_mut(),
                )
            };
        }
    }

    fn flush(&self) {}
}