File: src\runtime\src\libraries\System.Private.CoreLib\src\System\Diagnostics\Tracing\EventListener.cs
Web Access
Project: src\runtime\src\coreclr\nativeaot\System.Private.CoreLib\src\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Threading;

namespace System.Diagnostics.Tracing;

/// <summary>
/// An EventListener represents a target for the events generated by EventSources (that is subclasses
/// of <see cref="EventSource"/>), in the current appdomain. When a new EventListener is created
/// it is logically attached to all eventSources in that appdomain. When the EventListener is Disposed, then
/// it is disconnected from the event eventSources. Note that there is a internal list of STRONG references
/// to EventListeners, which means that relying on the lack of references to EventListeners to clean up
/// EventListeners will NOT work. You must call EventListener.Dispose explicitly when a dispatcher is no
/// longer needed.
/// <para>
/// Once created, EventListeners can enable or disable on a per-eventSource basis using verbosity levels
/// (<see cref="EventLevel"/>) and bitfields (<see cref="EventKeywords"/>) to further restrict the set of
/// events to be sent to the dispatcher. The dispatcher can also send arbitrary commands to a particular
/// eventSource using the 'SendCommand' method. The meaning of the commands are eventSource specific.
/// </para><para>
/// The Null Guid (that is (new Guid()) has special meaning as a wildcard for 'all current eventSources in
/// the appdomain'. Thus it is relatively easy to turn on all events in the appdomain if desired.
/// </para><para>
/// It is possible for there to be many EventListener's defined in a single appdomain. Each dispatcher is
/// logically independent of the other listeners. Thus when one dispatcher enables or disables events, it
/// affects only that dispatcher (other listeners get the events they asked for). It is possible that
/// commands sent with 'SendCommand' would do a semantic operation that would affect the other listeners
/// (like doing a GC, or flushing data ...), but this is the exception rather than the rule.
/// </para><para>
/// Thus the model is that each EventSource keeps a list of EventListeners that it is sending events
/// to. Associated with each EventSource-dispatcher pair is a set of filtering criteria that determine for
/// that eventSource what events that dispatcher will receive.
/// </para><para>
/// Listeners receive the events on their 'OnEventWritten' method. Thus subclasses of EventListener must
/// override this method to do something useful with the data.
/// </para><para>
/// In addition, when new eventSources are created, the 'OnEventSourceCreate' method is called. The
/// invariant associated with this callback is that every eventSource gets exactly one
/// 'OnEventSourceCreate' call for ever eventSource that can potentially send it log messages. In
/// particular when a EventListener is created, typically a series of OnEventSourceCreate' calls are
/// made to notify the new dispatcher of all the eventSources that existed before the EventListener was
/// created.
/// </para>
/// </summary>
public abstract class EventListener : IDisposable
{
    private event EventHandler<EventSourceCreatedEventArgs>? _EventSourceCreated;

    /// <summary>
    /// This event is raised whenever a new eventSource is 'attached' to the dispatcher.
    /// This can happen for all existing EventSources when the EventListener is created
    /// as well as for any EventSources that come into existence after the EventListener
    /// has been created.
    ///
    /// These 'catch up' events are called during the construction of the EventListener.
    /// Subclasses need to be prepared for that.
    ///
    /// In a multi-threaded environment, it is possible that 'EventSourceEventWrittenCallback'
    /// events for a particular eventSource to occur BEFORE the EventSourceCreatedCallback is issued.
    /// </summary>
    public event EventHandler<EventSourceCreatedEventArgs>? EventSourceCreated
    {
        add
        {
            CallBackForExistingEventSources(false, value);

            this._EventSourceCreated = (EventHandler<EventSourceCreatedEventArgs>?)Delegate.Combine(_EventSourceCreated, value);
        }
        remove
        {
            this._EventSourceCreated = (EventHandler<EventSourceCreatedEventArgs>?)Delegate.Remove(_EventSourceCreated, value);
        }
    }

    /// <summary>
    /// This event is raised whenever an event has been written by a EventSource for which
    /// the EventListener has enabled events.
    /// </summary>
    public event EventHandler<EventWrittenEventArgs>? EventWritten;

    /// <summary>
    /// Create a new EventListener in which all events start off turned off (use EnableEvents to turn
    /// them on).
    /// </summary>
    protected EventListener()
    {
        // This will cause the OnEventSourceCreated callback to fire.
        CallBackForExistingEventSources(true, (obj, args) =>
            args.EventSource!.AddListener((EventListener)obj!));
    }

    /// <summary>
    /// Dispose should be called when the EventListener no longer desires 'OnEvent*' callbacks. Because
    /// there is an internal list of strong references to all EventListeners, calling 'Dispose' directly
    /// is the only way to actually make the listen die. Thus it is important that users of EventListener
    /// call Dispose when they are done with their logging.
    /// </summary>
    public virtual void Dispose()
    {
        lock (EventListenersLock)
        {
            if (s_Listeners != null)
            {
                if (this == s_Listeners)
                {
                    EventListener cur = s_Listeners;
                    s_Listeners = this.m_Next;
                    RemoveReferencesToListenerInEventSources(cur);
                }
                else
                {
                    // Find 'this' from the s_Listeners linked list.
                    EventListener prev = s_Listeners;
                    while (true)
                    {
                        EventListener? cur = prev.m_Next;
                        if (cur == null)
                            break;
                        if (cur == this)
                        {
                            // Found our Listener, remove references to it in the eventSources
                            prev.m_Next = cur.m_Next;       // Remove entry.
                            RemoveReferencesToListenerInEventSources(cur);
                            break;
                        }
                        prev = cur;
                    }
                }
            }
            Validate();
        }

#if FEATURE_PERFTRACING
        // Remove the listener from the EventPipe dispatcher. EventCommand.Update with enable==false removes it.
        EventPipeEventDispatcher.Instance.SendCommand(this, EventCommand.Update, false, EventLevel.LogAlways, (EventKeywords)0);
#endif // FEATURE_PERFTRACING
    }
    // We don't expose a Dispose(bool), because the contract is that you don't have any non-syncronous
    // 'cleanup' associated with this object

    /// <summary>
    /// Enable all events from the eventSource identified by 'eventSource' to the current
    /// dispatcher that have a verbosity level of 'level' or lower.
    ///
    /// This call can have the effect of REDUCING the number of events sent to the
    /// dispatcher if 'level' indicates a less verbose level than was previously enabled.
    ///
    /// This call never has an effect on other EventListeners.
    ///
    /// </summary>
    public void EnableEvents(EventSource eventSource, EventLevel level)
    {
        EnableEvents(eventSource, level, EventKeywords.None);
    }
    /// <summary>
    /// Enable all events from the eventSource identified by 'eventSource' to the current
    /// dispatcher that have a verbosity level of 'level' or lower and have a event keyword
    /// matching any of the bits in 'matchAnyKeyword'.
    ///
    /// This call can have the effect of REDUCING the number of events sent to the
    /// dispatcher if 'level' indicates a less verbose level than was previously enabled or
    /// if 'matchAnyKeyword' has fewer keywords set than where previously set.
    ///
    /// This call never has an effect on other EventListeners.
    /// </summary>
    public void EnableEvents(EventSource eventSource, EventLevel level, EventKeywords matchAnyKeyword)
    {
        EnableEvents(eventSource, level, matchAnyKeyword, null);
    }
    /// <summary>
    /// Enable all events from the eventSource identified by 'eventSource' to the current
    /// dispatcher that have a verbosity level of 'level' or lower and have a event keyword
    /// matching any of the bits in 'matchAnyKeyword' as well as any (eventSource specific)
    /// effect passing additional 'key-value' arguments 'arguments' might have.
    ///
    /// This call can have the effect of REDUCING the number of events sent to the
    /// dispatcher if 'level' indicates a less verbose level than was previously enabled or
    /// if 'matchAnyKeyword' has fewer keywords set than where previously set.
    ///
    /// This call never has an effect on other EventListeners.
    /// </summary>
    public void EnableEvents(EventSource eventSource, EventLevel level, EventKeywords matchAnyKeyword, IDictionary<string, string?>? arguments)
    {
        ArgumentNullException.ThrowIfNull(eventSource);

        eventSource.SendCommand(this, EventProviderType.None, 0, EventCommand.Update, true, level, matchAnyKeyword, arguments);

#if FEATURE_PERFTRACING
        if (eventSource.GetType() == typeof(NativeRuntimeEventSource))
        {
            EventPipeEventDispatcher.Instance.SendCommand(this, EventCommand.Update, true, level, matchAnyKeyword);
        }
#endif // FEATURE_PERFTRACING
    }
    /// <summary>
    /// Disables all events coming from eventSource identified by 'eventSource'.
    ///
    /// This call never has an effect on other EventListeners.
    /// </summary>
    public void DisableEvents(EventSource eventSource)
    {
        ArgumentNullException.ThrowIfNull(eventSource);

        eventSource.SendCommand(this, EventProviderType.None, 0, EventCommand.Update, false, EventLevel.LogAlways, EventKeywords.None, null);

#if FEATURE_PERFTRACING
        if (eventSource.GetType() == typeof(NativeRuntimeEventSource))
        {
            EventPipeEventDispatcher.Instance.SendCommand(this, EventCommand.Update, false, EventLevel.LogAlways, EventKeywords.None);
        }
#endif // FEATURE_PERFTRACING
    }

    /// <summary>
    /// EventSourceIndex is small non-negative integer (suitable for indexing in an array)
    /// identifying EventSource. It is unique per-appdomain. Some EventListeners might find
    /// it useful to store additional information about each eventSource connected to it,
    /// and EventSourceIndex allows this extra information to be efficiently stored in a
    /// (growable) array (eg List(T)).
    /// </summary>
    protected internal static int EventSourceIndex(EventSource eventSource) { return eventSource.m_id; }

    /// <summary>
    /// This method is called whenever a new eventSource is 'attached' to the dispatcher.
    /// This can happen for all existing EventSources when the EventListener is created
    /// as well as for any EventSources that come into existence after the EventListener
    /// has been created.
    ///
    /// These 'catch up' events are called during the construction of the EventListener.
    /// Subclasses need to be prepared for that.
    ///
    /// In a multi-threaded environment, it is possible that 'OnEventWritten' callbacks
    /// for a particular eventSource to occur BEFORE the OnEventSourceCreated is issued.
    /// </summary>
    /// <param name="eventSource"></param>
    protected internal virtual void OnEventSourceCreated(EventSource eventSource)
    {
        EventHandler<EventSourceCreatedEventArgs>? callBack = this._EventSourceCreated;
        if (callBack != null)
        {
            EventSourceCreatedEventArgs args = new EventSourceCreatedEventArgs();
            args.EventSource = eventSource;
            callBack(this, args);
        }
    }

    /// <summary>
    /// This method is called whenever an event has been written by a EventSource for which
    /// the EventListener has enabled events.
    /// </summary>
    /// <param name="eventData"></param>
    protected internal virtual void OnEventWritten(EventWrittenEventArgs eventData)
    {
        this.EventWritten?.Invoke(this, eventData);
    }

#region private
    /// <summary>
    /// This routine adds newEventSource to the global list of eventSources, it also assigns the
    /// ID to the eventSource (which is simply the ordinal in the global list).
    ///
    /// EventSources currently do not pro-actively remove themselves from this list. Instead
    /// when eventSources's are GCed, the weak handle in this list naturally gets nulled, and
    /// we will reuse the slot. Today this list never shrinks (but we do reuse entries
    /// that are in the list). This seems OK since the expectation is that EventSources
    /// tend to live for the lifetime of the appdomain anyway (they tend to be used in
    /// global variables).
    /// </summary>
    /// <param name="newEventSource"></param>
    internal static void AddEventSource(EventSource newEventSource)
    {
        lock (EventListenersLock)
        {
            Debug.Assert(s_EventSources != null);

            // Periodically search the list for existing entries to reuse, this avoids
            // unbounded memory use if we keep recycling eventSources (an unlikely thing).
            int newIndex = -1;
            if (s_EventSources.Count % 64 == 63)   // on every block of 64, fill up the block before continuing
            {
                int i = s_EventSources.Count;      // Work from the top down.
                while (0 < i)
                {
                    --i;
                    WeakReference<EventSource> weakRef = s_EventSources[i];
                    if (!weakRef.TryGetTarget(out _))
                    {
                        newIndex = i;
                        weakRef.SetTarget(newEventSource);
                        break;
                    }
                }
            }
            if (newIndex < 0)
            {
                newIndex = s_EventSources.Count;
                s_EventSources.Add(new WeakReference<EventSource>(newEventSource));
            }
            newEventSource.m_id = newIndex;

#if DEBUG
            // Disable validation of EventSource/EventListener connections in case a call to EventSource.AddListener
            // causes a recursive call into this method.
            bool previousValue = s_ConnectingEventSourcesAndListener;
            s_ConnectingEventSourcesAndListener = true;
            try
            {
#endif
                // Add every existing dispatcher to the new EventSource
                for (EventListener? listener = s_Listeners; listener != null; listener = listener.m_Next)
                    newEventSource.AddListener(listener);
#if DEBUG
            }
            finally
            {
                s_ConnectingEventSourcesAndListener = previousValue;
            }
#endif

            Validate();
        }
    }

    // Whenever we have async callbacks from native code, there is an ugly issue where
    // during .NET shutdown native code could be calling the callback, but the CLR
    // has already prohibited callbacks to managed code in the appdomain, causing the CLR
    // to throw a COMPLUS_BOOT_EXCEPTION.   The guideline we give is that you must unregister
    // such callbacks on process shutdown or appdomain so that unmanaged code will never
    // do this.  This is what this callback is for.
    // See bug 724140 for more
    internal static void DisposeOnShutdown()
    {
        Debug.Assert(EventSource.IsSupported);
        List<EventSource> sourcesToDispose = new List<EventSource>();
        lock (EventListenersLock)
        {
            Debug.Assert(s_EventSources != null);
            foreach (WeakReference<EventSource> esRef in s_EventSources)
            {
                if (esRef.TryGetTarget(out EventSource? es))
                {
                    sourcesToDispose.Add(es);
                }
            }
        }

        // Do not invoke Dispose under the lock as this can lead to a deadlock.
        // See https://github.com/dotnet/runtime/issues/48342 for details.
        Debug.Assert(!Monitor.IsEntered(EventListenersLock));
        foreach (EventSource es in sourcesToDispose)
        {
            es.Dispose();
        }
    }

    // If an EventListener calls Dispose without calling DisableEvents first we want to issue the Disable command now
    private static void CallDisableEventsIfNecessary(EventDispatcher eventDispatcher, EventSource eventSource)
    {
#if DEBUG
        // Disable validation of EventSource/EventListener connections in case a call to EventSource.AddListener
        // causes a recursive call into this method.
        bool previousValue = s_ConnectingEventSourcesAndListener;
        s_ConnectingEventSourcesAndListener = true;
        try
        {
#endif
            if (eventDispatcher.m_EventEnabled == null)
            {
                return;
            }

            foreach (bool value in eventDispatcher.m_EventEnabled.Values)
            {
                if (value)
                {
                    eventDispatcher.m_Listener.DisableEvents(eventSource);
                }
            }
#if DEBUG
        }
        finally
        {
            s_ConnectingEventSourcesAndListener = previousValue;
        }
#endif
    }

    /// <summary>
    /// Helper used in code:Dispose that removes any references to 'listenerToRemove' in any of the
    /// eventSources in the appdomain.
    ///
    /// The EventListenersLock must be held before calling this routine.
    /// </summary>
    private static void RemoveReferencesToListenerInEventSources(EventListener listenerToRemove)
    {
        Debug.Assert(Monitor.IsEntered(EventListenersLock));
        // Foreach existing EventSource in the appdomain
        Debug.Assert(s_EventSources != null);

        // First pass to call DisableEvents
        WeakReference<EventSource>[] eventSourcesSnapshot = s_EventSources.ToArray();
        foreach (WeakReference<EventSource> eventSourceRef in eventSourcesSnapshot)
        {
            if (eventSourceRef.TryGetTarget(out EventSource? eventSource))
            {
                EventDispatcher? cur = eventSource.m_Dispatchers;
                while (cur != null)
                {
                    if (cur.m_Listener == listenerToRemove)
                    {
                        CallDisableEventsIfNecessary(cur, eventSource);
                    }

                    cur = cur.m_Next;
                }
            }
        }

        // DisableEvents can call back to user code and we have to start over since s_EventSources and
        // eventSource.m_Dispatchers could have mutated
        foreach (WeakReference<EventSource> eventSourceRef in s_EventSources)
        {
            if (eventSourceRef.TryGetTarget(out EventSource? eventSource)
                && eventSource.m_Dispatchers != null)
            {
                // Is the first output dispatcher the dispatcher we are removing?
                if (eventSource.m_Dispatchers.m_Listener == listenerToRemove)
                {
                    eventSource.m_Dispatchers = eventSource.m_Dispatchers.m_Next;
                }
                else
                {
                    // Remove 'listenerToRemove' from the eventSource.m_Dispatchers linked list.
                    EventDispatcher? prev = eventSource.m_Dispatchers;
                    while (true)
                    {
                        EventDispatcher? cur = prev.m_Next;
                        if (cur == null)
                        {
                            Debug.Fail("EventSource did not have a registered EventListener!");
                            break;
                        }
                        if (cur.m_Listener == listenerToRemove)
                        {
                            prev.m_Next = cur.m_Next;       // Remove entry.
                            break;
                        }
                        prev = cur;
                    }
                }
            }
        }
    }


    /// <summary>
    /// Checks internal consistency of EventSources/Listeners.
    /// </summary>
    [Conditional("DEBUG")]
    internal static void Validate()
    {
#if DEBUG
        // Don't run validation code if we're in the middle of modifying the connections between EventSources and EventListeners.
        if (s_ConnectingEventSourcesAndListener)
        {
            return;
        }
#endif

        lock (EventListenersLock)
        {
            Debug.Assert(s_EventSources != null);
            // Get all listeners
            Dictionary<EventListener, bool> allListeners = new Dictionary<EventListener, bool>();
            EventListener? cur = s_Listeners;
            while (cur != null)
            {
                allListeners.Add(cur, true);
                cur = cur.m_Next;
            }

            // For all eventSources
            int id = -1;
            foreach (WeakReference<EventSource> eventSourceRef in s_EventSources)
            {
                id++;
                if (!eventSourceRef.TryGetTarget(out EventSource? eventSource))
                    continue;
                Debug.Assert(eventSource.m_id == id, "Unexpected event source ID.");

                // None listeners on eventSources exist in the dispatcher list.
                EventDispatcher? dispatcher = eventSource.m_Dispatchers;
                while (dispatcher != null)
                {
                    Debug.Assert(allListeners.ContainsKey(dispatcher.m_Listener), "EventSource has a listener not on the global list.");
                    dispatcher = dispatcher.m_Next;
                }

                // Every dispatcher is on Dispatcher List of every eventSource.
                foreach (EventListener listener in allListeners.Keys)
                {
                    dispatcher = eventSource.m_Dispatchers;
                    while (true)
                    {
                        Debug.Assert(dispatcher != null, "Listener is not on all eventSources.");
                        if (dispatcher.m_Listener == listener)
                            break;
                        dispatcher = dispatcher.m_Next;
                    }
                }
            }
        }
    }

    /// <summary>
    /// Gets a global lock that is intended to protect the code:s_Listeners linked list and the
    /// code:s_EventSources list.  (We happen to use the s_EventSources list as the lock object)
    /// </summary>
    internal static object EventListenersLock
    {
        get
        {
            if (s_EventSources == null)
            {
                Interlocked.CompareExchange(ref s_EventSources, new List<WeakReference<EventSource>>(2), null);
            }
            return s_EventSources;
        }
    }

    private void CallBackForExistingEventSources(bool addToListenersList, EventHandler<EventSourceCreatedEventArgs>? callback)
    {
        // Pre-registered EventSources may not have been constructed yet but we need to do so now to ensure they are
        // reported to the EventListener.
        EventSourceInitHelper.EnsurePreregisteredEventSourcesExist();

        lock (EventListenersLock)
        {
            Debug.Assert(s_EventSources != null);

            // Disallow creating EventListener reentrancy.
            if (s_CreatingListener)
            {
                throw new InvalidOperationException(SR.EventSource_ListenerCreatedInsideCallback);
            }

            try
            {
                s_CreatingListener = true;

                if (addToListenersList)
                {
                    // Add to list of listeners in the system, do this BEFORE firing the 'OnEventSourceCreated' so that
                    // Those added sources see this listener.
                    this.m_Next = s_Listeners;
                    s_Listeners = this;
                }

                if (callback != null)
                {
                    // Find all existing eventSources call OnEventSourceCreated to 'catchup'
                    // Note that we DO have reentrancy here because 'AddListener' calls out to user code (via OnEventSourceCreated callback)
                    // We tolerate this by iterating over a copy of the list here. New event sources will take care of adding listeners themselves
                    // EventSources are not guaranteed to be added at the end of the s_EventSource list -- We re-use slots when a new source
                    // is created.
                    WeakReference<EventSource>[] eventSourcesSnapshot = s_EventSources.ToArray();

#if DEBUG
                    bool previousValue = s_ConnectingEventSourcesAndListener;
                    s_ConnectingEventSourcesAndListener = true;
                    try
                    {
#endif
                        for (int i = 0; i < eventSourcesSnapshot.Length; i++)
                        {
                            WeakReference<EventSource> eventSourceRef = eventSourcesSnapshot[i];
                            if (eventSourceRef.TryGetTarget(out EventSource? eventSource))
                            {
                                EventSourceCreatedEventArgs args = new EventSourceCreatedEventArgs();
                                args.EventSource = eventSource;
                                callback(this, args);
                            }
                        }
#if DEBUG
                    }
                    finally
                    {
                        s_ConnectingEventSourcesAndListener = previousValue;
                    }
#endif
                }

                Validate();
            }
            finally
            {
                s_CreatingListener = false;
            }
        }
    }

    // Instance fields
    internal volatile EventListener? m_Next;                         // These form a linked list in s_Listeners

    // static fields

    /// <summary>
    /// The list of all listeners in the appdomain.  Listeners must be explicitly disposed to remove themselves
    /// from this list.   Note that EventSources point to their listener but NOT the reverse.
    /// </summary>
    internal static EventListener? s_Listeners;
    /// <summary>
    /// The list of all active eventSources in the appdomain.  Note that eventSources do NOT
    /// remove themselves from this list this is a weak list and the GC that removes them may
    /// not have happened yet.  Thus it can contain event sources that are dead (thus you have
    /// to filter those out.
    /// </summary>
    internal static List<WeakReference<EventSource>>? s_EventSources;

    /// <summary>
    /// Used to disallow reentrancy.
    /// </summary>
    private static bool s_CreatingListener;

#if DEBUG
    /// <summary>
    /// Used to disable validation of EventSource and EventListener connectivity.
    /// This is needed when an EventListener is in the middle of being published to all EventSources
    /// and another EventSource is created as part of the process.
    /// </summary>
    [ThreadStatic]
    private static bool s_ConnectingEventSourcesAndListener;
#endif

#endregion
}