pub mod query;
pub mod render;
pub mod types;
use crate::error::{Error, EventLogError, EventLogQueryError, Result};
use crate::utils::to_utf16_nul;
use crate::wait::Wait;
use query::QueryBuilder;
use std::path::Path;
use types::{ChannelFilter, Event, EventQueryResult, RenderFormat};
use windows::Win32::Foundation::GetLastError;
use windows::Win32::System::EventLog::*;
use windows::core::PWSTR;
pub struct EventLog {
handle: EVT_HANDLE,
channel_or_path: String,
is_file: bool,
}
impl EventLog {
pub fn open(channel_name: &str) -> Result<Self> {
let channel_wide: Vec<u16> = channel_name
.encode_utf16()
.chain(std::iter::once(0))
.collect();
let handle = unsafe {
EvtOpenLog(
EVT_HANDLE::default(), PWSTR(channel_wide.as_ptr() as *mut u16),
EvtOpenChannelPath.0,
)
}
.map_err(|_| {
Error::EventLog(EventLogError::QueryFailed(EventLogQueryError::new(
channel_name.to_string(),
"Channel not found or access denied",
)))
})?;
Ok(EventLog {
handle,
channel_or_path: channel_name.to_string(),
is_file: false,
})
}
pub fn open_file(path: &Path) -> Result<Self> {
let path_str = path.to_str().ok_or_else(|| {
Error::EventLog(EventLogError::QueryFailed(EventLogQueryError::new(
"file_path",
"Invalid file path",
)))
})?;
let path_wide = to_utf16_nul(path_str);
let handle = unsafe {
EvtOpenLog(
EVT_HANDLE::default(), PWSTR(path_wide.as_ptr() as *mut u16),
EvtOpenFilePath.0,
)
}
.map_err(|_| {
Error::EventLog(EventLogError::QueryFailed(EventLogQueryError::new(
path_str.to_string(),
"Log file not found or cannot be read",
)))
})?;
Ok(EventLog {
handle,
channel_or_path: path_str.to_string(),
is_file: true,
})
}
pub fn list_channels() -> Result<Vec<String>> {
Self::list_channels_filtered(ChannelFilter::Operational)
}
pub fn list_channels_filtered(filter: ChannelFilter) -> Result<Vec<String>> {
let mut channels = Vec::new();
let enum_handle =
unsafe { EvtOpenChannelEnum(EVT_HANDLE::default(), 0) }.map_err(|_| {
Error::EventLog(EventLogError::QueryFailed(EventLogQueryError::new(
"channels",
"Failed to enumerate channels",
)))
})?;
let mut buffer = [0u16; 1024];
loop {
let mut buffer_used = 0u32;
let result =
unsafe { EvtNextChannelPath(enum_handle, Some(&mut buffer[..]), &mut buffer_used) };
if result.is_err() {
break;
}
let channel_name = String::from_utf16_lossy(&buffer[..buffer_used as usize]);
if should_include_channel(&channel_name, filter) {
channels.push(channel_name.to_string());
}
}
unsafe {
let _ = EvtClose(enum_handle);
}
Ok(channels)
}
pub fn query(&self, builder: &QueryBuilder) -> Result<EventQueryResult> {
let mut result = EventQueryResult::default();
self.query_internal(builder, &mut result, None)?;
Ok(result)
}
pub fn query_stream(&self, xpath: &str) -> Result<EventQuery> {
let xpath_wide = to_utf16_nul(xpath);
let channel_wide: Vec<u16> = self
.channel_or_path
.encode_utf16()
.chain(std::iter::once(0))
.collect();
let flags = if self.is_file {
EvtQueryFilePath.0
} else {
EvtQueryChannelPath.0
};
let query_handle = unsafe {
EvtQuery(
EVT_HANDLE::default(), PWSTR(channel_wide.as_ptr() as *mut u16),
PWSTR(xpath_wide.as_ptr() as *mut u16),
flags,
)
}
.map_err(|_| {
Error::EventLog(EventLogError::QueryFailed(EventLogQueryError::new(
self.channel_or_path.clone(),
"Failed to create query handle",
)))
})?;
Ok(EventQuery {
handle: query_handle,
batch_buffer: vec![0isize; 64], batch_timeout_ms: 1000,
render_format: RenderFormat::Values,
include_event_data: false,
parse_message: false,
#[cfg(feature = "serde")]
xml_buffer: String::with_capacity(16384),
variant_buffer: Vec::with_capacity(8192),
corrupted: Vec::new(),
total_processed: 0,
})
}
fn query_internal(
&self,
builder: &QueryBuilder,
result: &mut EventQueryResult,
_max_events: Option<usize>,
) -> Result<()> {
let mut query = self.query_stream(&builder.build_xpath())?;
let mut batch = Vec::with_capacity(64);
while query.next_batch(&mut batch)? > 0 {
result.events.append(&mut batch);
}
result.corrupted = query.corrupted.clone();
result.total_processed = query.total_processed;
Ok(())
}
}
impl Drop for EventLog {
fn drop(&mut self) {
if !self.handle.is_invalid() {
unsafe {
let _ = EvtClose(self.handle);
}
}
}
}
pub struct EventQuery {
handle: EVT_HANDLE,
batch_buffer: Vec<isize>, batch_timeout_ms: u32,
render_format: RenderFormat,
include_event_data: bool,
parse_message: bool,
#[cfg(feature = "serde")]
xml_buffer: String,
#[allow(dead_code)]
variant_buffer: Vec<u8>, corrupted: Vec<crate::evt::types::CorruptedEvent>,
total_processed: usize,
}
impl EventQuery {
pub fn set_batch_timeout(&mut self, timeout: std::time::Duration) {
self.batch_timeout_ms = timeout.as_millis().min(u32::MAX as u128) as u32;
}
pub fn with_event_data(mut self) -> Self {
self.include_event_data = true;
self
}
pub fn with_message(mut self) -> Self {
self.parse_message = true;
self
}
pub fn set_render_format(&mut self, format: RenderFormat) {
self.render_format = format;
}
pub fn next_batch(&mut self, out_events: &mut Vec<Event>) -> Result<usize> {
self.next_batch_with_filter(out_events, |_| true)
}
pub fn next_batch_or_cancel(
&mut self,
out_events: &mut Vec<Event>,
cancel: &Wait,
) -> Result<usize> {
if cancel.is_signaled()? {
out_events.clear();
return Ok(0);
}
self.next_batch(out_events)
}
pub fn next_batch_with_filter<F>(
&mut self,
out_events: &mut Vec<Event>,
filter: F,
) -> Result<usize>
where
F: Fn(&Event) -> bool,
{
out_events.clear();
let mut returned = 0u32;
let result = unsafe {
EvtNext(
self.handle,
self.batch_buffer.as_mut_slice(),
self.batch_timeout_ms,
0,
&mut returned,
)
};
if result.is_err() {
let error_code = unsafe { GetLastError() };
if error_code.0 == 259 {
return Ok(0);
}
return Err(Error::EventLog(EventLogError::QueryFailed(
EventLogQueryError::with_code("", "EvtNext failed", error_code.0 as i32),
)));
}
for i in 0..returned as usize {
let handle_val = self.batch_buffer[i];
let handle = EVT_HANDLE(handle_val);
match render::render_event(
handle,
self.render_format,
self.include_event_data,
self.parse_message,
) {
Ok(event) => {
if filter(&event) {
out_events.push(event);
}
self.total_processed += 1;
}
Err(corruption_info) => {
self.corrupted.push(corruption_info);
self.total_processed += 1;
}
}
unsafe {
let _ = EvtClose(handle);
}
}
Ok(out_events.len())
}
pub fn next_batch_raw_with_filter<T, F, P>(
&mut self,
out_events: &mut Vec<T>,
mut converter: F,
filter: P,
) -> Result<usize>
where
F: FnMut(EVT_HANDLE) -> Result<T>,
P: Fn(&T) -> bool,
{
out_events.clear();
let mut returned = 0u32;
let result = unsafe {
EvtNext(
self.handle,
self.batch_buffer.as_mut_slice(),
self.batch_timeout_ms,
0,
&mut returned,
)
};
if result.is_err() {
let error_code = unsafe { GetLastError() };
if error_code.0 == 259 {
return Ok(0);
}
return Err(Error::EventLog(EventLogError::QueryFailed(
EventLogQueryError::with_code("", "EvtNext failed", error_code.0 as i32),
)));
}
for i in 0..returned as usize {
let handle_val = self.batch_buffer[i];
let handle = EVT_HANDLE(handle_val);
match converter(handle) {
Ok(event) => {
if filter(&event) {
out_events.push(event);
}
self.total_processed += 1;
}
Err(_) => {
self.total_processed += 1;
}
}
unsafe {
let _ = EvtClose(handle);
}
}
Ok(out_events.len())
}
pub fn next_batch_with_results(
&mut self,
out_events: &mut Vec<std::result::Result<Event, types::CorruptedEvent>>,
) -> Result<usize> {
out_events.clear();
let mut returned = 0u32;
let result = unsafe {
EvtNext(
self.handle,
self.batch_buffer.as_mut_slice(),
self.batch_timeout_ms,
0,
&mut returned,
)
};
if result.is_err() {
let error_code = unsafe { GetLastError() };
if error_code.0 == 259 {
return Ok(0);
}
return Err(Error::EventLog(EventLogError::QueryFailed(
EventLogQueryError::with_code("", "EvtNext failed", error_code.0 as i32),
)));
}
for i in 0..returned as usize {
let handle_val = self.batch_buffer[i];
let handle = EVT_HANDLE(handle_val);
match render::render_event(
handle,
self.render_format,
self.include_event_data,
self.parse_message,
) {
Ok(event) => {
out_events.push(Ok(event));
self.total_processed += 1;
}
Err(corruption_info) => {
out_events.push(Err(corruption_info));
self.total_processed += 1;
}
}
unsafe {
let _ = EvtClose(handle);
}
}
Ok(out_events.len())
}
#[cfg(feature = "serde")]
pub fn next_batch_deserialize<T>(&mut self, out_events: &mut Vec<T>) -> Result<usize>
where
T: serde::de::DeserializeOwned,
{
out_events.clear();
let mut returned = 0u32;
let result = unsafe {
EvtNext(
self.handle,
self.batch_buffer.as_mut_slice(),
self.batch_timeout_ms,
0,
&mut returned,
)
};
if result.is_err() {
let error_code = unsafe { GetLastError() };
if error_code.0 == 259 {
return Ok(0);
}
return Err(Error::EventLog(EventLogError::QueryFailed(
EventLogQueryError::with_code("", "EvtNext failed", error_code.0 as i32),
)));
}
for i in 0..returned as usize {
let handle_val = self.batch_buffer[i];
let handle = EVT_HANDLE(handle_val);
self.xml_buffer.clear();
let mut buffer = vec![0u8; 16384];
let mut buffer_used = 0u32;
let mut prop_count = 0u32;
let render_result = unsafe {
EvtRender(
EVT_HANDLE::default(),
handle,
EvtRenderEventXml.0,
buffer.len() as u32,
Some(buffer.as_mut_ptr() as *mut std::ffi::c_void),
&mut buffer_used,
&mut prop_count,
)
};
if render_result.is_ok() {
let xml_bytes = &buffer[..buffer_used as usize];
let xml_str = String::from_utf16_lossy(unsafe {
std::slice::from_raw_parts(
xml_bytes.as_ptr() as *const u16,
xml_bytes.len() / 2,
)
});
self.xml_buffer.clear();
self.xml_buffer.push_str(&xml_str);
match quick_xml::de::from_str::<T>(&self.xml_buffer) {
Ok(event) => {
out_events.push(event);
self.total_processed += 1;
}
Err(_) => {
self.total_processed += 1;
}
}
}
unsafe {
let _ = EvtClose(handle);
}
}
Ok(out_events.len())
}
}
impl Drop for EventQuery {
fn drop(&mut self) {
if !self.handle.is_invalid() {
unsafe {
let _ = EvtClose(self.handle);
}
}
}
}
fn should_include_channel(channel_name: &str, filter: ChannelFilter) -> bool {
match filter {
ChannelFilter::All => true,
ChannelFilter::Operational => {
!channel_name.contains("Analytic") && !channel_name.contains("Debug")
}
ChannelFilter::AdminOrHigher => {
!channel_name.contains("Analytic") && !channel_name.contains("Debug")
}
ChannelFilter::IncludeAnalytic => true,
}
}