diff --git a/proxy_agent_shared/src/windows_events/evt_writer.rs b/proxy_agent_shared/src/windows_events/evt_writer.rs index 87b3fe9b..97e632fa 100644 --- a/proxy_agent_shared/src/windows_events/evt_writer.rs +++ b/proxy_agent_shared/src/windows_events/evt_writer.rs @@ -7,9 +7,9 @@ use crate::error::Error; use crate::logger::LoggerLevel; +use crate::misc_helpers; use crate::result::Result; use windows_sys::core::PWSTR; -use windows_sys::Win32::Foundation::HANDLE; use windows_sys::Win32::System::EventLog::{ DeregisterEventSource, RegisterEventSourceW, ReportEventW, }; // advapi32.dll @@ -17,6 +17,15 @@ use windows_sys::Win32::System::EventLog::{ EVENTLOG_ERROR_TYPE, EVENTLOG_INFORMATION_TYPE, EVENTLOG_WARNING_TYPE, REPORT_EVENT_TYPE, }; +const EVENT_QUEUE_CAPACITY: usize = 1024; +const MAX_EVENT_MESSAGE_LENGTH: usize = 32 * 1024; + +struct EventLogMessage { + log_level: LoggerLevel, + event_id: u32, + message: String, +} + /// Converts a `LoggerLevel` to a `REPORT_EVENT_TYPE`. /// This function maps the logging levels to the corresponding Windows Event Log types. fn to_event_level(level: LoggerLevel) -> REPORT_EVENT_TYPE { @@ -33,7 +42,8 @@ fn to_event_level(level: LoggerLevel) -> REPORT_EVENT_TYPE { /// It registers an event source and provides a method to write logs. /// It also ensures that the event source is deregistered when the struct is dropped. pub struct WindowsEventWriter { - event_source: HANDLE, + sender: Option>, + worker: Option>, } impl WindowsEventWriter { @@ -48,17 +58,32 @@ impl WindowsEventWriter { ); crate::windows::set_reg_string(&key_name, "EventMessageFile", value)?; - let source_name_wide = super::to_wide(source_name); - let event_source = - unsafe { RegisterEventSourceW(std::ptr::null(), source_name_wide.as_ptr()) }; - if event_source == 0 { - return Err(Error::WindowsApi( - "RegisterEventSourceW".to_string(), - std::io::Error::last_os_error(), - )); + let (sender, receiver) = std::sync::mpsc::sync_channel(EVENT_QUEUE_CAPACITY); + let (startup_sender, startup_receiver) = std::sync::mpsc::sync_channel(0); + let source_name = source_name.to_string(); + let worker = std::thread::Builder::new() + .name("windows-event-writer".to_string()) + .spawn(move || run_event_writer(source_name, receiver, startup_sender))?; + + match startup_receiver.recv() { + Ok(Ok(())) => {} + Ok(Err(error)) => { + _ = worker.join(); + return Err(Error::WindowsApi("RegisterEventSourceW".to_string(), error)); + } + Err(error) => { + _ = worker.join(); + return Err(Error::Io(std::io::Error::new( + std::io::ErrorKind::BrokenPipe, + format!("Windows event writer failed to start: {error}"), + ))); + } } - Ok(WindowsEventWriter { event_source }) + Ok(WindowsEventWriter { + sender: Some(sender), + worker: Some(worker), + }) } pub fn write(&self, log_level: LoggerLevel, message: String) { @@ -66,15 +91,65 @@ impl WindowsEventWriter { } pub fn write_with_event_id(&self, log_level: LoggerLevel, event_id: u32, message: String) { + let mut message = message; + misc_helpers::truncate_to_char_boundary(&mut message, MAX_EVENT_MESSAGE_LENGTH); + + if let Some(sender) = &self.sender { + // Never block a request or runtime thread on redaction, event-log I/O, or queue space. + if let Err(error) = sender.try_send(EventLogMessage { + log_level, + event_id, + message, + }) { + eprintln!("Failed to enqueue Windows event log message: {error}"); + } + } + } +} + +impl Drop for WindowsEventWriter { + fn drop(&mut self) { + // Closing the channel lets the backend drain queued events before releasing the source. + self.sender.take(); + if let Some(worker) = self.worker.take() { + _ = worker.join(); + } + } +} + +/// Runs the event writer in a background thread, +/// processing messages from the receiver and writing them to the Windows Event Log. +fn run_event_writer( + source_name: String, + receiver: std::sync::mpsc::Receiver, + startup_sender: std::sync::mpsc::SyncSender>, +) { + // Register the event source with the Windows Event Log API. + let source_name_wide = super::to_wide(&source_name); + let event_source = unsafe { RegisterEventSourceW(std::ptr::null(), source_name_wide.as_ptr()) }; + if event_source == 0 { + _ = startup_sender.send(Err(std::io::Error::last_os_error())); + return; + } + // Notify the main thread that the event writer has started successfully. + if startup_sender.send(Ok(())).is_err() { + unsafe { DeregisterEventSource(event_source) }; + return; + } + + for event in receiver { + // This single backend worker keeps regex cache concurrency at one and keeps both redaction + // and the blocking Windows API call off request/runtime threads. + let message = crate::secrets_redactor::redact_secrets_string(event.message); let wide_message = super::to_wide(&message); let wide_message_ptrs: [PWSTR; 1] = [wide_message.as_ptr() as PWSTR]; unsafe { ReportEventW( - self.event_source, - to_event_level(log_level), + event_source, + to_event_level(event.log_level), 0, - event_id, + event.event_id, std::ptr::null_mut(), 1, 0, @@ -83,14 +158,8 @@ impl WindowsEventWriter { ); } } -} -impl Drop for WindowsEventWriter { - fn drop(&mut self) { - unsafe { - DeregisterEventSource(self.event_source); - } - } + unsafe { DeregisterEventSource(event_source) }; } #[cfg(test)]