|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using Microsoft.Win32;
namespace System.Diagnostics.Eventing.Reader
{
/// <summary>
/// Used for subscribing to event record notifications from
/// event log.
/// </summary>
public class EventLogWatcher : IDisposable
{
public event EventHandler<EventRecordWrittenEventArgs>? EventRecordWritten;
private readonly EventLogQuery _eventQuery;
private readonly EventBookmark? _bookmark;
private readonly bool _readExistingEvents;
private EventLogHandle? _handle;
private readonly IntPtr[] _eventsBuffer;
private int _numEventsInBuffer;
[MemberNotNullWhen(true, nameof(_handle))]
private bool IsSubscribing { get; set; }
private int _callbackThreadId;
private AutoResetEvent? _subscriptionWaitHandle;
private AutoResetEvent? _unregisterDoneHandle;
private RegisteredWaitHandle? _registeredWaitHandle;
/// <summary>
/// Maintains cached display / metadata information returned from
/// EventRecords that were obtained from this reader.
/// </summary>
private readonly ProviderMetadataCachedInformation cachedMetadataInformation;
private EventLogException? asyncException;
public EventLogWatcher(string path)
: this(new EventLogQuery(path, PathType.LogName), null, false)
{
}
public EventLogWatcher(EventLogQuery eventQuery)
: this(eventQuery, null, false)
{
}
public EventLogWatcher(EventLogQuery eventQuery, EventBookmark? bookmark)
: this(eventQuery, bookmark, false)
{
}
public EventLogWatcher(EventLogQuery eventQuery, EventBookmark? bookmark, bool readExistingEvents)
{
ArgumentNullException.ThrowIfNull(eventQuery);
if (bookmark != null)
{
readExistingEvents = false;
}
// Explicit data
_eventQuery = eventQuery;
_readExistingEvents = readExistingEvents;
if (_eventQuery.ReverseDirection)
{
throw new InvalidOperationException();
}
_eventsBuffer = new IntPtr[64];
cachedMetadataInformation = new ProviderMetadataCachedInformation(eventQuery.Session, null, 50);
_bookmark = bookmark;
}
public bool Enabled
{
get
{
return IsSubscribing;
}
set
{
if (value && !IsSubscribing)
{
StartSubscribing();
}
else if (!value && IsSubscribing)
{
StopSubscribing();
}
}
}
internal void StopSubscribing()
{
// C:\public\System.Diagnostics.Eventing\Microsoft\Win32\SafeHandles;
// Need to set isSubscribing to false before waiting for completion of callback.
IsSubscribing = false;
if (_registeredWaitHandle != null)
{
_registeredWaitHandle.Unregister(_unregisterDoneHandle);
if (_callbackThreadId != Environment.CurrentManagedThreadId)
{
// Not calling Stop from within callback - wait for
// Any outstanding callbacks to complete.
_unregisterDoneHandle?.WaitOne();
}
_registeredWaitHandle = null;
}
if (_unregisterDoneHandle != null)
{
_unregisterDoneHandle.Close();
_unregisterDoneHandle = null;
}
if (_subscriptionWaitHandle != null)
{
_subscriptionWaitHandle.Close();
_subscriptionWaitHandle = null;
}
for (int i = 0; i < _numEventsInBuffer; i++)
{
if (_eventsBuffer[i] != IntPtr.Zero)
{
UnsafeNativeMethods.EvtClose(_eventsBuffer[i]);
_eventsBuffer[i] = IntPtr.Zero;
}
}
_numEventsInBuffer = 0;
if (_handle != null && !_handle.IsInvalid)
{
_handle.Dispose();
}
}
[MemberNotNull(nameof(_handle))]
internal void StartSubscribing()
{
if (IsSubscribing)
{
throw new InvalidOperationException();
}
int flag = 0;
if (_bookmark != null)
{
flag |= (int)UnsafeNativeMethods.EvtSubscribeFlags.EvtSubscribeStartAfterBookmark;
}
else if (_readExistingEvents)
{
flag |= (int)UnsafeNativeMethods.EvtSubscribeFlags.EvtSubscribeStartAtOldestRecord;
}
else
{
flag |= (int)UnsafeNativeMethods.EvtSubscribeFlags.EvtSubscribeToFutureEvents;
}
if (_eventQuery.TolerateQueryErrors)
{
flag |= (int)UnsafeNativeMethods.EvtSubscribeFlags.EvtSubscribeTolerateQueryErrors;
}
// C:\public\System.Diagnostics.Eventing\Microsoft\Win32\SafeHandles;
_callbackThreadId = -1;
_unregisterDoneHandle = new AutoResetEvent(false);
_subscriptionWaitHandle = new AutoResetEvent(false);
EventLogHandle bookmarkHandle = EventLogRecord.GetBookmarkHandleFromBookmark(_bookmark);
using (bookmarkHandle)
{
_handle = UnsafeNativeMethods.EvtSubscribe(_eventQuery.Session.Handle,
_subscriptionWaitHandle.SafeWaitHandle,
_eventQuery.Path,
_eventQuery.Query,
bookmarkHandle,
IntPtr.Zero,
IntPtr.Zero,
flag);
}
IsSubscribing = true;
RequestEvents();
_registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject(
_subscriptionWaitHandle,
new WaitOrTimerCallback(SubscribedEventsAvailableCallback),
null,
-1,
false);
}
internal void SubscribedEventsAvailableCallback(object? state, bool timedOut)
{
_callbackThreadId = Environment.CurrentManagedThreadId;
try
{
RequestEvents();
}
finally
{
_callbackThreadId = -1;
}
}
private void RequestEvents()
{
// C:\public\System.Diagnostics.Eventing\Microsoft\Win32\SafeHandles;
asyncException = null;
Debug.Assert(_numEventsInBuffer == 0);
bool results = false;
do
{
if (!IsSubscribing)
{
break;
}
try
{
results = NativeWrapper.EvtNext(_handle, _eventsBuffer.Length, _eventsBuffer, 0, 0, ref _numEventsInBuffer);
if (!results)
{
return;
}
}
catch (Exception e)
{
asyncException = new EventLogException();
asyncException.Data.Add("RealException", e);
}
HandleEventsRequestCompletion();
} while (results);
}
private void IssueCallback(EventRecordWrittenEventArgs eventArgs)
{
EventRecordWritten?.Invoke(this, eventArgs);
}
private void HandleEventsRequestCompletion()
{
if (asyncException != null)
{
EventRecordWrittenEventArgs args = new EventRecordWrittenEventArgs(asyncException.Data["RealException"] as Exception);
IssueCallback(args);
}
for (int i = 0; i < _numEventsInBuffer; i++)
{
if (!IsSubscribing)
{
break;
}
EventLogRecord record = new EventLogRecord(new EventLogHandle(_eventsBuffer[i], true), _eventQuery.Session, cachedMetadataInformation);
EventRecordWrittenEventArgs args = new EventRecordWrittenEventArgs(record);
_eventsBuffer[i] = IntPtr.Zero; // user is responsible for calling Dispose().
IssueCallback(args);
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
StopSubscribing();
return;
}
for (int i = 0; i < _numEventsInBuffer; i++)
{
if (_eventsBuffer[i] != IntPtr.Zero)
{
NativeWrapper.EvtClose(_eventsBuffer[i]);
_eventsBuffer[i] = IntPtr.Zero;
}
}
_numEventsInBuffer = 0;
}
}
}
|