Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 90 additions & 21 deletions proxy_agent_shared/src/windows_events/evt_writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,25 @@

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
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 {
Expand All @@ -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<std::sync::mpsc::SyncSender<EventLogMessage>>,
worker: Option<std::thread::JoinHandle<()>>,
}

impl WindowsEventWriter {
Expand All @@ -48,33 +58,98 @@ 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) {
self.write_with_event_id(log_level, 0, message);
}

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<EventLogMessage>,
startup_sender: std::sync::mpsc::SyncSender<std::io::Result<()>>,
) {
// 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,
Expand All @@ -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)]
Expand Down
Loading