File: System\Diagnostics\Reader\EventLogWatcher.cs
Web Access
Project: src\src\runtime\src\libraries\System.Diagnostics.EventLog\src\System.Diagnostics.EventLog.csproj (System.Diagnostics.EventLog)
// 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;
        }
    }
}