Skip to content

Commit

Permalink
Added event rendering into the event reader
Browse files Browse the repository at this point in the history
  • Loading branch information
jschick04 authored and bill-long committed Nov 11, 2024
1 parent 9b7b4c1 commit 8f3c1f2
Show file tree
Hide file tree
Showing 11 changed files with 351 additions and 167 deletions.
123 changes: 123 additions & 0 deletions src/EventLogExpert.Eventing/Helpers/EventMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,43 @@ internal enum EvtLogPropertyId
Full,
}

internal enum EvtRenderContextFlags
{
Values = 0,
System = 1,
User = 2
}

internal enum EvtRenderFlags
{
EventValues = 0,
EventXml = 1,
Bookmark = 2
}

internal enum EvtSystemPropertyId
{
ProviderName = 0,
ProviderGuid = 1,
EventId = 2,
Qualifiers = 3,
Level = 4,
Task = 5,
Opcode = 6,
Keywords = 7,
TimeCreated = 8,
EventRecordId = 9,
ActivityId = 10,
RelatedActivityId = 11,
ProcessID = 12,
ThreadID = 13,
Channel = 14,
Computer = 15,
UserID = 16,
Version = 17,
PropertyIdEND = 18,
}

internal enum EvtVariantType
{
Null = 0,
Expand Down Expand Up @@ -142,6 +179,12 @@ internal static partial class EventMethods
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool EvtClose(IntPtr handle);

[LibraryImport(EventLogApi, SetLastError = true)]
internal static partial EventLogHandle EvtCreateRenderContext(
int valuePathsCount,
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr)][Out] string[]? valuePaths,
EvtRenderContextFlags flags);

[LibraryImport(EventLogApi, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool EvtGetLogInfo(
Expand All @@ -151,6 +194,16 @@ internal static partial bool EvtGetLogInfo(
IntPtr propertyValueBuffer,
out int propertyValueBufferUsed);

[LibraryImport(EventLogApi, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool EvtNext(
EventLogHandle resultSet,
int eventsSize,
[MarshalAs(UnmanagedType.LPArray)][Out] IntPtr[] events,
int timeout,
int flags,
ref int returned);

[LibraryImport(EventLogApi, SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool EvtNextChannelPath(
Expand Down Expand Up @@ -184,6 +237,17 @@ internal static partial EventLogHandle EvtQuery(
[MarshalAs(UnmanagedType.LPWStr)] string? query,
int flags);

[LibraryImport(EventLogApi, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool EvtRender(
EventLogHandle context,
EventLogHandle fragment,
EvtRenderFlags flags,
int bufferSize,
IntPtr buffer,
out int bufferUsed,
out int propertyCount);

[LibraryImport(EventLogApi, SetLastError = true)]
internal static partial EventLogHandle EvtSeek(
EventLogHandle resultSet,
Expand All @@ -192,6 +256,65 @@ internal static partial EventLogHandle EvtSeek(
int timeout,
SeekFlags flags);

/// <summary>Converts an event buffer that was returned from EvtRender to an <see cref="EventProperties"/></summary>
/// <param name="eventBuffer">Pointer to a buffer returned from EvtRender</param>
/// <param name="propertyCount">Property count returned from EvtRender</param>
/// <returns></returns>
internal static EventProperties GetEventProperties(IntPtr eventBuffer, int propertyCount)
{
EventProperties properties = new();

for (int i = 0; i < propertyCount; i++)
{
var property = Marshal.PtrToStructure<EvtVariant>(eventBuffer + (i * Marshal.SizeOf<EvtVariant>()));

switch (i)
{
case (int)EvtSystemPropertyId.ActivityId:
properties.ActivityId = (Guid?)ConvertVariant(property);
break;
case (int)EvtSystemPropertyId.Computer:
properties.ComputerName = (string?)ConvertVariant(property);
break;
case (int)EvtSystemPropertyId.EventId:
properties.Id = (ushort?)ConvertVariant(property);
break;
case (int)EvtSystemPropertyId.Keywords:
properties.Keywords = (ulong?)ConvertVariant(property);
break;
case (int)EvtSystemPropertyId.Level:
properties.Level = (byte?)ConvertVariant(property);
break;
case (int)EvtSystemPropertyId.Channel:
properties.LogName = (string?)ConvertVariant(property);
break;
case (int)EvtSystemPropertyId.ProcessID:
properties.ProcessId = (uint?)ConvertVariant(property);
break;
case (int)EvtSystemPropertyId.EventRecordId:
properties.RecordId = (ulong?)ConvertVariant(property);
break;
case (int)EvtSystemPropertyId.ProviderName:
properties.Source = (string?)ConvertVariant(property);
break;
case (int)EvtSystemPropertyId.Task:
properties.Task = (ushort?)ConvertVariant(property);
break;
case (int)EvtSystemPropertyId.ThreadID:
properties.ThreadId = (uint?)ConvertVariant(property);
break;
case (int)EvtSystemPropertyId.TimeCreated:
properties.TimeCreated = (DateTime?)ConvertVariant(property);
break;
case (int)EvtSystemPropertyId.UserID:
properties.UserId = (SecurityIdentifier?)ConvertVariant(property);
break;
}
}

return properties;
}

internal static void ThrowEventLogException(int error)
{
var message = ResolverMethods.GetErrorMessage((uint)Converter.HResultFromWin32(error));
Expand Down
35 changes: 35 additions & 0 deletions src/EventLogExpert.Eventing/Models/EventProperties.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// // Copyright (c) Microsoft Corporation.
// // Licensed under the MIT License.

using System.Security.Principal;

namespace EventLogExpert.Eventing.Models;

public class EventProperties
{
public Guid? ActivityId { get; set; }

public string? ComputerName { get; set; }

public ushort? Id { get; set; }

public ulong? Keywords { get; set; }

public byte? Level { get; set; }

public string? LogName { get; set; }

public uint? ProcessId { get; set; }

public ulong? RecordId { get; set; }

public string? Source { get; set; }

public ushort? Task { get; set; }

public uint? ThreadId { get; set; }

public DateTime? TimeCreated { get; set; }

public SecurityIdentifier? UserId { get; set; }
}
112 changes: 81 additions & 31 deletions src/EventLogExpert.Eventing/Reader/EventLogReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,56 +3,106 @@

using EventLogExpert.Eventing.Helpers;
using EventLogExpert.Eventing.Models;
using System.Runtime.InteropServices;

namespace EventLogExpert.Eventing.Reader;

public sealed class EventLogReader : IDisposable
public sealed partial class EventLogReader(string path, PathType pathType) : IDisposable
{
private readonly EventLogHandle _handle;

private IntPtr[] _buffer;
private int _currentIndex;
private bool _disposed;
private int _total;

public EventLogReader(string path, PathType pathType)
{
//_handle = EventMethods.EvtQuery(EventLogSession.GlobalSession.Handle, path, null, pathType == PathType.LogName ? 1 : 2);
}

// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
// ~EventLogReader()
// {
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose(disposing: false);
// }
private readonly EventLogHandle _handle =
EventMethods.EvtQuery(EventLogSession.GlobalSession.Handle, path, null, (int)pathType);
private readonly SemaphoreSlim _semaphore = new(1);

public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}

private void Dispose(bool disposing)
public bool TryGetEvents(out EventProperties[] events, int batchSize = 100)
{
if (_disposed) { return; }
var buffer = new IntPtr[batchSize];
int count = 0;

_semaphore.Wait();

try
{
bool success = EventMethods.EvtNext(_handle, batchSize, buffer, 0, 0, ref count);

if (disposing)
if (!success)
{
events = [];
return false;
}
}
finally
{
// TODO: dispose managed state (managed objects)
_semaphore.Release();
}

while (_currentIndex < _total)
events = new EventProperties[count];

for (int i = 0; i < count; i++)
{
EventMethods.EvtClose(_buffer[_currentIndex]);
_currentIndex++;
using var eventHandle = new EventLogHandle(buffer[i]);

events[i] = RenderEvent(eventHandle, EvtRenderFlags.EventValues);
}

_handle.Dispose();
return true;
}

private static EventProperties RenderEvent(EventLogHandle eventHandle, EvtRenderFlags flag)
{
IntPtr buffer = IntPtr.Zero;

// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
_disposed = true;
try
{
bool success = EventMethods.EvtRender(
EventLogSession.GlobalSession.RenderContext,
eventHandle,
flag,
0,
IntPtr.Zero,
out int bufferUsed,
out int propertyCount);

int error = Marshal.GetLastWin32Error();

if (!success && error != 122 /* ERROR_INSUFFICIENT_BUFFER */)
{
EventMethods.ThrowEventLogException(error);
}

buffer = Marshal.AllocHGlobal(bufferUsed);

success = EventMethods.EvtRender(
EventLogSession.GlobalSession.RenderContext,
eventHandle,
flag,
bufferUsed,
buffer,
out bufferUsed,
out propertyCount);

error = Marshal.GetLastWin32Error();

if (!success)
{
EventMethods.ThrowEventLogException(error);
}

return EventMethods.GetEventProperties(buffer, propertyCount);
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}

private void Dispose(bool disposing)
{
_handle.Dispose();
}
}
25 changes: 24 additions & 1 deletion src/EventLogExpert.Eventing/Reader/EventLogSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public sealed partial class EventLogSession : IDisposable

internal EventLogHandle Handle { get; } = EventLogHandle.Zero;

internal EventLogHandle RenderContext { get; } = CreateRenderContext();

public void Dispose()
{
Dispose(disposing: true);
Expand All @@ -21,6 +23,7 @@ public void Dispose()

public EventLogInformation GetLogInformation(string logName, PathType pathType) => new(this, logName, pathType);

/// <summary>Gets an ordered list of all the log names on the system.</summary>
public IEnumerable<string> GetLogNames()
{
List<string> paths = [];
Expand Down Expand Up @@ -57,6 +60,21 @@ public IEnumerable<string> GetLogNames()
return paths.Order();
}

private static EventLogHandle CreateRenderContext()
{
EventLogHandle renderContextHandle = EventMethods.EvtCreateRenderContext(0, null, EvtRenderContextFlags.System);
int error = Marshal.GetLastWin32Error();

if (renderContextHandle.IsInvalid)
{
renderContextHandle.Dispose();

EventMethods.ThrowEventLogException(error);
}

return renderContextHandle;
}

private static string NextChannelPath(EventLogHandle handle, ref bool doneReading)
{
bool success = EventMethods.EvtNextChannelPath(handle, 0, null, out int bufferSize);
Expand All @@ -77,7 +95,7 @@ private static string NextChannelPath(EventLogHandle handle, ref bool doneReadin
}
}

char[] buffer = new char[bufferSize];
var buffer = new char[bufferSize];

success = EventMethods.EvtNextChannelPath(handle, bufferSize, buffer, out bufferSize);
error = Marshal.GetLastWin32Error();
Expand All @@ -92,6 +110,11 @@ private static string NextChannelPath(EventLogHandle handle, ref bool doneReadin

private void Dispose(bool disposing)
{
if (RenderContext is { IsInvalid: false })
{
RenderContext.Dispose();
}

if (Handle is { IsInvalid: false })
{
Handle.Dispose();
Expand Down
Loading

0 comments on commit 8f3c1f2

Please sign in to comment.