File: src\runtime\src\libraries\System.Private.CoreLib\src\System\Diagnostics\Tracing\EventSource.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.

// This program uses code hyperlinks available as part of the HyperAddin Visual Studio plug-in.
// It is available from http://www.codeplex.com/hyperAddin

/* DESIGN NOTES DESIGN NOTES DESIGN NOTES DESIGN NOTES */
// DESIGN NOTES
// Over the years EventSource has become more complex and so it is important to understand
// the basic structure of the code to ensure that it does not grow more complex.
//
// Basic Model
//
// PRINCIPLE: EventSource - ETW decoupling
//
// Conceptually an EventSource is something that takes event logging data from the source methods
// to the EventListener that can subscribe to them.  Note that CONCEPTUALLY EVENTSOURCES DON'T
// KNOW ABOUT ETW!.   The MODEL of the system is that there is a special EventListener which
// we will call the EtwEventListener, that forwards commands from ETW to EventSources and
// listens to EventSources and forwards on those events to ETW.   Thus the model should
// be that you DON'T NEED ETW.
//
// Now in actual practice, EventSource have rather intimate knowledge of ETW and send events
// to it directly, but this can be VIEWED AS AN OPTIMIZATION.
//
// Basic Event Data Flow:
//
// There are two ways for event Data to enter the system
//     1) WriteEvent* and friends.  This is called the 'contract' based approach because
//        you write a method per event which forms a contract that is know at compile time.
//        In this scheme each event is given an EVENTID (small integer), which is its identity
//     2) Write<T> methods.   This is called the 'dynamic' approach because new events
//        can be created on the fly.  Event identity is determined by the event NAME, and these
//        are not quite as efficient at runtime since you have at least a hash table lookup
//        on every event write.
//
// EventSource-EventListener transfer fully supports both ways of writing events (either contract
// based (WriteEvent*) or dynamic (Write<T>)).   Both ways fully support the same set of data
// types.   It is recommended, however, that you use the contract based approach when the event scheme
// is known at compile time (that is whenever possible).  It is more efficient, but more importantly
// it makes the contract very explicit, and centralizes all policy about logging.  These are good
// things.    The Write<T> API is really meant for more ad-hoc cases.
//
// Allowed Data:
//
// Note that EventSource-EventListeners have a conceptual serialization-deserialization that happens
// during the transfer.   In particular object identity is not preserved, some objects are morphed,
// and not all data types are supported.   In particular you can pass
//
// A Valid type to log to an EventSource include
//   * Primitive data types
//   * IEnumerable<T> of valid types T (this include arrays)  (* New for V4.6)
//   * Explicitly Opted in class or struct with public property Getters over Valid types.  (* New for V4.6)
//
// This set of types is roughly a generalization of JSON support (basically primitives, bags, and arrays).
//
// Explicitly allowed structs include (* New for V4.6)
//   * Marked with the EventData attribute
//   * implicitly defined (e.g the C# new {x = 3, y = 5} syntax)
//   * KeyValuePair<K,V>  (thus dictionaries can be passed since they are an IEnumerable of KeyValuePair)
//
// When classes are returned in an EventListener, what is returned is something that implements
// IDictionary<string, T>.  Thus when objects are passed to an EventSource they are transformed
// into a key-value bag (the IDictionary<string, T>) for consumption in the listener.   These
// are obviously NOT the original objects.
//
// ETW serialization formats:
//
// As mentioned, conceptually EventSources send data to EventListeners and there is a conceptual
// copy/morph of that data as described above.   In addition the .NET framework supports a conceptual
// ETWListener that will send the data to the ETW stream.   If you use this feature, the data needs
// to be serialized in a way that ETW supports.  ETW supports the following serialization formats
//
//     1) Manifest Based serialization.
//     2) SelfDescribing serialization (TraceLogging style in the TraceLogging directory)
//
// A key factor is that the Write<T> method, which supports on the fly definition of events, can't
// support the manifest based serialization because the manifest needs the schema of all events
// to be known before any events are emitted.  This implies the following:
//
// If you use Write<T> and the output goes to ETW it will use the SelfDescribing format.
// If you use the EventSource(string) constructor for an eventSource (in which you don't
// create a subclass), the default is also to use Self-Describing serialization.  In addition
// you can use the EventSoruce(EventSourceSettings) constructor to also explicitly specify
// Self-Describing serialization format.   These affect the WriteEvent* APIs going to ETW.
//
// Note that none of this ETW serialization logic affects EventListeners.   Only the ETW listener.
//
// *************************************************************************************
// *** INTERNALS: Event Propagation
//
//   Data enters the system either though
//
// 1) A user defined method in the user defined subclass of EventSource which calls
//     A) A typesafe type specific overload of WriteEvent(ID, ...)  e.g. WriteEvent(ID, string, string)
//           * which calls into the unsafe WriteEventCore(ID COUNT EventData*) WriteEventWithRelatedActivityIdCore()
//     B) The typesafe overload WriteEvent(ID, object[])  which calls the private helper WriteEventVarargs(ID, Guid* object[])
//     C) Directly into the unsafe WriteEventCore(ID, COUNT EventData*) or WriteEventWithRelatedActivityIdCore()
//
//     All event data eventually flows to one of
//        * WriteEventWithRelatedActivityIdCore(ID, Guid*, COUNT, EventData*)
//        * WriteEventVarargs(ID, Guid*, object[])
//
// 2) A call to one of the overloads of Write<T>.   All these overloads end up in
//        * WriteImpl<T>(EventName, Options, Data, Guid*, Guid*)
//
// On output there are the following routines
//    Writing to all listeners that are NOT ETW, we have the following routines
//       * WriteToAllListeners(ID, Guid*, Guid*, COUNT, EventData*)
//       * WriteToAllListeners(ID, Guid*, Guid*, object[])
//       * WriteToAllListeners(NAME, Guid*, Guid*, EventPayload)
//
//       EventPayload is the internal type that implements the IDictionary<string, object> interface
//       The EventListeners will pass back for serialized classes for nested object, but
//       WriteToAllListeners(NAME, Guid*, Guid*, EventPayload) unpacks this and uses the fields as if they
//       were parameters to a method.
//
//       The first two are used for the WriteEvent* case, and the later is used for the Write<T> case.
//
//    Writing to ETW, Manifest Based
//          EventProvider.WriteEvent(EventDescriptor, Guid*, COUNT, EventData*)
//          EventProvider.WriteEvent(EventDescriptor, Guid*, object[])
//    Writing to ETW, Self-Describing format
//          WriteMultiMerge(NAME, Options, Types, EventData*)
//          WriteMultiMerge(NAME, Options, Types, object[])
//          WriteImpl<T> has logic that knows how to serialize (like WriteMultiMerge) but also knows
//             where it will write it to
//
//    All ETW writes eventually call
//      EventWriteTransfer
//         EventProvider.WriteEventRaw   - sets last error
//         EventSource.WriteEventRaw     - Does EventSource exception handling logic
//            WriteMultiMerge
//            WriteImpl<T>
//         EventProvider.WriteEvent(EventDescriptor, Guid*, COUNT, EventData*)
//         EventProvider.WriteEvent(EventDescriptor, Guid*, object[])
//
// Serialization:  We have a bit of a hodge-podge of serializers right now.   Only the one for ETW knows
// how to deal with nested classes or arrays.   I will call this serializer the 'TypeInfo' serializer
// since it is the TraceLoggingTypeInfo structure that knows how to do this.   Effectively for a type you
// can call one of these
//      WriteMetadata - transforms the type T into serialization meta data blob for that type
//      WriteObjectData - transforms an object of T into serialization data blob for that instance
//      GetData - transforms an object of T into its deserialized form suitable for passing to EventListener.
// The first two are used to serialize something for ETW.   The second one is used to transform the object
// for use by the EventListener.    We also have a 'DecodeObject' method that will take a EventData* and
// deserialize to pass to an EventListener, but it only works on primitive types (types supported in version V4.5).
//
// It is an important observation that while EventSource does support users directly calling with EventData*
// blobs, we ONLY support that for the primitive types (V4.5 level support).   Thus while there is a EventData*
// path through the system it is only for some types.  The object[] path is the more general (but less efficient) path.
//
// TODO There is cleanup needed There should be no divergence until WriteEventRaw.
//
// TODO: We should have a single choke point (right now we always have this parallel EventData* and object[] path.   This
// was historical (at one point we tried to pass object directly from EventSoruce to EventListener.  That was always
// fragile and a compatibility headache, but we have finally been forced into the idea that there is always a transformation.
// This allows us to use the EventData* form to be the canonical data format in the low level APIs.  This also gives us the
// opportunity to expose this format to EventListeners in the future.
//

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Numerics;
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace System.Diagnostics.Tracing
{
    /// <summary>
    /// This class is meant to be inherited by a user-defined event source in order to define a managed
    /// ETW provider.   Please See DESIGN NOTES above for the internal architecture.
    /// The minimal definition of an EventSource simply specifies a number of ETW event methods that
    /// call one of the EventSource.WriteEvent overloads, <see cref="WriteEventCore"/>,
    /// or <see cref="WriteEventWithRelatedActivityIdCore"/> to log them. This functionality
    /// is sufficient for many users.
    /// <para>
    /// To achieve more control over the ETW provider manifest exposed by the event source type, the
    /// [<see cref="EventAttribute"/>] attributes can be specified for the ETW event methods.
    /// </para><para>
    /// For very advanced EventSources, it is possible to intercept the commands being given to the
    /// eventSource and change what filtering is done (see EventListener.EnableEvents and
    /// <see cref="EventListener.DisableEvents"/>) or cause actions to be performed by the eventSource,
    /// e.g. dumping a data structure (see EventSource.SendCommand and
    /// <see cref="OnEventCommand"/>).
    /// </para><para>
    /// The eventSources can be turned on with Windows ETW controllers (e.g. logman), immediately.
    /// It is also possible to control and intercept the data dispatcher programmatically.  See
    /// <see cref="EventListener"/> for more.
    /// </para>
    /// </summary>
    /// <remarks>
    /// This is a minimal definition for a custom event source:
    /// <code>
    /// [EventSource(Name="Samples.Demos.Minimal")]
    /// sealed class MinimalEventSource : EventSource
    /// {
    ///     public static readonly MinimalEventSource Log = new MinimalEventSource();
    ///     public void Load(long ImageBase, string Name) { WriteEvent(1, ImageBase, Name); }
    ///     public void Unload(long ImageBase) { WriteEvent(2, ImageBase); }
    ///     private MinimalEventSource() {}
    /// }
    /// </code>
    /// </remarks>
    // The EnsureDescriptorsInitialized() method might need to access EventSource and its derived type
    // members and the trimmer ensures that these members are preserved.
    [DynamicallyAccessedMembers(ManifestMemberTypes)]
    public partial class EventSource : IDisposable
    {
        // private instance state
        private string m_name = null!;                  // My friendly name (privided in ctor)
        internal int m_id;                              // A small integer that is unique to this instance.
        private Guid m_guid;                            // GUID representing the ETW eventSource to the OS.
        internal volatile Dictionary<int, EventMetadata>? m_eventData; // None per-event data
        private volatile byte[]? m_rawManifest;          // Bytes to send out representing the event schema

        private EventHandler<EventCommandEventArgs>? m_eventCommandExecuted;

        private readonly EventSourceSettings m_config;      // configuration information

        private bool m_eventSourceDisposed;              // has Dispose been called.

        // Enabling bits
        private bool m_eventSourceEnabled;              // am I enabled (any of my events are enabled for any dispatcher)
        internal EventLevel m_level;                    // highest level enabled by any output dispatcher
        internal EventKeywords m_matchAnyKeyword;       // the logical OR of all levels enabled by any output dispatcher (zero is a special case) meaning 'all keywords'

        // Dispatching state
        internal volatile EventDispatcher? m_Dispatchers;    // Linked list of code:EventDispatchers we write the data to (we also do ETW specially)
        private volatile OverrideEventProvider m_etwProvider = null!;   // This hooks up ETW commands to our 'OnEventCommand' callback
#if FEATURE_PERFTRACING
        private object? m_createEventLock;
        private IntPtr m_writeEventStringEventHandle = IntPtr.Zero;
        private volatile OverrideEventProvider m_eventPipeProvider = null!;
#endif
        private bool m_completelyInited;                // The EventSource constructor has returned without exception.
        private Exception? m_constructionException;      // If there was an exception construction, this is it
        private byte m_outOfBandMessageCount;           // The number of out of band messages sent (we throttle them
        private EventCommandEventArgs? m_deferredCommands; // If we get commands before we are fully we store them here and run the when we are fully inited.

        private string[]? m_traits;                      // Used to implement GetTraits

        [ThreadStatic]
        private static byte m_EventSourceExceptionRecurenceCount; // current recursion count inside ThrowEventSourceException

        internal volatile ulong[]? m_channelData;

        // We use a single instance of ActivityTracker for all EventSources instances to allow correlation between multiple event providers.
        // We have m_activityTracker field simply because instance field is more efficient than static field fetch.
        private ActivityTracker m_activityTracker = null!;
        internal const string ActivityStartSuffix = "Start";
        internal const string ActivityStopSuffix = "Stop";

        // This switch controls an opt-in, off-by-default mechanism for allowing multiple EventSources to have the same
        // name and by extension GUID. This is not considered a mainline scenario and is explicitly intended as a release
        // valve for users that make heavy use of AssemblyLoadContext and experience exceptions from EventSource.
        // This does not solve any issues that might arise from this configuration. For instance:
        //
        // * If multiple manifest-mode EventSources have the same name/GUID, it is ambiguous which manifest should be used by an ETW parser.
        //   This can result in events being incorrectly parse. The data will still be there, but EventTrace (or other libraries) won't
        //   know how to parse it.
        // * Potential issues in parsing self-describing EventSources that use the same name/GUID, event name, and payload type from the same AssemblyLoadContext
        //   but have different event IDs set.
        //
        // Most users should not turn this on.
        internal const string DuplicateSourceNamesSwitch = "System.Diagnostics.Tracing.EventSource.AllowDuplicateSourceNames";
        private static readonly bool AllowDuplicateSourceNames = AppContext.TryGetSwitch(DuplicateSourceNamesSwitch, out bool isEnabled) ? isEnabled : false;

        [FeatureSwitchDefinition("System.Diagnostics.Tracing.EventSource.IsSupported")]
        internal static bool IsSupported { get; } = AppContext.TryGetSwitch("System.Diagnostics.Tracing.EventSource.IsSupported", out bool isSupported) ? isSupported : true;

        [FeatureSwitchDefinition("System.Diagnostics.Metrics.Meter.IsSupported")]
        internal static bool IsMeterSupported { get; } = AppContext.TryGetSwitch("System.Diagnostics.Metrics.Meter.IsSupported", out bool isSupported) ? isSupported : true;

#if FEATURE_EVENTSOURCE_XPLAT
#pragma warning disable CA1823 // field is used to keep listener alive
        private static readonly EventListener? persistent_Xplat_Listener = IsSupported ? XplatEventLogger.InitializePersistentListener() : null;
#pragma warning restore CA1823
#endif //FEATURE_EVENTSOURCE_XPLAT

        /// <summary>
        /// The human-friendly name of the eventSource.  It defaults to the simple name of the class
        /// </summary>
        public string Name => m_name;
        /// <summary>
        /// Every eventSource is assigned a GUID to uniquely identify it to the system.
        /// </summary>
        public Guid Guid => m_guid;

        /// <summary>
        /// Returns true if the eventSource has been enabled at all. This is the preferred test
        /// to be performed before a relatively expensive EventSource operation.
        /// </summary>
        public bool IsEnabled()
        {
            return m_eventSourceEnabled;
        }

        /// <summary>
        /// Returns true if events with greater than or equal 'level' and have one of 'keywords' set are enabled.
        ///
        /// Note that the result of this function is only an approximation on whether a particular
        /// event is active or not. It is only meant to be used as way of avoiding expensive
        /// computation for logging when logging is not on, therefore it sometimes returns false
        /// positives (but is always accurate when returning false).  EventSources are free to
        /// have additional filtering.
        /// </summary>
        public bool IsEnabled(EventLevel level, EventKeywords keywords)
        {
            return IsEnabled(level, keywords, EventChannel.None);
        }

        /// <summary>
        /// Returns true if events with greater than or equal 'level' and have one of 'keywords' set are enabled, or
        /// if 'keywords' specifies a channel bit for a channel that is enabled.
        ///
        /// Note that the result of this function only an approximation on whether a particular
        /// event is active or not. It is only meant to be used as way of avoiding expensive
        /// computation for logging when logging is not on, therefore it sometimes returns false
        /// positives (but is always accurate when returning false).  EventSources are free to
        /// have additional filtering.
        /// </summary>
        public bool IsEnabled(EventLevel level, EventKeywords keywords, EventChannel channel)
        {
            if (!IsEnabled())
                return false;

            if (!IsEnabledCommon(m_eventSourceEnabled, m_level, m_matchAnyKeyword, level, keywords, channel))
                return false;

            return true;
        }

        /// <summary>
        /// Returns the settings for the event source instance
        /// </summary>
        public EventSourceSettings Settings => m_config;

        // Manifest support
        /// <summary>
        /// Returns the GUID that uniquely identifies the eventSource defined by 'eventSourceType'.
        /// This API allows you to compute this without actually creating an instance of the EventSource.
        /// It only needs to reflect over the type.
        /// </summary>
        public static Guid GetGuid(Type eventSourceType)
        {
            ArgumentNullException.ThrowIfNull(eventSourceType);

            EventSourceAttribute? attrib = (EventSourceAttribute?)EventSourceHelpers.GetCustomAttributeHelper(eventSourceType, typeof(EventSourceAttribute));
            string name = eventSourceType.Name;
            if (attrib != null)
            {
                if (attrib.Guid != null)
                {
                    if (Guid.TryParse(attrib.Guid, out Guid g))
                        return g;
                }

                if (attrib.Name != null)
                    name = attrib.Name;
            }

            if (name == null)
            {
                throw new ArgumentException(SR.Argument_InvalidTypeName, nameof(eventSourceType));
            }
            return GenerateGuidFromName(name.ToUpperInvariant());       // Make it case insensitive.
        }
        /// <summary>
        /// Returns the official ETW Provider name for the eventSource defined by 'eventSourceType'.
        /// This API allows you to compute this without actually creating an instance of the EventSource.
        /// It only needs to reflect over the type.
        /// </summary>
        public static string GetName(Type eventSourceType)
        {
            return GetName(eventSourceType, EventManifestOptions.None);
        }

        private const DynamicallyAccessedMemberTypes ManifestMemberTypes =
            DynamicallyAccessedMemberTypes.PublicMethods
            | DynamicallyAccessedMemberTypes.NonPublicMethods
            | DynamicallyAccessedMemberTypes.PublicNestedTypes;

        /// <summary>
        /// Returns a string of the XML manifest associated with the eventSourceType. The scheme for this XML is
        /// documented at in EventManifest Schema https://learn.microsoft.com/windows/desktop/WES/eventmanifestschema-schema.
        /// This is the preferred way of generating a manifest to be embedded in the ETW stream as it is fast and
        /// the fact that it only includes localized entries for the current UI culture is an acceptable tradeoff.
        /// </summary>
        /// <param name="eventSourceType">The type of the event source class for which the manifest is generated</param>
        /// <param name="assemblyPathToIncludeInManifest">The manifest XML fragment contains the string name of the DLL name in
        /// which it is embedded.  This parameter specifies what name will be used</param>
        /// <returns>The XML data string</returns>
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2114:ReflectionToDynamicallyAccessedMembers",
            Justification = "EnsureDescriptorsInitialized's use of GetType preserves this method which " +
                            "has dynamically accessed members requirements, but EnsureDescriptorsInitialized does not " +
                            "access this member and is safe to call.")]
        public static string? GenerateManifest(
            [DynamicallyAccessedMembers(ManifestMemberTypes)]
            Type eventSourceType,
            string? assemblyPathToIncludeInManifest)
        {
            return GenerateManifest(eventSourceType, assemblyPathToIncludeInManifest, EventManifestOptions.None);
        }

        /// <summary>
        /// Returns a string of the XML manifest associated with the eventSourceType. The scheme for this XML is
        /// documented at in EventManifest Schema https://learn.microsoft.com/windows/desktop/WES/eventmanifestschema-schema.
        /// Pass EventManifestOptions.AllCultures when generating a manifest to be registered on the machine. This
        /// ensures that the entries in the event log will be "optimally" localized.
        /// </summary>
        /// <param name="eventSourceType">The type of the event source class for which the manifest is generated</param>
        /// <param name="assemblyPathToIncludeInManifest">The manifest XML fragment contains the string name of the DLL name in
        /// which it is embedded.  This parameter specifies what name will be used</param>
        /// <param name="flags">The flags to customize manifest generation. If flags has bit OnlyIfNeededForRegistration specified
        /// this returns null when the eventSourceType does not require explicit registration</param>
        /// <returns>The XML data string or null</returns>
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2114:ReflectionToDynamicallyAccessedMembers",
            Justification = "EnsureDescriptorsInitialized's use of GetType preserves this method which " +
                            "has dynamically accessed members requirements, but EnsureDescriptorsInitialized does not " +
                            "access this member and is safe to call.")]
        public static string? GenerateManifest(
            [DynamicallyAccessedMembers(ManifestMemberTypes)]
            Type eventSourceType,
            string? assemblyPathToIncludeInManifest,
            EventManifestOptions flags)
        {
            if (!IsSupported)
            {
                return null;
            }

            ArgumentNullException.ThrowIfNull(eventSourceType);

            byte[]? manifestBytes = EventSourceHelpers.CreateManifestAndDescriptors(eventSourceType, assemblyPathToIncludeInManifest, null, flags);
            return (manifestBytes == null) ? null : Encoding.UTF8.GetString(manifestBytes, 0, manifestBytes.Length);
        }

        // EventListener support
        /// <summary>
        /// returns a list (IEnumerable) of all sources in the appdomain).  EventListeners typically need this.
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<EventSource> GetSources()
        {
            if (!IsSupported)
            {
                return [];
            }

            var ret = new List<EventSource>();
            lock (EventListener.EventListenersLock)
            {
                Debug.Assert(EventListener.s_EventSources != null);

                foreach (WeakReference<EventSource> eventSourceRef in EventListener.s_EventSources)
                {
                    if (eventSourceRef.TryGetTarget(out EventSource? eventSource) && !eventSource.IsDisposed)
                        ret.Add(eventSource);
                }
            }
            return ret;
        }

        /// <summary>
        /// Send a command to a particular EventSource identified by 'eventSource'.
        /// Calling this routine simply forwards the command to the EventSource.OnEventCommand
        /// callback.  What the EventSource does with the command and its arguments are from
        /// that point EventSource-specific.
        /// </summary>
        /// <param name="eventSource">The instance of EventSource to send the command to</param>
        /// <param name="command">A positive user-defined EventCommand, or EventCommand.SendManifest</param>
        /// <param name="commandArguments">A set of (name-argument, value-argument) pairs associated with the command</param>
        public static void SendCommand(EventSource eventSource, EventCommand command, IDictionary<string, string?>? commandArguments)
        {
            if (!IsSupported)
            {
                return;
            }

            ArgumentNullException.ThrowIfNull(eventSource);

            // User-defined EventCommands should not conflict with the reserved commands.
            if ((int)command <= (int)EventCommand.Update && (int)command != (int)EventCommand.SendManifest)
            {
                throw new ArgumentException(SR.EventSource_InvalidCommand, nameof(command));
            }

            eventSource.SendCommand(null, EventProviderType.ETW, 0, command, true, EventLevel.LogAlways, EventKeywords.None, commandArguments);
        }

        // Error APIs.  (We don't throw by default, but you can probe for status)
        /// <summary>
        /// Because
        ///
        ///     1) Logging is often optional and thus should not generate fatal errors (exceptions)
        ///     2) EventSources are often initialized in class constructors (which propagate exceptions poorly)
        ///
        /// The event source constructor does not throw exceptions.  Instead we remember any exception that
        /// was generated (it is also logged to Trace.WriteLine).
        /// </summary>
        public Exception? ConstructionException => m_constructionException;

        /// <summary>
        /// EventSources can have arbitrary string key-value pairs associated with them called Traits.
        /// These traits are not interpreted by the EventSource but may be interpreted by EventListeners
        /// (e.g. like the built in ETW listener).   These traits are specified at EventSource
        /// construction time and can be retrieved by using this GetTrait API.
        /// </summary>
        /// <param name="key">The key to look up in the set of key-value pairs passed to the EventSource constructor</param>
        /// <returns>The value string associated with key.  Will return null if there is no such key.</returns>
        public string? GetTrait(string key)
        {
            if (m_traits != null)
            {
                for (int i = 0; i < m_traits.Length - 1; i += 2)
                {
                    if (m_traits[i] == key)
                        return m_traits[i + 1];
                }
            }

            return null;
        }

        /// <summary>
        /// Displays the name and GUID for the eventSource for debugging purposes.
        /// </summary>
        public override string ToString()
        {
            if (!IsSupported)
                return base.ToString()!;

            return SR.Format(SR.EventSource_ToString, Name, Guid);
        }

        /// <summary>
        /// Fires when a Command (e.g. Enable) comes from a an EventListener.
        /// </summary>
        public event EventHandler<EventCommandEventArgs>? EventCommandExecuted
        {
            add
            {
                if (value == null)
                    return;

                m_eventCommandExecuted += value;

                if (m_completelyInited)
                {
                    // If we have an EventHandler<EventCommandEventArgs> attached to the EventSource before the first command arrives
                    // It should get a chance to handle the deferred commands.
                    EventCommandEventArgs? deferredCommands = m_deferredCommands;
                    while (deferredCommands != null)
                    {
                        value(this, deferredCommands);
                        deferredCommands = deferredCommands.nextCommand;
                    }
                }
            }
            remove
            {
                m_eventCommandExecuted -= value;
            }
        }

#region ActivityID

        /// <summary>
        /// When a thread starts work that is on behalf of 'something else' (typically another
        /// thread or network request) it should mark the thread as working on that other work.
        /// This API marks the current thread as working on activity 'activityID'. This API
        /// should be used when the caller knows the thread's current activity (the one being
        /// overwritten) has completed. Otherwise, callers should prefer the overload that
        /// return the oldActivityThatWillContinue (below).
        ///
        /// All events created with the EventSource on this thread are also tagged with the
        /// activity ID of the thread.
        ///
        /// It is common, and good practice after setting the thread to an activity to log an event
        /// with a 'start' opcode to indicate that precise time/thread where the new activity
        /// started.
        /// </summary>
        /// <param name="activityId">A Guid that represents the new activity with which to mark
        /// the current thread</param>
        public static void SetCurrentThreadActivityId(Guid activityId)
        {
            if (!IsSupported)
            {
                return;
            }

            TplEventSource.Log?.SetActivityId(activityId);

            // We ignore errors to keep with the convention that EventSources do not throw errors.
            // Note we can't access m_throwOnWrites because this is a static method.
#if FEATURE_PERFTRACING
            // Set the activity id via EventPipe.
            EventPipeEventProvider.EventActivityIdControl(
                Interop.Advapi32.ActivityControl.EVENT_ACTIVITY_CTRL_SET_ID,
                ref activityId);
#endif // FEATURE_PERFTRACING
#if TARGET_WINDOWS
            // Set the activity id via ETW.
            Interop.Advapi32.EventActivityIdControl(
                Interop.Advapi32.ActivityControl.EVENT_ACTIVITY_CTRL_SET_ID,
                ref activityId);
#endif // TARGET_WINDOWS
        }

        /// <summary>
        /// Retrieves the ETW activity ID associated with the current thread.
        /// </summary>
        public static Guid CurrentThreadActivityId
        {
            get
            {
                if (!IsSupported)
                {
                    return default;
                }

                // We ignore errors to keep with the convention that EventSources do not throw
                // errors. Note we can't access m_throwOnWrites because this is a static method.
                Guid retVal = default;
#if TARGET_WINDOWS
                Interop.Advapi32.EventActivityIdControl(
                    Interop.Advapi32.ActivityControl.EVENT_ACTIVITY_CTRL_GET_ID,
                    ref retVal);
#elif FEATURE_PERFTRACING
                EventPipeEventProvider.EventActivityIdControl(
                    Interop.Advapi32.ActivityControl.EVENT_ACTIVITY_CTRL_GET_ID,
                    ref retVal);
#endif // TARGET_WINDOWS
                return retVal;
            }
        }

        /// <summary>
        /// When a thread starts work that is on behalf of 'something else' (typically another
        /// thread or network request) it should mark the thread as working on that other work.
        /// This API marks the current thread as working on activity 'activityID'. It returns
        /// whatever activity the thread was previously marked with. There is a convention that
        /// callers can assume that callees restore this activity mark before the callee returns.
        /// To encourage this, this API returns the old activity, so that it can be restored later.
        ///
        /// All events created with the EventSource on this thread are also tagged with the
        /// activity ID of the thread.
        ///
        /// It is common, and good practice after setting the thread to an activity to log an event
        /// with a 'start' opcode to indicate that precise time/thread where the new activity
        /// started.
        /// </summary>
        /// <param name="activityId">A Guid that represents the new activity with which to mark
        /// the current thread</param>
        /// <param name="oldActivityThatWillContinue">The Guid that represents the current activity
        /// which will continue at some point in the future, on the current thread</param>
        public static void SetCurrentThreadActivityId(Guid activityId, out Guid oldActivityThatWillContinue)
        {
            if (!IsSupported)
            {
                oldActivityThatWillContinue = default;
                return;
            }

            oldActivityThatWillContinue = activityId;
            // We ignore errors to keep with the convention that EventSources do not throw errors.
            // Note we can't access m_throwOnWrites because this is a static method.

#if FEATURE_PERFTRACING && TARGET_WINDOWS
            EventPipeEventProvider.EventActivityIdControl(
                Interop.Advapi32.ActivityControl.EVENT_ACTIVITY_CTRL_SET_ID,
                    ref oldActivityThatWillContinue);
#elif FEATURE_PERFTRACING
            EventPipeEventProvider.EventActivityIdControl(
                Interop.Advapi32.ActivityControl.EVENT_ACTIVITY_CTRL_GET_SET_ID,
                    ref oldActivityThatWillContinue);
#endif // FEATURE_PERFTRACING && TARGET_WINDOWS

#if TARGET_WINDOWS
            Interop.Advapi32.EventActivityIdControl(
                Interop.Advapi32.ActivityControl.EVENT_ACTIVITY_CTRL_GET_SET_ID,
                    ref oldActivityThatWillContinue);
#endif // TARGET_WINDOWS

            // We don't call the activityDying callback here because the caller has declared that
            // it is not dying.
            TplEventSource.Log?.SetActivityId(activityId);
        }
#endregion

#region protected
        /// <summary>
        /// This is the constructor that most users will use to create their eventSource.   It takes
        /// no parameters.  The ETW provider name and GUID of the EventSource are determined by the EventSource
        /// custom attribute (so you can determine these things declaratively).   If the GUID for the eventSource
        /// is not specified in the EventSourceAttribute (recommended), it is Generated by hashing the name.
        /// If the ETW provider name of the EventSource is not given, the name of the EventSource class is used as
        /// the ETW provider name.
        /// </summary>
        protected EventSource()
            : this(EventSourceSettings.EtwManifestEventFormat)
        {
        }

        /// <summary>
        /// By default calling the 'WriteEvent' methods do NOT throw on errors (they silently discard the event).
        /// This is because in most cases users assume logging is not 'precious' and do NOT wish to have logging failures
        /// crash the program. However for those applications where logging is 'precious' and if it fails the caller
        /// wishes to react, setting 'throwOnEventWriteErrors' will cause an exception to be thrown if WriteEvent
        /// fails. Note the fact that EventWrite succeeds does not necessarily mean that the event reached its destination
        /// only that operation of writing it did not fail. These EventSources will not generate self-describing ETW events.
        ///
        /// For compatibility only use the EventSourceSettings.ThrowOnEventWriteErrors flag instead.
        /// </summary>
        // [Obsolete("Use the EventSource(EventSourceSettings) overload")]
        protected EventSource(bool throwOnEventWriteErrors)
            : this(EventSourceSettings.EtwManifestEventFormat | (throwOnEventWriteErrors ? EventSourceSettings.ThrowOnEventWriteErrors : 0))
        { }

        /// <summary>
        /// Construct an EventSource with additional non-default settings (see EventSourceSettings for more)
        /// </summary>
        protected EventSource(EventSourceSettings settings) : this(settings, null) { }

        /// <summary>
        /// Construct an EventSource with additional non-default settings.
        ///
        /// Also specify a list of key-value pairs called traits (you must pass an even number of strings).
        /// The first string is the key and the second is the value.   These are not interpreted by EventSource
        /// itself but may be interpreted the listeners.  Can be fetched with GetTrait(string).
        /// </summary>
        /// <param name="settings">See EventSourceSettings for more.</param>
        /// <param name="traits">A collection of key-value strings (must be an even number).</param>
        protected EventSource(EventSourceSettings settings, params string[]? traits)
        {
            if (IsSupported)
            {
#if FEATURE_PERFTRACING
                m_eventHandleTable = new TraceLoggingEventHandleTable();
#endif
                m_config = ValidateSettings(settings);

                Type myType = this.GetType();
                Guid eventSourceGuid = GetGuid(myType);
                string eventSourceName = GetName(myType);

                Initialize(eventSourceGuid, eventSourceName, traits);
            }
        }

#if FEATURE_PERFTRACING
        // Generate the serialized blobs that describe events for all strongly typed events (that is events that define strongly
        // typed event methods. Dynamically defined events (that use Write) hare defined on the fly and are handled elsewhere.
        private unsafe void DefineEventPipeEvents()
        {
            // If the EventSource is set to emit all events as TraceLogging events, skip this initialization.
            // Events will be defined when they are emitted for the first time.
            if (SelfDescribingEvents)
            {
                return;
            }

            Debug.Assert(m_eventData != null);
            Debug.Assert(m_eventPipeProvider != null);
            foreach (int metaKey in m_eventData.Keys)
            {
                ref EventMetadata eventMetadata = ref CollectionsMarshal.GetValueRefOrNullRef(m_eventData, metaKey);
                uint eventID = (uint)eventMetadata.Descriptor.EventId;
                if (eventID == 0)
                    continue;

                byte[]? metadata = EventPipeMetadataGenerator.Instance.GenerateEventMetadata(eventMetadata);
                uint metadataLength = (metadata != null) ? (uint)metadata.Length : 0;

                string eventName = eventMetadata.Name;
                long keywords = eventMetadata.Descriptor.Keywords;
                uint eventVersion = eventMetadata.Descriptor.Version;
                uint level = eventMetadata.Descriptor.Level;

                fixed (byte *pMetadata = metadata)
                {
                    IntPtr eventHandle = m_eventPipeProvider._eventProvider.DefineEventHandle(
                        eventID,
                        eventName,
                        keywords,
                        eventVersion,
                        level,
                        pMetadata,
                        metadataLength);

                    Debug.Assert(eventHandle != IntPtr.Zero);
                    eventMetadata.EventHandle = eventHandle;
                }
            }
        }
#endif

        /// <summary>
        /// This method is called when the eventSource is updated by the controller.
        /// </summary>
        protected virtual void OnEventCommand(EventCommandEventArgs command) { }

#pragma warning disable 1591
        // optimized for common signatures (no args)
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
                   Justification = EventSourceSuppressMessage)]
        protected unsafe void WriteEvent(int eventId)
        {
            WriteEventCore(eventId, 0, null);
        }

        // optimized for common signatures (ints)
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
                   Justification = EventSourceSuppressMessage)]
        protected unsafe void WriteEvent(int eventId, int arg1)
        {
            if (IsEnabled())
            {
                EventData* descrs = stackalloc EventData[1];
                descrs[0].DataPointer = (IntPtr)(&arg1);
                descrs[0].Size = 4;
                descrs[0].Reserved = 0;
                WriteEventCore(eventId, 1, descrs);
            }
        }

        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
                   Justification = EventSourceSuppressMessage)]
        protected unsafe void WriteEvent(int eventId, int arg1, int arg2)
        {
            if (IsEnabled())
            {
                EventData* descrs = stackalloc EventData[2];
                descrs[0].DataPointer = (IntPtr)(&arg1);
                descrs[0].Size = 4;
                descrs[0].Reserved = 0;
                descrs[1].DataPointer = (IntPtr)(&arg2);
                descrs[1].Size = 4;
                descrs[1].Reserved = 0;
                WriteEventCore(eventId, 2, descrs);
            }
        }

        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
                   Justification = EventSourceSuppressMessage)]
        protected unsafe void WriteEvent(int eventId, int arg1, int arg2, int arg3)
        {
            if (IsEnabled())
            {
                EventData* descrs = stackalloc EventData[3];
                descrs[0].DataPointer = (IntPtr)(&arg1);
                descrs[0].Size = 4;
                descrs[0].Reserved = 0;
                descrs[1].DataPointer = (IntPtr)(&arg2);
                descrs[1].Size = 4;
                descrs[1].Reserved = 0;
                descrs[2].DataPointer = (IntPtr)(&arg3);
                descrs[2].Size = 4;
                descrs[2].Reserved = 0;
                WriteEventCore(eventId, 3, descrs);
            }
        }

        // optimized for common signatures (longs)
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
                   Justification = EventSourceSuppressMessage)]
        protected unsafe void WriteEvent(int eventId, long arg1)
        {
            if (IsEnabled())
            {
                EventData* descrs = stackalloc EventData[1];
                descrs[0].DataPointer = (IntPtr)(&arg1);
                descrs[0].Size = 8;
                descrs[0].Reserved = 0;
                WriteEventCore(eventId, 1, descrs);
            }
        }

        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
                   Justification = EventSourceSuppressMessage)]
        protected unsafe void WriteEvent(int eventId, long arg1, long arg2)
        {
            if (IsEnabled())
            {
                EventData* descrs = stackalloc EventData[2];
                descrs[0].DataPointer = (IntPtr)(&arg1);
                descrs[0].Size = 8;
                descrs[0].Reserved = 0;
                descrs[1].DataPointer = (IntPtr)(&arg2);
                descrs[1].Size = 8;
                descrs[1].Reserved = 0;
                WriteEventCore(eventId, 2, descrs);
            }
        }

        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
                   Justification = EventSourceSuppressMessage)]
        protected unsafe void WriteEvent(int eventId, long arg1, long arg2, long arg3)
        {
            if (IsEnabled())
            {
                EventData* descrs = stackalloc EventData[3];
                descrs[0].DataPointer = (IntPtr)(&arg1);
                descrs[0].Size = 8;
                descrs[0].Reserved = 0;
                descrs[1].DataPointer = (IntPtr)(&arg2);
                descrs[1].Size = 8;
                descrs[1].Reserved = 0;
                descrs[2].DataPointer = (IntPtr)(&arg3);
                descrs[2].Size = 8;
                descrs[2].Reserved = 0;
                WriteEventCore(eventId, 3, descrs);
            }
        }

        // optimized for common signatures (strings)
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
                   Justification = EventSourceSuppressMessage)]
        protected unsafe void WriteEvent(int eventId, string? arg1)
        {
            if (IsEnabled())
            {
                arg1 ??= "";
                fixed (char* string1Bytes = arg1)
                {
                    EventData* descrs = stackalloc EventData[1];
                    descrs[0].DataPointer = (IntPtr)string1Bytes;
                    descrs[0].Size = ((arg1.Length + 1) * 2);
                    descrs[0].Reserved = 0;
                    WriteEventCore(eventId, 1, descrs);
                }
            }
        }

        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
                   Justification = EventSourceSuppressMessage)]
        protected unsafe void WriteEvent(int eventId, string? arg1, string? arg2)
        {
            if (IsEnabled())
            {
                arg1 ??= "";
                arg2 ??= "";
                fixed (char* string1Bytes = arg1)
                fixed (char* string2Bytes = arg2)
                {
                    EventData* descrs = stackalloc EventData[2];
                    descrs[0].DataPointer = (IntPtr)string1Bytes;
                    descrs[0].Size = ((arg1.Length + 1) * 2);
                    descrs[0].Reserved = 0;
                    descrs[1].DataPointer = (IntPtr)string2Bytes;
                    descrs[1].Size = ((arg2.Length + 1) * 2);
                    descrs[1].Reserved = 0;
                    WriteEventCore(eventId, 2, descrs);
                }
            }
        }

        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
                   Justification = EventSourceSuppressMessage)]
        protected unsafe void WriteEvent(int eventId, string? arg1, string? arg2, string? arg3)
        {
            if (IsEnabled())
            {
                arg1 ??= "";
                arg2 ??= "";
                arg3 ??= "";
                fixed (char* string1Bytes = arg1)
                fixed (char* string2Bytes = arg2)
                fixed (char* string3Bytes = arg3)
                {
                    EventData* descrs = stackalloc EventData[3];
                    descrs[0].DataPointer = (IntPtr)string1Bytes;
                    descrs[0].Size = ((arg1.Length + 1) * 2);
                    descrs[0].Reserved = 0;
                    descrs[1].DataPointer = (IntPtr)string2Bytes;
                    descrs[1].Size = ((arg2.Length + 1) * 2);
                    descrs[1].Reserved = 0;
                    descrs[2].DataPointer = (IntPtr)string3Bytes;
                    descrs[2].Size = ((arg3.Length + 1) * 2);
                    descrs[2].Reserved = 0;
                    WriteEventCore(eventId, 3, descrs);
                }
            }
        }

        // optimized for common signatures (string and ints)
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
                   Justification = EventSourceSuppressMessage)]
        protected unsafe void WriteEvent(int eventId, string? arg1, int arg2)
        {
            if (IsEnabled())
            {
                arg1 ??= "";
                fixed (char* string1Bytes = arg1)
                {
                    EventData* descrs = stackalloc EventData[2];
                    descrs[0].DataPointer = (IntPtr)string1Bytes;
                    descrs[0].Size = ((arg1.Length + 1) * 2);
                    descrs[0].Reserved = 0;
                    descrs[1].DataPointer = (IntPtr)(&arg2);
                    descrs[1].Size = 4;
                    descrs[1].Reserved = 0;
                    WriteEventCore(eventId, 2, descrs);
                }
            }
        }

        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
                   Justification = EventSourceSuppressMessage)]
        protected unsafe void WriteEvent(int eventId, string? arg1, int arg2, int arg3)
        {
            if (IsEnabled())
            {
                arg1 ??= "";
                fixed (char* string1Bytes = arg1)
                {
                    EventData* descrs = stackalloc EventData[3];
                    descrs[0].DataPointer = (IntPtr)string1Bytes;
                    descrs[0].Size = ((arg1.Length + 1) * 2);
                    descrs[0].Reserved = 0;
                    descrs[1].DataPointer = (IntPtr)(&arg2);
                    descrs[1].Size = 4;
                    descrs[1].Reserved = 0;
                    descrs[2].DataPointer = (IntPtr)(&arg3);
                    descrs[2].Size = 4;
                    descrs[2].Reserved = 0;
                    WriteEventCore(eventId, 3, descrs);
                }
            }
        }

        // optimized for common signatures (string and longs)
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
                   Justification = EventSourceSuppressMessage)]
        protected unsafe void WriteEvent(int eventId, string? arg1, long arg2)
        {
            if (IsEnabled())
            {
                arg1 ??= "";
                fixed (char* string1Bytes = arg1)
                {
                    EventData* descrs = stackalloc EventData[2];
                    descrs[0].DataPointer = (IntPtr)string1Bytes;
                    descrs[0].Size = ((arg1.Length + 1) * 2);
                    descrs[0].Reserved = 0;
                    descrs[1].DataPointer = (IntPtr)(&arg2);
                    descrs[1].Size = 8;
                    descrs[1].Reserved = 0;
                    WriteEventCore(eventId, 2, descrs);
                }
            }
        }

        // optimized for common signatures (long and string)
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
                   Justification = EventSourceSuppressMessage)]
        protected unsafe void WriteEvent(int eventId, long arg1, string? arg2)
        {
            if (IsEnabled())
            {
                arg2 ??= "";
                fixed (char* string2Bytes = arg2)
                {
                    EventData* descrs = stackalloc EventData[2];
                    descrs[0].DataPointer = (IntPtr)(&arg1);
                    descrs[0].Size = 8;
                    descrs[0].Reserved = 0;
                    descrs[1].DataPointer = (IntPtr)string2Bytes;
                    descrs[1].Size = ((arg2.Length + 1) * 2);
                    descrs[1].Reserved = 0;
                    WriteEventCore(eventId, 2, descrs);
                }
            }
        }

        // optimized for common signatures (int and string)
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
                   Justification = EventSourceSuppressMessage)]
        protected unsafe void WriteEvent(int eventId, int arg1, string? arg2)
        {
            if (IsEnabled())
            {
                arg2 ??= "";
                fixed (char* string2Bytes = arg2)
                {
                    EventData* descrs = stackalloc EventData[2];
                    descrs[0].DataPointer = (IntPtr)(&arg1);
                    descrs[0].Size = 4;
                    descrs[0].Reserved = 0;
                    descrs[1].DataPointer = (IntPtr)string2Bytes;
                    descrs[1].Size = ((arg2.Length + 1) * 2);
                    descrs[1].Reserved = 0;
                    WriteEventCore(eventId, 2, descrs);
                }
            }
        }

        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
                   Justification = EventSourceSuppressMessage)]
        protected unsafe void WriteEvent(int eventId, byte[]? arg1)
        {
            if (IsEnabled())
            {
                EventData* descrs = stackalloc EventData[2];
                if (arg1 == null || arg1.Length == 0)
                {
                    int blobSize = 0;
                    descrs[0].DataPointer = (IntPtr)(&blobSize);
                    descrs[0].Size = 4;
                    descrs[0].Reserved = 0;
                    descrs[1].DataPointer = (IntPtr)(&blobSize); // valid address instead of empty content
                    descrs[1].Size = 0;
                    descrs[1].Reserved = 0;
                    WriteEventCore(eventId, 2, descrs);
                }
                else
                {
                    int blobSize = arg1.Length;
                    fixed (byte* blob = &arg1[0])
                    {
                        descrs[0].DataPointer = (IntPtr)(&blobSize);
                        descrs[0].Size = 4;
                        descrs[0].Reserved = 0;
                        descrs[1].DataPointer = (IntPtr)blob;
                        descrs[1].Size = blobSize;
                        descrs[1].Reserved = 0;
                        WriteEventCore(eventId, 2, descrs);
                    }
                }
            }
        }

        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
                   Justification = EventSourceSuppressMessage)]
        protected unsafe void WriteEvent(int eventId, long arg1, byte[]? arg2)
        {
            if (IsEnabled())
            {
                EventData* descrs = stackalloc EventData[3];
                descrs[0].DataPointer = (IntPtr)(&arg1);
                descrs[0].Size = 8;
                descrs[0].Reserved = 0;
                if (arg2 == null || arg2.Length == 0)
                {
                    int blobSize = 0;
                    descrs[1].DataPointer = (IntPtr)(&blobSize);
                    descrs[1].Size = 4;
                    descrs[1].Reserved = 0;
                    descrs[2].DataPointer = (IntPtr)(&blobSize); // valid address instead of empty contents
                    descrs[2].Size = 0;
                    descrs[2].Reserved = 0;
                    WriteEventCore(eventId, 3, descrs);
                }
                else
                {
                    int blobSize = arg2.Length;
                    fixed (byte* blob = &arg2[0])
                    {
                        descrs[1].DataPointer = (IntPtr)(&blobSize);
                        descrs[1].Size = 4;
                        descrs[1].Reserved = 0;
                        descrs[2].DataPointer = (IntPtr)blob;
                        descrs[2].Size = blobSize;
                        descrs[2].Reserved = 0;
                        WriteEventCore(eventId, 3, descrs);
                    }
                }
            }
        }

        // Returns the object as a IntPtr - safe when only used for logging
        internal static unsafe nint ObjectIDForEvents(object? o) => *(nint*)&o;

#pragma warning restore 1591

        /// <summary>
        /// A wrapper type for separating primitive types (int, long, string, etc) from other types
        /// in the EventSource API. This type shouldn't be used directly, but just as implicit conversions
        /// when using the WriteEvent API.
        /// </summary>
        public readonly struct EventSourcePrimitive
        {
            internal readonly object? Value;

            private EventSourcePrimitive(object? value)
            {
                Value = value;
            }
            public static implicit operator EventSourcePrimitive(bool value) => new(value);
            public static implicit operator EventSourcePrimitive(byte value) => new(value);
            public static implicit operator EventSourcePrimitive(short value) => new(value);
            public static implicit operator EventSourcePrimitive(int value) => new(value);
            public static implicit operator EventSourcePrimitive(long value) => new(value);

            [CLSCompliant(false)]
            public static implicit operator EventSourcePrimitive(sbyte value) => new(value);
            [CLSCompliant(false)]
            public static implicit operator EventSourcePrimitive(ushort value) => new(value);
            [CLSCompliant(false)]
            public static implicit operator EventSourcePrimitive(uint value) => new(value);
            [CLSCompliant(false)]
            public static implicit operator EventSourcePrimitive(ulong value) => new(value);
            [CLSCompliant(false)]
            // Added to prevent going through the nuint -> ulong conversion
            public static implicit operator EventSourcePrimitive(nuint value) => new(value);

            public static implicit operator EventSourcePrimitive(float value) => new(value);
            public static implicit operator EventSourcePrimitive(double value) => new(value);
            public static implicit operator EventSourcePrimitive(decimal value) => new(value);

            public static implicit operator EventSourcePrimitive(string? value) => new(value);
            public static implicit operator EventSourcePrimitive(byte[]? value) => new(value);

            public static implicit operator EventSourcePrimitive(Guid value) => new(value);
            public static implicit operator EventSourcePrimitive(DateTime value) => new(value);
            public static implicit operator EventSourcePrimitive(nint value) => new(value);
            public static implicit operator EventSourcePrimitive(char value) => new(value);

            public static implicit operator EventSourcePrimitive(Enum value) => new(value);
        }

        /// <summary>
        /// Used to construct the data structure to be passed to the native ETW APIs - EventWrite and EventWriteTransfer.
        /// </summary>
        protected internal struct EventData
        {
            /// <summary>
            /// Address where the one argument lives (if this points to managed memory you must ensure the
            /// managed object is pinned.
            /// </summary>
            public unsafe IntPtr DataPointer
            {
                get => (IntPtr)(void*)m_Ptr;
                set => m_Ptr = unchecked((ulong)(void*)value);
            }

            /// <summary>
            /// Size of the argument referenced by DataPointer
            /// </summary>
            public int Size
            {
                get => m_Size;
                set => m_Size = value;
            }

            /// <summary>
            /// Reserved by ETW.  This property is present to ensure that we can zero it
            /// since System.Private.CoreLib uses are not zero'd.
            /// </summary>
            internal int Reserved
            {
                get => m_Reserved;
                set => m_Reserved = value;
            }

            #region private
            /// <summary>
            /// Initializes the members of this EventData object to point at a previously-pinned
            /// tracelogging-compatible metadata blob.
            /// </summary>
            /// <param name="pointer">Pinned tracelogging-compatible metadata blob.</param>
            /// <param name="size">The size of the metadata blob.</param>
            /// <param name="reserved">Value for reserved: 2 for per-provider metadata, 1 for per-event metadata</param>
            internal unsafe void SetMetadata(byte* pointer, int size, int reserved)
            {
                this.m_Ptr = (ulong)pointer;
                this.m_Size = size;
                this.m_Reserved = reserved; // Mark this descriptor as containing tracelogging-compatible metadata.
            }

            // Important, we pass this structure directly to the Win32 EventWrite API, so this structure must
            // be laid out exactly the way EventWrite wants it.
            internal ulong m_Ptr;
            internal int m_Size;
#pragma warning disable 0649
            internal int m_Reserved;       // Used to pad the size to match the Win32 API
#pragma warning restore 0649
#endregion
        }

        /// <summary>
        /// This routine allows you to create efficient WriteEvent helpers, however the code that you use to
        /// do this, while straightforward, is unsafe.
        /// </summary>
        /// <remarks>
        /// <code>
        ///    protected unsafe void WriteEvent(int eventId, string arg1, long arg2)
        ///    {
        ///        if (IsEnabled())
        ///        {
        ///            arg2 ??= "";
        ///            fixed (char* string2Bytes = arg2)
        ///            {
        ///                EventSource.EventData* descrs = stackalloc EventSource.EventData[2];
        ///                descrs[0].DataPointer = (IntPtr)(&amp;arg1);
        ///                descrs[0].Size = 8;
        ///                descrs[0].Reserved = 0;
        ///                descrs[1].DataPointer = (IntPtr)string2Bytes;
        ///                descrs[1].Size = ((arg2.Length + 1) * 2);
        ///                descrs[1].Reserved = 0;
        ///                WriteEventCore(eventId, 2, descrs);
        ///            }
        ///        }
        ///    }
        /// </code>
        /// </remarks>
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2112:ReflectionToRequiresUnreferencedCode",
                    Justification = "EnsureDescriptorsInitialized's use of GetType preserves this method which " +
                                    "requires unreferenced code, but EnsureDescriptorsInitialized does not access this member and is safe to call.")]
        [RequiresUnreferencedCode(EventSourceRequiresUnreferenceMessage)]
        [CLSCompliant(false)]
        protected unsafe void WriteEventCore(int eventId, int eventDataCount, EventData* data)
        {
            WriteEventWithRelatedActivityIdCore(eventId, null, eventDataCount, data);
        }

        /// <summary>
        /// This routine allows you to create efficient WriteEventWithRelatedActivityId helpers, however the code
        /// that you use to do this, while straightforward, is unsafe. The only difference from
        /// <see cref="WriteEventCore"/> is that you pass the relatedActivityId from caller through to this API
        /// </summary>
        /// <remarks>
        /// <code>
        ///    protected unsafe void WriteEventWithRelatedActivityId(int eventId, Guid relatedActivityId, string arg1, long arg2)
        ///    {
        ///        if (IsEnabled())
        ///        {
        ///            arg2 ??= "";
        ///            fixed (char* string2Bytes = arg2)
        ///            {
        ///                EventSource.EventData* descrs = stackalloc EventSource.EventData[2];
        ///                descrs[0].DataPointer = (IntPtr)(&amp;arg1);
        ///                descrs[0].Size = 8;
        ///                descrs[1].DataPointer = (IntPtr)string2Bytes;
        ///                descrs[1].Size = ((arg2.Length + 1) * 2);
        ///                WriteEventWithRelatedActivityIdCore(eventId, relatedActivityId, 2, descrs);
        ///            }
        ///        }
        ///    }
        /// </code>
        /// </remarks>
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2112:ReflectionToRequiresUnreferencedCode",
                    Justification = "EnsureDescriptorsInitialized's use of GetType preserves this method which " +
                                    "requires unreferenced code, but EnsureDescriptorsInitialized does not access this member and is safe to call.")]
        [RequiresUnreferencedCode(EventSourceRequiresUnreferenceMessage)]
        [CLSCompliant(false)]
        protected unsafe void WriteEventWithRelatedActivityIdCore(int eventId, Guid* relatedActivityId, int eventDataCount, EventData* data)
        {
            if (IsEnabled())
            {
                Debug.Assert(m_eventData != null);  // You must have initialized this if you enabled the source.
                try
                {
                    ref EventMetadata metadata = ref CollectionsMarshal.GetValueRefOrNullRef(m_eventData, eventId);

                    EventOpcode opcode = (EventOpcode)metadata.Descriptor.Opcode;
                    Guid* pActivityId = null;
                    Guid activityId = Guid.Empty;
                    Guid relActivityId = Guid.Empty;

                    if (opcode != EventOpcode.Info && relatedActivityId == null &&
                       ((metadata.ActivityOptions & EventActivityOptions.Disable) == 0))
                    {
                        if (opcode == EventOpcode.Start)
                        {
                            m_activityTracker.OnStart(m_name, metadata.Name, metadata.Descriptor.Task, ref activityId, ref relActivityId, metadata.ActivityOptions);
                        }
                        else if (opcode == EventOpcode.Stop)
                        {
                            m_activityTracker.OnStop(m_name, metadata.Name, metadata.Descriptor.Task, ref activityId);
                        }

                        if (activityId != Guid.Empty)
                            pActivityId = &activityId;
                        if (relActivityId != Guid.Empty)
                            relatedActivityId = &relActivityId;
                    }

                    if (!SelfDescribingEvents)
                    {
                        if (metadata.EnabledForETW && !m_etwProvider.WriteEvent(ref metadata.Descriptor, metadata.EventHandle, pActivityId, relatedActivityId, eventDataCount, (IntPtr)data))
                            ThrowEventSourceException(metadata.Name);
#if FEATURE_PERFTRACING
                        if (metadata.EnabledForEventPipe && !m_eventPipeProvider.WriteEvent(ref metadata.Descriptor, metadata.EventHandle, pActivityId, relatedActivityId, eventDataCount, (IntPtr)data))
                            ThrowEventSourceException(metadata.Name);
#endif // FEATURE_PERFTRACING
                    }
                    else if (metadata.EnabledForETW
#if FEATURE_PERFTRACING
                            || metadata.EnabledForEventPipe
#endif // FEATURE_PERFTRACING
                            )
                    {
                        EventSourceOptions opt = new EventSourceOptions
                        {
                            Keywords = (EventKeywords)metadata.Descriptor.Keywords,
                            Level = (EventLevel)metadata.Descriptor.Level,
                            Opcode = (EventOpcode)metadata.Descriptor.Opcode
                        };

                        WriteMultiMerge(metadata.Name, ref opt, metadata.TraceLoggingEventTypes, pActivityId, relatedActivityId, data);
                    }

                    if (m_Dispatchers != null && metadata.EnabledForAnyListener)
                    {
#if MONO && !TARGET_WASI
                        // On Mono, managed events from NativeRuntimeEventSource are written using WriteEventCore which can be
                        // written doubly because EventPipe tries to pump it back up to EventListener via NativeRuntimeEventSource.ProcessEvents.
                        // So we need to prevent this from getting written directly to the Listeners.
                        if (this.GetType() != typeof(NativeRuntimeEventSource))
#endif // MONO && !TARGET_WASI
                        {
                            var eventCallbackArgs = new EventWrittenEventArgs(this, eventId, pActivityId, relatedActivityId);
                            WriteToAllListeners(eventCallbackArgs, eventDataCount, data);
                        }
                    }
                }
                catch (Exception ex)
                {
                    if (ex is EventSourceException)
                        throw;
                    else
                    {
                        ref EventMetadata metadata = ref CollectionsMarshal.GetValueRefOrNullRef(m_eventData, eventId);
                        ThrowEventSourceException(metadata.Name, ex);
                    }
                }
            }
        }

        /// <summary>
        /// This is a varargs helper for writing an event. It does create an array and box all the arguments so it is
        /// relatively inefficient and should only be used for relatively rare events (e.g. less than 100 / sec). If your
        /// rates are faster than that you should use <see cref="WriteEventCore"/> to create fast helpers for your particular
        /// method signature. Even if you use this for rare events, this call should be guarded by an <see cref="IsEnabled()"/>
        /// check so that the varargs call is not made when the EventSource is not active.
        /// </summary>
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
                   Justification = EventSourceSuppressMessage)]
        protected void WriteEvent(int eventId, params EventSourcePrimitive[] args)
        {
            var argValues = new object?[args.Length];
            for (int i = 0; i < args.Length; i++)
            {
                argValues[i] = args[i].Value;
            }
            unsafe
            {
                WriteEventVarargs(eventId, null, argValues);
            }
        }

        // fallback varags helpers.
        /// <summary>
        /// This is the varargs helper for writing an event. It does create an array and box all the arguments so it is
        /// relatively inefficient and should only be used for relatively rare events (e.g. less than 100 / sec). If your
        /// rates are faster than that you should use <see cref="WriteEventCore"/> to create fast helpers for your particular
        /// method signature. Even if you use this for rare events, this call should be guarded by an <see cref="IsEnabled()"/>
        /// check so that the varargs call is not made when the EventSource is not active.
        /// </summary>
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2112:ReflectionToRequiresUnreferencedCode",
                    Justification = "EnsureDescriptorsInitialized's use of GetType preserves this method which " +
                                    "requires unreferenced code, but EnsureDescriptorsInitialized does not access this member and is safe to call.")]
        [RequiresUnreferencedCode(EventSourceRequiresUnreferenceMessage)]
        protected unsafe void WriteEvent(int eventId, params object?[] args)
        {
            WriteEventVarargs(eventId, null, args);
        }

        /// <summary>
        /// This is the varargs helper for writing an event which also specifies a related activity. It is completely analogous
        /// to corresponding WriteEvent (they share implementation). It does create an array and box all the arguments so it is
        /// relatively inefficient and should only be used for relatively rare events (e.g. less than 100 / sec).  If your
        /// rates are faster than that you should use <see cref="WriteEventWithRelatedActivityIdCore"/> to create fast helpers for your
        /// particular method signature. Even if you use this for rare events, this call should be guarded by an <see cref="IsEnabled()"/>
        /// check so that the varargs call is not made when the EventSource is not active.
        /// </summary>
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2112:ReflectionToRequiresUnreferencedCode",
                    Justification = "EnsureDescriptorsInitialized's use of GetType preserves this method which " +
                                    "requires unreferenced code, but EnsureDescriptorsInitialized does not access this member and is safe to call.")]
        [RequiresUnreferencedCode(EventSourceRequiresUnreferenceMessage)]
        protected unsafe void WriteEventWithRelatedActivityId(int eventId, Guid relatedActivityId, params object?[] args)
        {
            WriteEventVarargs(eventId, &relatedActivityId, args);
        }

#endregion

#region IDisposable Members
        /// <summary>
        /// Disposes of an EventSource.
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }
        /// <summary>
        /// Disposes of an EventSource.
        /// </summary>
        /// <remarks>
        /// Called from Dispose() with disposing=true, and from the finalizer (~EventSource) with disposing=false.
        /// Guidelines:
        /// 1. We may be called more than once: do nothing after the first call.
        /// 2. Avoid throwing exceptions if disposing is false, i.e. if we're being finalized.
        /// </remarks>
        /// <param name="disposing">True if called from Dispose(), false if called from the finalizer.</param>
        protected virtual void Dispose(bool disposing)
        {
            // NOTE: If !IsSupported, we use ILLink.Substitutions to nop out the finalizer.
            //       Do not add any code before this line (or you'd need to delete the substitution).
            if (!IsSupported)
            {
                return;
            }

            // 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(EventListener.EventListenersLock));

            if (disposing)
            {
                // Send the manifest one more time to ensure circular buffers have a chance to get to this information
                // even in scenarios with a high volume of ETW events.
                if (m_eventSourceEnabled)
                {
                    try
                    {
                        SendManifest(m_rawManifest);
                    }
                    catch { } // If it fails, simply give up.
                    m_eventSourceEnabled = false;
                }
                if (m_etwProvider != null)
                {
                    m_etwProvider.Dispose();
                    m_etwProvider = null!;
                }
#if FEATURE_PERFTRACING
                if (m_eventPipeProvider != null)
                {
                    m_eventPipeProvider.Dispose();
                    m_eventPipeProvider = null!;
                }
#endif
            }
            m_eventSourceEnabled = false;
            m_eventSourceDisposed = true;
        }
        /// <summary>
        /// Finalizer for EventSource
        /// </summary>
        ~EventSource()
        {
            // NOTE: we nop out this method body if !IsSupported using ILLink.Substitutions.
            this.Dispose(false);
        }
        #endregion

        #region private

        private unsafe void WriteEventRaw(
            string? eventName,
            ref EventDescriptor eventDescriptor,
            IntPtr eventHandle,
            Guid* activityID,
            Guid* relatedActivityID,
            int dataCount,
            IntPtr data)
        {
            bool allAreNull = m_etwProvider == null;
            if (m_etwProvider != null
                && !m_etwProvider.WriteEventRaw(ref eventDescriptor, eventHandle, activityID, relatedActivityID, dataCount, data))
            {
                ThrowEventSourceException(eventName);
            }
#if FEATURE_PERFTRACING
            allAreNull &= (m_eventPipeProvider == null);
            if (m_eventPipeProvider != null
                && !m_eventPipeProvider.WriteEventRaw(ref eventDescriptor, eventHandle, activityID, relatedActivityID, dataCount, data))
            {
                ThrowEventSourceException(eventName);
            }
#endif // FEATURE_PERFTRACING
            if (allAreNull)
            {
                ThrowEventSourceException(eventName);
            }
        }

        /// <summary>
        /// Construct an EventSource with a given name for non-contract based events (e.g. those using the Write() API)
        /// and a given Guid to identify the source.
        /// </summary>
        /// <param name="eventSourceName">
        /// The name of the event source. Must not be null.
        /// </param>
        /// <param name="eventSourceGuid">
        /// The unique identifier for the event source. Must not be Guid.Empty.
        /// </param>
        public EventSource(string eventSourceName, Guid eventSourceGuid)
            : this(eventSourceName, eventSourceGuid, EventSourceSettings.EtwManifestEventFormat)
        { }

        /// <summary>
        /// Construct an EventSource with a given name for non-contract based events (e.g. those using the Write() API)
        /// and a given Guid to identify the source.
        ///
        /// Also specify a list of key-value pairs called traits (you must pass an even number of strings).
        /// The first string is the key and the second is the value.   These are not interpreted by EventSource
        /// itself but may be interpreted the listeners.  Can be fetched with GetTrait(string).
        /// </summary>
        /// <param name="eventSourceName">
        /// The name of the event source. Must not be null.
        /// </param>
        /// <param name="eventSourceGuid">
        /// The unique identifier for the event source. Must not be Guid.Empty.
        /// </param>
        /// <param name="settings">
        /// Configuration options for the EventSource as a whole.
        /// </param>
        /// <param name="traits">A collection of key-value strings (must be an even number).</param>
        public EventSource(string eventSourceName, Guid eventSourceGuid, EventSourceSettings settings, string[]? traits = null)
        {
            if (IsSupported)
            {
#if FEATURE_PERFTRACING
                m_eventHandleTable = new TraceLoggingEventHandleTable();
#endif

                m_config = ValidateSettings(settings);
                Initialize(eventSourceGuid, eventSourceName, traits);
            }
        }

        /// <summary>
        /// This method is responsible for the common initialization path from our constructors. It must
        /// not leak any exceptions (otherwise, since most EventSource classes define a static member,
        /// "Log", such an exception would become a cached exception for the initialization of the static
        /// member, and any future access to the "Log" would throw the cached exception).
        /// </summary>
        private unsafe void Initialize(Guid eventSourceGuid, string eventSourceName, string[]? traits)
        {
            try
            {
                m_traits = traits;
                if (m_traits != null && m_traits.Length % 2 != 0)
                {
                    throw new ArgumentException(SR.EventSource_TraitEven, nameof(traits));
                }

                if (eventSourceGuid == Guid.Empty)
                {
                    throw new ArgumentException(SR.EventSource_NeedGuid);
                }

                if (eventSourceName == null)
                {
                    throw new ArgumentException(SR.EventSource_NeedName);
                }

                m_name = eventSourceName;
                m_guid = eventSourceGuid;

                // Enable Implicit Activity tracker
                m_activityTracker = ActivityTracker.Instance;

#if !DEBUG
                if (ProviderMetadata.Length == 0)
#endif
                {
                    // Create and register our provider traits.  We do this early because it is needed to log errors
                    // In the self-describing event case.
                    InitializeProviderMetadata();
                }

                // Register the provider with ETW
                Func<EventSource?> eventSourceFactory = () => this;
                OverrideEventProvider? etwProvider = EventSourceInitHelper.TryGetPreregisteredEtwProvider(eventSourceGuid);
                if (etwProvider == null)
                {
                    etwProvider = new OverrideEventProvider(eventSourceFactory, EventProviderType.ETW);
                    etwProvider.Register(eventSourceGuid, eventSourceName);
    #if TARGET_WINDOWS
                    var providerMetadata = ProviderMetadata;
                    fixed (byte* pMetadata = providerMetadata)
                    {
                        etwProvider.SetInformation(
                            Interop.Advapi32.EVENT_INFO_CLASS.SetTraits,
                            pMetadata,
                            (uint)providerMetadata.Length);
                    }
    #endif // TARGET_WINDOWS
                }

#if FEATURE_PERFTRACING
                // Register the provider with EventPipe
                OverrideEventProvider? eventPipeProvider = EventSourceInitHelper.TryGetPreregisteredEventPipeProvider(eventSourceName);
                if (eventPipeProvider == null)
                {
                    eventPipeProvider = new OverrideEventProvider(eventSourceFactory, EventProviderType.EventPipe);
                    eventPipeProvider.Register(eventSourceGuid, eventSourceName);

                }
#endif
                // Add the eventSource to the global (weak) list.
                // This also sets m_id, which is the index in the list.
                EventListener.AddEventSource(this);

                // OK if we get this far without an exception, then we can at least write out error messages.
                // Set m_provider, which allows this.
                m_etwProvider = etwProvider;

#if FEATURE_PERFTRACING
                m_eventPipeProvider = eventPipeProvider;
#endif
                Debug.Assert(!m_eventSourceEnabled);     // We can't be enabled until we are completely initted.
            }
            catch (Exception e)
            {
                m_constructionException ??= e;
                ReportOutOfBandMessage("ERROR: Exception during construction of EventSource " + Name + ": " + e.Message);
            }

            // Once m_completelyInited is set, you can have concurrency, so all work is under the lock.
            lock (EventListener.EventListenersLock)
            {
                // If there are any deferred commands, we can do them now.
                // This is the most likely place for exceptions to happen.
                // Note that we are NOT resetting m_deferredCommands to NULL here,
                // We are giving for EventHandler<EventCommandEventArgs> that will be attached later
                EventCommandEventArgs? deferredCommands = m_deferredCommands;
                while (deferredCommands != null)
                {
                    DoCommand(deferredCommands);      // This can never throw, it catches them and reports the errors.
                    deferredCommands = deferredCommands.nextCommand;
                }

                if (m_constructionException == null)
                {
                    m_completelyInited = true;
                }
            }
        }

        private static string GetName(Type eventSourceType, EventManifestOptions flags)
        {
            ArgumentNullException.ThrowIfNull(eventSourceType);

            EventSourceAttribute? attrib = (EventSourceAttribute?)EventSourceHelpers.GetCustomAttributeHelper(eventSourceType, typeof(EventSourceAttribute), flags);
            if (attrib != null && attrib.Name != null)
                return attrib.Name;

            return eventSourceType.Name;
        }

        private static unsafe Guid GenerateGuidFromName(string name)
        {
            ReadOnlySpan<byte> namespaceBytes =
            [
                0x48, 0x2C, 0x2D, 0xB2, 0xC3, 0x90, 0x47, 0xC8,
                0x87, 0xF8, 0x1A, 0x15, 0xBF, 0xC1, 0x30, 0xFB,
            ];

            int nameByteCount = Encoding.BigEndianUnicode.GetByteCount(name);
            int totalLength = namespaceBytes.Length + nameByteCount;
            Span<byte> source = totalLength <= 256 ? stackalloc byte[totalLength] : new byte[totalLength];

            namespaceBytes.CopyTo(source);
            Encoding.BigEndianUnicode.GetBytes(name, source.Slice(namespaceBytes.Length));

            Span<byte> bytes = stackalloc byte[20];
            Sha1ForNonSecretPurposes.HashData(source, bytes);

            bytes[7] = unchecked((byte)((bytes[7] & 0x0F) | 0x50));    // Set high 4 bits of octet 7 to 5, as per RFC 4122
            return new Guid(bytes.Slice(0, 16));
        }

        private static unsafe void DecodeObjects(object?[] decodedObjects, Type[] parameterTypes, EventData* data)
        {
            for (int i = 0; i < decodedObjects.Length; i++, data++)
            {
                IntPtr dataPointer = data->DataPointer;
                Type dataType = parameterTypes[i];
                object? decoded;

                if (dataType == typeof(string))
                {
                    goto String;
                }
                else if (dataType == typeof(int))
                {
                    Debug.Assert(data->Size == 4);
                    decoded = *(int*)dataPointer;
                }
                else
                {
                    TypeCode typeCode = Type.GetTypeCode(dataType);
                    int size = data->Size;

                    if (size == 4)
                    {
                        if ((uint)(typeCode - TypeCode.SByte) <= TypeCode.Int32 - TypeCode.SByte)
                        {
                            Debug.Assert(dataType.IsEnum);
                            // Enums less than 4 bytes in size should be treated as int.
                            decoded = *(int*)dataPointer;
                        }
                        else if (typeCode == TypeCode.UInt32)
                        {
                            decoded = *(uint*)dataPointer;
                        }
                        else if (typeCode == TypeCode.Single)
                        {
                            decoded = *(float*)dataPointer;
                        }
                        else if (typeCode == TypeCode.Boolean)
                        {
                            // The manifest defines a bool as a 32bit type (WIN32 BOOL), not 1 bit as CLR Does.
                            decoded = *(int*)dataPointer == 1;
                        }
                        else if (dataType == typeof(byte[]))
                        {
                            // byte[] are written to EventData* as an int followed by a blob
                            Debug.Assert(*(int*)dataPointer == (data + 1)->Size);
                            data++;
                            goto BytePtr;
                        }
                        else if (IntPtr.Size == 4 && dataType == typeof(IntPtr))
                        {
                            decoded = *(IntPtr*)dataPointer;
                        }
                        else
                        {
                            goto Unknown;
                        }
                    }
                    else if (size <= 2)
                    {
                        Debug.Assert(!dataType.IsEnum);
                        if (typeCode == TypeCode.Byte)
                        {
                            Debug.Assert(size == 1);
                            decoded = *(byte*)dataPointer;
                        }
                        else if (typeCode == TypeCode.SByte)
                        {
                            Debug.Assert(size == 1);
                            decoded = *(sbyte*)dataPointer;
                        }
                        else if (typeCode == TypeCode.Int16)
                        {
                            Debug.Assert(size == 2);
                            decoded = *(short*)dataPointer;
                        }
                        else if (typeCode == TypeCode.UInt16)
                        {
                            Debug.Assert(size == 2);
                            decoded = *(ushort*)dataPointer;
                        }
                        else if (typeCode == TypeCode.Char)
                        {
                            Debug.Assert(size == 2);
                            decoded = *(char*)dataPointer;
                        }
                        else
                        {
                            goto Unknown;
                        }
                    }
                    else if (size == 8)
                    {
                        if (typeCode == TypeCode.Int64)
                        {
                            decoded = *(long*)dataPointer;
                        }
                        else if (typeCode == TypeCode.UInt64)
                        {
                            decoded = *(ulong*)dataPointer;
                        }
                        else if (typeCode == TypeCode.Double)
                        {
                            decoded = *(double*)dataPointer;
                        }
                        else if (typeCode == TypeCode.DateTime)
                        {
                            decoded = DateTime.FromFileTimeUtc(*(long*)dataPointer);
                        }
                        else if (IntPtr.Size == 8 && dataType == typeof(IntPtr))
                        {
                            decoded = *(IntPtr*)dataPointer;
                        }
                        else
                        {
                            goto Unknown;
                        }
                    }
                    else if (typeCode == TypeCode.Decimal)
                    {
                        Debug.Assert(size == 16);
                        decoded = *(decimal*)dataPointer;
                    }
                    else if (dataType == typeof(Guid))
                    {
                        Debug.Assert(size == 16);
                        decoded = *(Guid*)dataPointer;
                    }
                    else
                    {
                        goto Unknown;
                    }
                }

                goto Store;

            Unknown:
                if (dataType != typeof(byte*))
                {
                    // Everything else is marshaled as a string.
                    goto String;
                }

            BytePtr:
                if (data->Size == 0)
                {
                    decoded = Array.Empty<byte>();
                }
                else
                {
                    var blob = new byte[data->Size];
                    Marshal.Copy(data->DataPointer, blob, 0, blob.Length);
                    decoded = blob;
                }
                goto Store;

            String:
                // ETW strings are NULL-terminated, so marshal everything up to the first null in the string.
                AssertValidString(data);
                decoded = dataPointer == IntPtr.Zero ? null : new string((char*)dataPointer, 0, (data->Size >> 1) - 1);

            Store:
                decodedObjects[i] = decoded;
            }
        }

        [Conditional("DEBUG")]
        private static unsafe void AssertValidString(EventData* data)
        {
            Debug.Assert(data->Size >= 0 && data->Size % 2 == 0, "String size should be even");
            char* charPointer = (char*)data->DataPointer;
            int charLength = data->Size / 2 - 1;
            for (int i = 0; i < charLength; i++)
            {
                Debug.Assert(*(charPointer + i) != 0, "String may not contain null chars");
            }
            Debug.Assert(*(charPointer + charLength) == 0, "String must be null terminated");
        }

        // Finds the Dispatcher (which holds the filtering state), for a given dispatcher for the current
        // eventSource).
        private EventDispatcher? GetDispatcher(EventListener? listener)
        {
            EventDispatcher? dispatcher = m_Dispatchers;
            while (dispatcher != null)
            {
                if (dispatcher.m_Listener == listener)
                    return dispatcher;
                dispatcher = dispatcher.m_Next;
            }
            return dispatcher;
        }

        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2112:ReflectionToRequiresUnreferencedCode",
                    Justification = "EnsureDescriptorsInitialized's use of GetType preserves this method which " +
                                    "requires unreferenced code, but EnsureDescriptorsInitialized does not access this member and is safe to call.")]
        [RequiresUnreferencedCode(EventSourceRequiresUnreferenceMessage)]
        private unsafe void WriteEventVarargs(int eventId, Guid* childActivityID, object?[] args)
        {
            if (IsEnabled())
            {
                Debug.Assert(m_eventData != null);  // You must have initialized this if you enabled the source.
                try
                {
                    ref EventMetadata metadata = ref CollectionsMarshal.GetValueRefOrNullRef(m_eventData, eventId);

                    if (childActivityID != null)
                    {
                        // If you use WriteEventWithRelatedActivityID you MUST declare the first argument to be a GUID
                        // with the name 'relatedActivityID, and NOT pass this argument to the WriteEvent method.
                        // During manifest creation we modify the ParameterInfo[] that we store to strip out any
                        // first parameter that is of type Guid and named "relatedActivityId." Thus, if you call
                        // WriteEventWithRelatedActivityID from a method that doesn't name its first parameter correctly
                        // we can end up in a state where the ParameterInfo[] doesn't have its first parameter stripped,
                        // and this leads to a mismatch between the number of arguments and the number of ParameterInfos,
                        // which would cause a cryptic IndexOutOfRangeException later if we don't catch it here.
                        if (!metadata.HasRelatedActivityID)
                        {
                            throw new ArgumentException(SR.EventSource_NoRelatedActivityId);
                        }
                    }

                    LogEventArgsMismatches(eventId, args);

                    Guid* pActivityId = null;
                    Guid activityId = Guid.Empty;
                    Guid relatedActivityId = Guid.Empty;
                    EventOpcode opcode = (EventOpcode)metadata.Descriptor.Opcode;
                    EventActivityOptions activityOptions = metadata.ActivityOptions;

                    if (childActivityID == null &&
                       ((activityOptions & EventActivityOptions.Disable) == 0))
                    {
                        if (opcode == EventOpcode.Start)
                        {
                            m_activityTracker.OnStart(m_name, metadata.Name, metadata.Descriptor.Task, ref activityId, ref relatedActivityId, metadata.ActivityOptions);
                        }
                        else if (opcode == EventOpcode.Stop)
                        {
                            m_activityTracker.OnStop(m_name, metadata.Name, metadata.Descriptor.Task, ref activityId);
                        }

                        if (activityId != Guid.Empty)
                            pActivityId = &activityId;
                        if (relatedActivityId != Guid.Empty)
                            childActivityID = &relatedActivityId;
                    }

                    if (metadata.EnabledForETW
#if FEATURE_PERFTRACING
                            || metadata.EnabledForEventPipe
#endif // FEATURE_PERFTRACING
                        )
                    {
                        if (!SelfDescribingEvents)
                        {
                            if (!m_etwProvider.WriteEvent(ref metadata.Descriptor, metadata.EventHandle, pActivityId, childActivityID, args))
                                ThrowEventSourceException(metadata.Name);
#if FEATURE_PERFTRACING
                            if (!m_eventPipeProvider.WriteEvent(ref metadata.Descriptor, metadata.EventHandle, pActivityId, childActivityID, args))
                                ThrowEventSourceException(metadata.Name);
#endif // FEATURE_PERFTRACING
                        }
                        else
                        {
                            // TODO: activity ID support
                            EventSourceOptions opt = new EventSourceOptions
                            {
                                Keywords = (EventKeywords)metadata.Descriptor.Keywords,
                                Level = (EventLevel)metadata.Descriptor.Level,
                                Opcode = (EventOpcode)metadata.Descriptor.Opcode
                            };

                            WriteMultiMerge(metadata.Name, ref opt, metadata.TraceLoggingEventTypes, pActivityId, childActivityID, args);
                        }
                    }

                    if (m_Dispatchers != null && metadata.EnabledForAnyListener)
                    {
                        // Maintain old behavior - object identity is preserved
                        if (!LocalAppContextSwitches.PreserveEventListenerObjectIdentity)
                        {
                            args = SerializeEventArgs(eventId, args);
                        }

                        var eventCallbackArgs = new EventWrittenEventArgs(this, eventId, pActivityId, childActivityID)
                        {
                            Payload = new ReadOnlyCollection<object?>(args)
                        };

                        DispatchToAllListeners(eventCallbackArgs);
                    }
                }
                catch (Exception ex)
                {
                    if (ex is EventSourceException)
                        throw;
                    else
                    {
                        ref EventMetadata metadata = ref CollectionsMarshal.GetValueRefOrNullRef(m_eventData, eventId);
                        ThrowEventSourceException(metadata.Name, ex);
                    }
                }
            }
        }

        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2112:ReflectionToRequiresUnreferencedCode",
            Justification = "EnsureDescriptorsInitialized's use of GetType preserves this method which " +
                            "requires unreferenced code, but EnsureDescriptorsInitialized does not access this member and is safe to call.")]
        [RequiresUnreferencedCode(EventSourceRequiresUnreferenceMessage)]
        private object?[] SerializeEventArgs(int eventId, object?[] args)
        {
            Debug.Assert(m_eventData != null);
            ref EventMetadata metadata = ref CollectionsMarshal.GetValueRefOrNullRef(m_eventData, eventId);
            TraceLoggingEventTypes eventTypes = metadata.TraceLoggingEventTypes;
            int paramCount = Math.Min(eventTypes.typeInfos.Length, args.Length); // parameter count mismatch get logged in LogEventArgsMismatches
            var eventData = new object?[eventTypes.typeInfos.Length];
            for (int i = 0; i < paramCount; i++)
            {
                eventData[i] = eventTypes.typeInfos[i].GetData(args[i]);
            }
            return eventData;
        }

        /// <summary>
        /// We expect that the arguments to the Event method and the arguments to WriteEvent match. This function
        /// checks that they in fact match and logs a warning to the debugger if they don't.
        /// </summary>
        /// <param name="eventId"></param>
        /// <param name="args"></param>
        private void LogEventArgsMismatches(int eventId, object?[] args)
        {
            Debug.Assert(m_eventData != null);
            ref EventMetadata metadata = ref CollectionsMarshal.GetValueRefOrNullRef(m_eventData, eventId);
            ParameterInfo[] infos = metadata.Parameters;

            if (args.Length != infos.Length)
            {
                ReportOutOfBandMessage(SR.Format(SR.EventSource_EventParametersMismatch, eventId, args.Length, infos.Length));
                return;
            }

            for (int i = 0; i < args.Length; i++)
            {
                Type pType = infos[i].ParameterType;
                object? arg = args[i];

                // Checking to see if the Parameter types (from the Event method) match the supplied argument types.
                // Fail if one of two things hold : either the argument type is not equal or assignable to the parameter type, or the
                // argument is null and the parameter type is a non-Nullable<T> value type.
                if ((arg != null && !pType.IsAssignableFrom(arg.GetType()))
                    || (arg == null && (pType.IsValueType && !(pType.IsGenericType && pType.GetGenericTypeDefinition() == typeof(Nullable<>))))
                    )
                {
                    ReportOutOfBandMessage(SR.Format(SR.EventSource_VarArgsParameterMismatch, eventId, infos[i].Name));
                    return;
                }
            }
        }

        private unsafe void WriteToAllListeners(EventWrittenEventArgs eventCallbackArgs, int eventDataCount, EventData* data)
        {
            Debug.Assert(m_eventData != null);
            ref EventMetadata metadata = ref CollectionsMarshal.GetValueRefOrNullRef(m_eventData, eventCallbackArgs.EventId);

            if (eventDataCount != metadata.EventListenerParameterCount)
            {
                ReportOutOfBandMessage(SR.Format(SR.EventSource_EventParametersMismatch, eventCallbackArgs.EventId, eventDataCount, metadata.Parameters.Length));
            }

            object?[] args;
            if (eventDataCount == 0)
            {
                eventCallbackArgs.Payload = ReadOnlyCollection<object?>.Empty;
            }
            else
            {
                args = new object?[Math.Min(eventDataCount, metadata.Parameters.Length)];

                if (metadata.AllParametersAreString)
                {
                    for (int i = 0; i < args.Length; i++, data++)
                    {
                        AssertValidString(data);
                        IntPtr dataPointer = data->DataPointer;
                        args[i] = dataPointer == IntPtr.Zero ? null : new string((char*)dataPointer, 0, (data->Size >> 1) - 1);
                    }
                }
                else if (metadata.AllParametersAreInt32)
                {
                    for (int i = 0; i < args.Length; i++, data++)
                    {
                        Debug.Assert(data->Size == 4);
                        args[i] = *(int*)data->DataPointer;
                    }
                }
                else
                {
                    DecodeObjects(args, metadata.ParameterTypes, data);
                }

                eventCallbackArgs.Payload = new ReadOnlyCollection<object?>(args);
            }

            DispatchToAllListeners(eventCallbackArgs);
        }

        internal void DispatchToAllListeners(EventWrittenEventArgs eventCallbackArgs)
        {
            int eventId = eventCallbackArgs.EventId;
            Exception? lastThrownException = null;
            for (EventDispatcher? dispatcher = m_Dispatchers; dispatcher != null; dispatcher = dispatcher.m_Next)
            {
                Debug.Assert(dispatcher.m_EventEnabled != null);
                if (eventId == -1 || dispatcher.m_EventEnabled[eventId])
                {
                    {
                        try
                        {
                            dispatcher.m_Listener.OnEventWritten(eventCallbackArgs);
                        }
                        catch (Exception e)
                        {
                            ReportOutOfBandMessage("ERROR: Exception during EventSource.OnEventWritten: "
                                 + e.Message);
                            lastThrownException = e;
                        }
                    }
                }
            }

            if (lastThrownException != null && ThrowOnEventWriteErrors)
            {
                throw new EventSourceException(lastThrownException);
            }
        }

        // WriteEventString is used for logging an error message (or similar) to
        // ETW and EventPipe providers. It is not a general purpose API, it will
        // log the message with Level=LogAlways and Keywords=All to make sure whoever
        // is listening gets the message.
        private unsafe void WriteEventString(string msgString)
        {
            bool allAreNull = m_etwProvider == null;
#if FEATURE_PERFTRACING
            allAreNull &= (m_eventPipeProvider == null);
#endif // FEATURE_PERFTRACING
            if (allAreNull)
            {
                return;
            }

            EventLevel level = EventLevel.LogAlways;
            long keywords = -1;
            const string EventName = "EventSourceMessage";
            if (SelfDescribingEvents)
            {
                EventSourceOptions opt = new EventSourceOptions
                {
                    Keywords = (EventKeywords)unchecked(keywords),
                    Level = level
                };

                [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
                    Justification = "The call to TraceLoggingEventTypes with the below parameter values are trim safe")]
                static TraceLoggingEventTypes GetTrimSafeTraceLoggingEventTypes() =>
                    new TraceLoggingEventTypes(EventName, EventTags.None, new Type[] { typeof(string) });

                var tlet = GetTrimSafeTraceLoggingEventTypes();
                WriteMultiMergeInner(EventName, ref opt, tlet, null, null, msgString);
            }
            else
            {
                // We want the name of the provider to show up so if we don't have a manifest we create
                // on that at least has the provider name (I don't define any events).
                if (m_rawManifest == null && m_outOfBandMessageCount == 1)
                {
                    ManifestBuilder manifestBuilder = new ManifestBuilder(Name, Guid, Name, null, EventManifestOptions.None);
                    manifestBuilder.StartEvent(EventName, new EventAttribute(0) { Level = level, Task = (EventTask)0xFFFE });
                    manifestBuilder.AddEventParameter(typeof(string), "message");
                    manifestBuilder.EndEvent();
                    SendManifest(manifestBuilder.CreateManifest());
                }

                // We use this low level routine to bypass the enabled checking, since the eventSource itself is only partially inited.
                fixed (char* msgStringPtr = msgString)
                {
                    EventDescriptor descr = new EventDescriptor(0, 0, 0, (byte)level, 0, 0, keywords);
                    EventProvider.EventData data = default;
                    data.Ptr = (ulong)msgStringPtr;
                    data.Size = (uint)(2 * (msgString.Length + 1));
                    data.Reserved = 0;
                    m_etwProvider?.WriteEvent(ref descr, IntPtr.Zero, null, null, 1, (IntPtr)((void*)&data));
#if FEATURE_PERFTRACING
                    if (m_eventPipeProvider != null)
                    {
                        if (m_writeEventStringEventHandle == IntPtr.Zero)
                        {
                            if (m_createEventLock is null)
                            {
                                Interlocked.CompareExchange(ref m_createEventLock, new object(), null);
                            }

                            lock (m_createEventLock)
                            {
                                if (m_writeEventStringEventHandle == IntPtr.Zero)
                                {
                                    string eventName = "EventSourceMessage";
                                    EventParameterInfo paramInfo = default(EventParameterInfo);
                                    paramInfo.SetInfo("message", typeof(string));
                                    byte[]? metadata = EventPipeMetadataGenerator.Instance.GenerateMetadata(0, eventName, keywords, (uint)level, 0, EventOpcode.Info, [paramInfo]);
                                    uint metadataLength = (metadata != null) ? (uint)metadata.Length : 0;

                                    fixed (byte* pMetadata = metadata)
                                    {
                                        m_writeEventStringEventHandle = m_eventPipeProvider._eventProvider.DefineEventHandle(0, eventName, keywords, 0, (uint)level,
                                                                            pMetadata, metadataLength);
                                    }
                                }
                            }
                        }

                        m_eventPipeProvider.WriteEvent(ref descr, m_writeEventStringEventHandle, null, null, 1, (IntPtr)((void*)&data));
                    }
#endif // FEATURE_PERFTRACING
                }
            }
        }

        private static ReadOnlyCollection<string>? s_errorPayloadNames;

        /// <summary>
        /// Since this is a means of reporting errors (see ReportoutOfBandMessage) any failure encountered
        /// while writing the message to any one of the listeners will be silently ignored.
        /// </summary>
        private void WriteStringToAllListeners(string eventName, string msg)
        {
#pragma warning disable CA1861
            var eventCallbackArgs = new EventWrittenEventArgs(this, 0)
            {
                EventName = eventName,
                Message = msg,
                Payload = new ReadOnlyCollection<object?>(new object[] { msg }),
                PayloadNames = s_errorPayloadNames ??= new ReadOnlyCollection<string>(new string[] { "message" })
            };
#pragma warning restore CA1861

            for (EventDispatcher? dispatcher = m_Dispatchers; dispatcher != null; dispatcher = dispatcher.m_Next)
            {
                bool dispatcherEnabled = false;
                if (dispatcher.m_EventEnabled == null)
                {
                    // if the listeners that weren't correctly initialized, we will send to it
                    // since this is an error message and we want to see it go out.
                    dispatcherEnabled = true;
                }
                else
                {
                    // if there's *any* enabled event on the dispatcher we'll write out the string
                    // otherwise we'll treat the listener as disabled and skip it
                    foreach (KeyValuePair<int, bool> entry in dispatcher.m_EventEnabled)
                    {
                        if (entry.Value)
                        {
                            dispatcherEnabled = true;
                            break;
                        }
                    }
                }
                try
                {
                    if (dispatcherEnabled)
                        dispatcher.m_Listener.OnEventWritten(eventCallbackArgs);
                }
                catch
                {
                    // ignore any exceptions thrown by listeners' OnEventWritten
                }
            }
        }

        /// <summary>
        /// Returns true if 'eventNum' is enabled if you only consider the level and matchAnyKeyword filters.
        /// It is possible that eventSources turn off the event based on additional filtering criteria.
        /// </summary>
        private bool IsEnabledByDefault(int eventNum, bool enable, EventLevel currentLevel, EventKeywords currentMatchAnyKeyword)
        {
            if (!enable)
                return false;

            Debug.Assert(m_eventData != null);
            ref EventMetadata metadata = ref CollectionsMarshal.GetValueRefOrNullRef(m_eventData, eventNum);
            EventLevel eventLevel = (EventLevel)metadata.Descriptor.Level;
            EventKeywords eventKeywords = unchecked((EventKeywords)((ulong)metadata.Descriptor.Keywords & (~(SessionMask.All.ToEventKeywords()))));
            EventChannel channel = unchecked((EventChannel)metadata.Descriptor.Channel);

            return IsEnabledCommon(enable, currentLevel, currentMatchAnyKeyword, eventLevel, eventKeywords, channel);
        }

        private bool IsEnabledCommon(bool enabled, EventLevel currentLevel, EventKeywords currentMatchAnyKeyword,
                                                          EventLevel eventLevel, EventKeywords eventKeywords, EventChannel eventChannel)
        {
            if (!enabled)
                return false;

            // does is pass the level test?
            if ((currentLevel != 0) && (currentLevel < eventLevel))
                return false;

            // if yes, does it pass the keywords test?
            if (currentMatchAnyKeyword != 0 && eventKeywords != 0)
            {
                // is there a channel with keywords that match currentMatchAnyKeyword?
                if (eventChannel != EventChannel.None && this.m_channelData != null && this.m_channelData.Length > (int)eventChannel)
                {
                    EventKeywords channel_keywords = unchecked((EventKeywords)(m_channelData[(int)eventChannel] | (ulong)eventKeywords));
                    if (channel_keywords != 0 && (channel_keywords & currentMatchAnyKeyword) == 0)
                        return false;
                }
                else
                {
                    if ((unchecked((ulong)eventKeywords & (ulong)currentMatchAnyKeyword)) == 0)
                        return false;
                }
            }
            return true;
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private void ThrowEventSourceException(string? eventName, Exception? innerEx = null)
        {
            // If we fail during out of band logging we may end up trying
            // to throw another EventSourceException, thus hitting a StackOverflowException.
            // Avoid StackOverflow by making sure we do not recursively call this method.
            if (m_EventSourceExceptionRecurenceCount > 0)
                return;
            try
            {
                m_EventSourceExceptionRecurenceCount++;

                string errorPrefix = "EventSourceException";
                if (eventName != null)
                {
                    errorPrefix += " while processing event \"" + eventName + "\"";
                }

                // TODO Create variations of EventSourceException that indicate more information using the error code.
                switch (EventProvider.GetLastWriteEventError())
                {
                    case EventProvider.WriteEventErrorCode.EventTooBig:
                        ReportOutOfBandMessage(errorPrefix + ": " + SR.EventSource_EventTooBig);
                        if (ThrowOnEventWriteErrors) throw new EventSourceException(SR.EventSource_EventTooBig, innerEx);
                        break;
                    case EventProvider.WriteEventErrorCode.NoFreeBuffers:
                        ReportOutOfBandMessage(errorPrefix + ": " + SR.EventSource_NoFreeBuffers);
                        if (ThrowOnEventWriteErrors) throw new EventSourceException(SR.EventSource_NoFreeBuffers, innerEx);
                        break;
                    case EventProvider.WriteEventErrorCode.NullInput:
                        ReportOutOfBandMessage(errorPrefix + ": " + SR.EventSource_NullInput);
                        if (ThrowOnEventWriteErrors) throw new EventSourceException(SR.EventSource_NullInput, innerEx);
                        break;
                    case EventProvider.WriteEventErrorCode.TooManyArgs:
                        ReportOutOfBandMessage(errorPrefix + ": " + SR.EventSource_TooManyArgs);
                        if (ThrowOnEventWriteErrors) throw new EventSourceException(SR.EventSource_TooManyArgs, innerEx);
                        break;
                    default:
                        if (innerEx != null)
                        {
                            innerEx = innerEx.GetBaseException();
                            ReportOutOfBandMessage(errorPrefix + ": " + innerEx.GetType() + ":" + innerEx.Message);
                        }
                        else
                            ReportOutOfBandMessage(errorPrefix);
                        if (ThrowOnEventWriteErrors) throw new EventSourceException(innerEx);
                        break;
                }
            }
            finally
            {
                m_EventSourceExceptionRecurenceCount--;
            }
        }

        internal static EventOpcode GetOpcodeWithDefault(EventOpcode opcode, string? eventName)
        {
            if (opcode == EventOpcode.Info && eventName != null)
            {
                if (eventName.EndsWith(ActivityStartSuffix, StringComparison.Ordinal))
                {
                    return EventOpcode.Start;
                }
                else if (eventName.EndsWith(ActivityStopSuffix, StringComparison.Ordinal))
                {
                    return EventOpcode.Stop;
                }
            }

            return opcode;
        }

        /// <summary>
        /// This class lets us hook the 'OnEventCommand' from the eventSource.
        /// </summary>
        internal sealed class OverrideEventProvider : EventProvider
        {
            public OverrideEventProvider(Func<EventSource?> eventSourceFactory, EventProviderType providerType)
                : base(providerType)
            {
                _eventSourceFactory = eventSourceFactory;
                _eventProviderType = providerType;
            }
            internal override void OnControllerCommand(ControllerCommand command, IDictionary<string, string?>? arguments,
                                                              int perEventSourceSessionId)
            {
                // We use null to represent the ETW EventListener.
                EventListener? listener = null;
                _eventSourceFactory()?.SendCommand(listener, _eventProviderType, perEventSourceSessionId,
                                          (EventCommand)command, IsEnabled(), Level, MatchAnyKeyword, arguments);
            }
            private readonly Func<EventSource?> _eventSourceFactory;
            private readonly EventProviderType _eventProviderType;
        }

        /// <summary>
        /// Used to hold all the static information about an event.  This includes everything in the event
        /// descriptor as well as some stuff we added specifically for EventSource. see the
        /// code:m_eventData for where we use this.
        /// </summary>

        internal partial struct EventMetadata
        {
            public EventDescriptor Descriptor;
            public IntPtr EventHandle;              // EventPipeEvent handle.
            public EventTags Tags;
            public bool EnabledForAnyListener;      // true if any dispatcher has this event turned on
            public bool EnabledForETW;              // is this event on for ETW?
#if FEATURE_PERFTRACING
            public bool EnabledForEventPipe;        // is this event on for EventPipe?
#endif

            public bool HasRelatedActivityID;       // Set if the event method's first parameter is a Guid named 'relatedActivityId'
            public string Name;                     // the name of the event
            public string? Message;                  // If the event has a message associated with it, this is it.
            public ParameterInfo[] Parameters;      // TODO can we remove?
            public int EventListenerParameterCount;
            public bool AllParametersAreString;
            public bool AllParametersAreInt32;

            public EventActivityOptions ActivityOptions;

            private TraceLoggingEventTypes _traceLoggingEventTypes;
            public TraceLoggingEventTypes TraceLoggingEventTypes
            {
                [RequiresUnreferencedCode(EventSourceRequiresUnreferenceMessage)]
                get
                {
                    if (_traceLoggingEventTypes is null)
                    {
                        var tlet = new TraceLoggingEventTypes(Name, Tags, Parameters);
                        Interlocked.CompareExchange(ref _traceLoggingEventTypes, tlet, null);
                    }
                    return _traceLoggingEventTypes;
                }
            }

            private ReadOnlyCollection<string>? _parameterNames;
            public ReadOnlyCollection<string> ParameterNames
            {
                get
                {
                    if (_parameterNames is null)
                    {
                        ParameterInfo[] parameters = Parameters;
                        var names = new string[parameters.Length];
                        for (int i = 0; i < names.Length; i++)
                        {
                            names[i] = parameters[i].Name!;
                        }
                        _parameterNames = new ReadOnlyCollection<string>(names);
                    }
                    return _parameterNames;
                }
            }

            private Type[]? _parameterTypes;
            public Type[] ParameterTypes
            {
                get
                {
                    return _parameterTypes ??= GetParameterTypes(Parameters);

                    static Type[] GetParameterTypes(ParameterInfo[] parameters)
                    {
                        var types = new Type[parameters.Length];
                        for (int i = 0; i < types.Length; i++)
                        {
                            types[i] = parameters[i].ParameterType;
                        }
                        return types;
                    }
                }
            }
        }

        // This is the internal entry point that code:EventListeners call when wanting to send a command to a
        // eventSource. The logic is as follows
        //
        // * if Command == Update
        //     * perEventSourceSessionId specifies the per-provider ETW session ID that the command applies
        //         to (if listener != null)
        //         perEventSourceSessionId = 0 - reserved for EventListeners
        //         perEventSourceSessionId = 1..SessionMask.MAX - reserved for activity tracing aware ETW sessions
        //                  perEventSourceSessionId-1 represents the bit in the reserved field (bits 44..47) in
        //                  Keywords that identifies the session
        //         perEventSourceSessionId = SessionMask.MAX+1 - reserved for legacy ETW sessions; these are
        //                  discriminated by etwSessionId
        //     * etwSessionId specifies a machine-wide ETW session ID; this allows correlation of
        //         activity tracing across different providers (which might have different sessionIds
        //         for the same ETW session)
        //     * enable, level, matchAnyKeywords are used to set a default for all events for the
        //         eventSource.  In particular, if 'enabled' is false, 'level' and
        //         'matchAnyKeywords' are not used.
        //     * OnEventCommand is invoked, which may cause calls to
        //         code:EventSource.EnableEventForDispatcher which may cause changes in the filtering
        //         depending on the logic in that routine.
        // * else (command != Update)
        //     * Simply call OnEventCommand. The expectation is that filtering is NOT changed.
        //     * The 'enabled' 'level', matchAnyKeyword' arguments are ignored (must be true, 0, 0).
        //
        // dispatcher == null has special meaning. It is the 'ETW' dispatcher.
        internal void SendCommand(EventListener? listener, EventProviderType eventProviderType, int perEventSourceSessionId,
                                  EventCommand command, bool enable,
                                  EventLevel level, EventKeywords matchAnyKeyword,
                                  IDictionary<string, string?>? commandArguments)
        {
            if (!IsSupported)
            {
                return;
            }

            var commandArgs = new EventCommandEventArgs(command, commandArguments, this, listener, eventProviderType, perEventSourceSessionId, enable, level, matchAnyKeyword);
            lock (EventListener.EventListenersLock)
            {
                if (m_completelyInited)
                {
                    // After the first command arrive after construction, we are ready to get rid of the deferred commands
                    this.m_deferredCommands = null;
                    // We are fully initialized, do the command
                    DoCommand(commandArgs);
                }
                else
                {
                    // We can't do the command, simply remember it and we do it when we are fully constructed.
                    if (m_deferredCommands == null)
                    {
                        m_deferredCommands = commandArgs;       // create the first entry
                    }
                    else
                    {
                        // We have one or more entries, find the last one and add it to that.
                        EventCommandEventArgs lastCommand = m_deferredCommands;
                        while (lastCommand.nextCommand != null)
                            lastCommand = lastCommand.nextCommand;
                        lastCommand.nextCommand = commandArgs;
                    }
                }
            }
        }

        /// <summary>
        /// We want the eventSource to be fully initialized when we do commands because that way we can send
        /// error messages and other logging directly to the event stream.   Unfortunately we can get callbacks
        /// when we are not fully initialized.  In that case we store them in 'commandArgs' and do them later.
        /// This helper actually does all actual command logic.
        /// </summary>
        internal void DoCommand(EventCommandEventArgs commandArgs)
        {
            if (!IsSupported)
            {
                return;
            }

            // PRECONDITION: We should be holding the EventListener.EventListenersLock
            Debug.Assert(Monitor.IsEntered(EventListener.EventListenersLock));

            // We defer commands until we can send error messages.
            if (m_etwProvider == null)     // If we failed to construct
                return;

#if FEATURE_PERFTRACING
            if (m_eventPipeProvider == null)
                return;
#endif

            m_outOfBandMessageCount = 0;
            try
            {
                EnsureDescriptorsInitialized();
                Debug.Assert(m_eventData != null);

                // Find the per-EventSource dispatcher corresponding to registered dispatcher
                commandArgs.dispatcher = GetDispatcher(commandArgs.listener);
                if (commandArgs.dispatcher == null && commandArgs.listener != null)     // dispatcher == null means ETW dispatcher
                {
                    throw new ArgumentException(SR.EventSource_ListenerNotFound);
                }

                commandArgs.Arguments ??= new Dictionary<string, string?>();

                if (commandArgs.Command == EventCommand.Update)
                {
                    // Set it up using the 'standard' filtering bitfields (use the "global" enable, not session specific one)
                    foreach (int eventID in m_eventData.Keys)
                        EnableEventForDispatcher(commandArgs.dispatcher, commandArgs.eventProviderType, eventID, IsEnabledByDefault(eventID, commandArgs.enable, commandArgs.level, commandArgs.matchAnyKeyword));

                    if (commandArgs.enable)
                    {
                        if (!m_eventSourceEnabled)
                        {
                            // EventSource turned on for the first time, simply copy the bits.
                            m_level = commandArgs.level;
                            m_matchAnyKeyword = commandArgs.matchAnyKeyword;
                        }
                        else
                        {
                            // Already enabled, make it the most verbose of the existing and new filter
                            if (commandArgs.level > m_level)
                                m_level = commandArgs.level;
                            if (commandArgs.matchAnyKeyword == 0)
                                m_matchAnyKeyword = 0;
                            else if (m_matchAnyKeyword != 0)
                                m_matchAnyKeyword = unchecked(m_matchAnyKeyword | commandArgs.matchAnyKeyword);
                        }
                    }

                    // interpret perEventSourceSessionId's sign, and adjust perEventSourceSessionId to
                    // represent 0-based positive values
                    bool bSessionEnable = (commandArgs.perEventSourceSessionId >= 0);
                    if (commandArgs.perEventSourceSessionId == 0 && !commandArgs.enable)
                        bSessionEnable = false;

                    if (commandArgs.listener == null)
                    {
                        if (!bSessionEnable)
                            commandArgs.perEventSourceSessionId = -commandArgs.perEventSourceSessionId;
                        // for "global" enable/disable (passed in with listener == null and
                        //  perEventSourceSessionId == 0) perEventSourceSessionId becomes -1
                        --commandArgs.perEventSourceSessionId;
                    }

                    commandArgs.Command = bSessionEnable ? EventCommand.Enable : EventCommand.Disable;

                    // perEventSourceSessionId = -1 when ETW sent a notification, but the set of active sessions
                    // hasn't changed.
                    // sesisonId = SessionMask.MAX when one of the legacy ETW sessions changed
                    // 0 <= perEventSourceSessionId < SessionMask.MAX for activity-tracing aware sessions
                    Debug.Assert(commandArgs.perEventSourceSessionId >= -1 && commandArgs.perEventSourceSessionId <= SessionMask.MAX);

                    // Send the manifest if we are enabling an ETW session
                    if (bSessionEnable && commandArgs.dispatcher == null)
                    {
                        // eventSourceDispatcher == null means this is the ETW manifest

                        // Note that we unconditionally send the manifest whenever we are enabled, even if
                        // we were already enabled.   This is because there may be multiple sessions active
                        // and we can't know that all the sessions have seen the manifest.
                        if (!SelfDescribingEvents)
                            SendManifest(m_rawManifest);
                    }

                    // Turn on the enable bit before making the OnEventCommand callback  This allows you to do useful
                    // things like log messages, or test if keywords are enabled in the callback.
                    if (commandArgs.enable)
                    {
                        Debug.Assert(m_eventData != null);
                        m_eventSourceEnabled = true;
                    }

                    if (!commandArgs.enable)
                    {
                        // If we are disabling, maybe we can turn on 'quick checks' to filter
                        // quickly.  These are all just optimizations (since later checks will still filter)

                        // There is a good chance EnabledForAnyListener are not as accurate as
                        // they could be, go ahead and get a better estimate.
                        foreach (int eventID in m_eventData.Keys)
                        {
                            bool isEnabledForAnyListener = false;
                            for (EventDispatcher? dispatcher = m_Dispatchers; dispatcher != null; dispatcher = dispatcher.m_Next)
                            {
                                Debug.Assert(dispatcher.m_EventEnabled != null);

                                if (dispatcher.m_EventEnabled[eventID])
                                {
                                    isEnabledForAnyListener = true;
                                    break;
                                }
                            }
                            ref  EventMetadata eventMeta = ref CollectionsMarshal.GetValueRefOrNullRef(m_eventData, eventID);
                            eventMeta.EnabledForAnyListener = isEnabledForAnyListener;
                        }

                        // If no events are enabled, disable the global enabled bit.
                        if (!AnyEventEnabled())
                        {
                            m_level = 0;
                            m_matchAnyKeyword = 0;
                            m_eventSourceEnabled = false;
                        }
                    }

                    this.OnEventCommand(commandArgs);
                    this.m_eventCommandExecuted?.Invoke(this, commandArgs);
                }
                else
                {
                    if (commandArgs.Command == EventCommand.SendManifest)
                    {
                        // TODO: should we generate the manifest here if we hadn't already?
                        if (m_rawManifest != null)
                            SendManifest(m_rawManifest);
                    }

                    // These are not used for non-update commands and thus should always be 'default' values
                    // Debug.Assert(enable == true);
                    // Debug.Assert(level == EventLevel.LogAlways);
                    // Debug.Assert(matchAnyKeyword == EventKeywords.None);

                    this.OnEventCommand(commandArgs);
                    m_eventCommandExecuted?.Invoke(this, commandArgs);
                }
            }
            catch (Exception e)
            {
                // When the ETW session is created after the EventSource has registered with the ETW system
                // we can send any error messages here.
                ReportOutOfBandMessage("ERROR: Exception in Command Processing for EventSource " + Name + ": " + e.Message);
                // We never throw when doing a command.
            }
        }

        /// <summary>
        /// If 'value is 'true' then set the eventSource so that 'dispatcher' will receive event with the eventId
        /// of 'eventId.  If value is 'false' disable the event for that dispatcher.   If 'eventId' is out of
        /// range return false, otherwise true.
        /// </summary>
        internal bool EnableEventForDispatcher(EventDispatcher? dispatcher, EventProviderType eventProviderType, int eventId, bool value)
        {
            if (!IsSupported)
                return false;

            Debug.Assert(m_eventData != null);
            ref EventMetadata eventMeta = ref CollectionsMarshal.GetValueRefOrNullRef(m_eventData, eventId);
            if (Unsafe.IsNullRef(ref eventMeta)) return false;

            if (dispatcher == null)
            {
                if (m_etwProvider != null && eventProviderType == EventProviderType.ETW)
                    eventMeta.EnabledForETW = value;

#if FEATURE_PERFTRACING
                if (m_eventPipeProvider != null && eventProviderType == EventProviderType.EventPipe)
                    eventMeta.EnabledForEventPipe = value;
#endif
            }
            else
            {
                Debug.Assert(dispatcher.m_EventEnabled != null);

                if (!dispatcher.m_EventEnabled.ContainsKey(eventId))
                    return false;

                dispatcher.m_EventEnabled[eventId] = value;
                if (value)
                    eventMeta.EnabledForAnyListener = true;
            }
            return true;
        }

        /// <summary>
        /// Returns true if any event at all is on.
        /// </summary>
        private bool AnyEventEnabled()
        {
            Debug.Assert(m_eventData != null);
            foreach (EventMetadata eventMeta in m_eventData.Values)
                if (eventMeta.EnabledForETW || eventMeta.EnabledForAnyListener
#if FEATURE_PERFTRACING
                        || eventMeta.EnabledForEventPipe
#endif // FEATURE_PERFTRACING
                    )
                    return true;
            return false;
        }

        private bool IsDisposed => m_eventSourceDisposed;

        private void EnsureDescriptorsInitialized()
        {
            Debug.Assert(Monitor.IsEntered(EventListener.EventListenersLock));
            if (m_eventData == null)
            {
                // get the metadata via reflection.
                Debug.Assert(m_rawManifest == null);
                m_rawManifest = EventSourceHelpers.CreateManifestAndDescriptors(this.GetType(), Name, this);
                Debug.Assert(m_eventData != null);

                // TODO Enforce singleton pattern
                if (!AllowDuplicateSourceNames)
                {
                    Debug.Assert(EventListener.s_EventSources != null, "should be called within lock on EventListener.EventListenersLock which ensures s_EventSources to be initialized");
                    foreach (WeakReference<EventSource> eventSourceRef in EventListener.s_EventSources)
                    {
                        if (eventSourceRef.TryGetTarget(out EventSource? eventSource) && eventSource.Guid == m_guid && !eventSource.IsDisposed)
                        {
                            if (eventSource != this)
                            {
                                throw new ArgumentException(SR.Format(SR.EventSource_EventSourceGuidInUse, m_guid));
                            }
                        }
                    }
                }

                // Make certain all dispatchers also have their arrays initialized
                EventDispatcher? dispatcher = m_Dispatchers;
                while (dispatcher != null)
                {
                    Dictionary<int, bool> eventEnabled = new Dictionary<int, bool>(m_eventData.Count);
                    foreach (int eventId in m_eventData.Keys)
                    {
                        eventEnabled[eventId] = false;
                    }
                    dispatcher.m_EventEnabled ??= eventEnabled;
                    dispatcher = dispatcher.m_Next;
                }
#if FEATURE_PERFTRACING
                // Initialize the EventPipe event handles.
                DefineEventPipeEvents();
#endif
            }
        }

        // Send out the ETW manifest XML out to ETW
        // Today, we only send the manifest to ETW, custom listeners don't get it.
        private unsafe void SendManifest(byte[]? rawManifest)
        {
            if (rawManifest == null)
            {
                return;
            }

            Debug.Assert(!SelfDescribingEvents);

            fixed (byte* dataPtr = rawManifest)
            {
                // we don't want the manifest to show up in the event log channels so we specify as keywords
                // everything but the first 8 bits (reserved for the 8 channels)
                var manifestDescr = new EventDescriptor(0xFFFE, 1, 0, 0, 0xFE, 0xFFFE, 0x00ffFFFFffffFFFF);
                ManifestEnvelope envelope = default;

                envelope.Format = ManifestEnvelope.ManifestFormats.SimpleXmlFormat;
                envelope.MajorVersion = 1;
                envelope.MinorVersion = 0;
                envelope.Magic = 0x5B;              // An unusual number that can be checked for consistency.
                int dataLeft = rawManifest.Length;
                envelope.ChunkNumber = 0;

                EventProvider.EventData* dataDescrs = stackalloc EventProvider.EventData[2];

                dataDescrs[0].Ptr = (ulong)&envelope;
                dataDescrs[0].Size = (uint)sizeof(ManifestEnvelope);
                dataDescrs[0].Reserved = 0;

                dataDescrs[1].Ptr = (ulong)dataPtr;
                dataDescrs[1].Reserved = 0;

                int chunkSize = ManifestEnvelope.MaxChunkSize;
                TRY_AGAIN_WITH_SMALLER_CHUNK_SIZE:
                envelope.TotalChunks = (ushort)((dataLeft + (chunkSize - 1)) / chunkSize);
                while (dataLeft > 0)
                {
                    dataDescrs[1].Size = (uint)Math.Min(dataLeft, chunkSize);
                    if (m_etwProvider != null)
                    {
                        if (!m_etwProvider.WriteEvent(ref manifestDescr, IntPtr.Zero, null, null, 2, (IntPtr)dataDescrs))
                        {
                            // Turns out that if users set the BufferSize to something less than 64K then WriteEvent
                            // can fail.   If we get this failure on the first chunk try again with something smaller
                            // The smallest BufferSize is 1K so if we get to 256 (to account for envelope overhead), we can give up making it smaller.
                            if (EventProvider.GetLastWriteEventError() == EventProvider.WriteEventErrorCode.EventTooBig)
                            {
                                if (envelope.ChunkNumber == 0 && chunkSize > 256)
                                {
                                    chunkSize /= 2;
                                    goto TRY_AGAIN_WITH_SMALLER_CHUNK_SIZE;
                                }
                            }

                            if (ThrowOnEventWriteErrors)
                                ThrowEventSourceException("SendManifest");
                            break;
                        }
                    }
                    dataLeft -= chunkSize;
                    dataDescrs[1].Ptr += (uint)chunkSize;
                    envelope.ChunkNumber++;

                    // For large manifests we want to not overflow any receiver's buffer. Most manifests will fit within
                    // 5 chunks, so only the largest manifests will hit the pause.
                    if ((envelope.ChunkNumber % 5) == 0)
                    {
                        Thread.Sleep(15);
                    }
                }
            }
        }

        // Helper to deal with the fact that the type we are reflecting over might be loaded in the ReflectionOnly context.
        // When that is the case, we have to build the custom assemblies on a member by hand.
        internal static bool IsCustomAttributeDefinedHelper(
            MemberInfo member,
            Type attributeType,
            EventManifestOptions flags = EventManifestOptions.None)
        {
            // AllowEventSourceOverride is an option that allows either Microsoft.Diagnostics.Tracing or
            // System.Diagnostics.Tracing EventSource to be considered valid.  This should not mattter anywhere but in Microsoft.Diagnostics.Tracing (nuget package).
            if (!member.Module.Assembly.ReflectionOnly && (flags & EventManifestOptions.AllowEventSourceOverride) == 0)
            {
                // Let the runtime do the work for us, since we can execute code in this context.
                return member.IsDefined(attributeType, inherit: false);
            }

            foreach (CustomAttributeData data in CustomAttributeData.GetCustomAttributes(member))
            {
                if (AttributeTypeNamesMatch(attributeType, data.Constructor.ReflectedType!))
                {
                    return true;
                }
            }

            return false;
        }

        private static class EventSourceHelpers
        {
            // Use reflection to look at the attributes of a class, and generate a manifest for it (as UTF8) and
            // return the UTF8 bytes.  It also sets up the code:EventData structures needed to dispatch events
            // at run time.  'source' is the event source to place the descriptors.  If it is null,
            // then the descriptors are not created, and just the manifest is generated.
            public static byte[]? CreateManifestAndDescriptors(
                [DynamicallyAccessedMembers(ManifestMemberTypes)]
                Type eventSourceType,
                string? eventSourceDllName,
                EventSource? source,
                EventManifestOptions flags = EventManifestOptions.None)
            {
                ManifestBuilder? manifest = null;
                bool bNeedsManifest = source != null ? !source.SelfDescribingEvents : true;
                Exception? exception = null; // exception that might get raised during validation b/c we couldn't/didn't recover from a previous error
                byte[]? res = null;

                if (eventSourceType.IsAbstract && (flags & EventManifestOptions.Strict) == 0)
                    return null;

                try
                {
                    MethodInfo[] methods = eventSourceType.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
                    EventAttribute defaultEventAttribute;
                    int eventId = 1;        // The number given to an event that does not have a explicitly given ID.
                    Dictionary<int, EventMetadata>? eventData = null;
                    Dictionary<string, string>? eventsByName = null;
                    if (source != null || (flags & EventManifestOptions.Strict) != 0)
                    {
                        eventData = new Dictionary<int, EventMetadata>();
                        ref EventMetadata newEventMetadata = ref CollectionsMarshal.GetValueRefOrAddDefault(eventData, 0, out _);
                        newEventMetadata.Name = ""; // Event 0 is the 'write messages string' event, and has an empty name.
                    }

                    // See if we have localization information.
                    ResourceManager? resources = null;
                    EventSourceAttribute? eventSourceAttrib = (EventSourceAttribute?)GetCustomAttributeHelper(eventSourceType, typeof(EventSourceAttribute), flags);
                    if (eventSourceAttrib != null && eventSourceAttrib.LocalizationResources != null)
                        resources = new ResourceManager(eventSourceAttrib.LocalizationResources, eventSourceType.Assembly);

                    if (source?.GetType() == typeof(NativeRuntimeEventSource))
                    {
                        // Don't emit nor generate the manifest for NativeRuntimeEventSource i.e., Microsoft-Windows-DotNETRuntime.
                        manifest = new ManifestBuilder(resources, flags);
                        bNeedsManifest = false;
                    }
                    else
                    {
                        // Try to get name and GUID directly from the source. Otherwise get it from the Type's attribute.
                        string providerName = source?.Name ?? GetName(eventSourceType, flags);
                        Guid providerGuid = source?.Guid ?? GetGuid(eventSourceType);

                        manifest = new ManifestBuilder(providerName, providerGuid, eventSourceDllName, resources, flags);
                    }

                    // Add an entry unconditionally for event ID 0 which will be for a string message.
                    manifest.StartEvent("EventSourceMessage", new EventAttribute(0) { Level = EventLevel.LogAlways, Task = (EventTask)0xFFFE });
                    manifest.AddEventParameter(typeof(string), "message");
                    manifest.EndEvent();

                    // eventSourceType must be sealed and must derive from this EventSource
                    if ((flags & EventManifestOptions.Strict) != 0)
                    {
                        bool typeMatch = GetEventSourceBaseType(eventSourceType, (flags & EventManifestOptions.AllowEventSourceOverride) != 0, eventSourceType.Assembly.ReflectionOnly) != null;

                        if (!typeMatch)
                        {
                            manifest.ManifestError(SR.EventSource_TypeMustDeriveFromEventSource);
                        }
                        if (!eventSourceType.IsAbstract && !eventSourceType.IsSealed)
                        {
                            manifest.ManifestError(SR.EventSource_TypeMustBeSealedOrAbstract);
                        }
                    }

                    // Collect task, opcode, keyword and channel information
                    foreach (string providerEnumKind in (ReadOnlySpan<string>)["Keywords", "Tasks", "Opcodes"])
                    {
                        Type? nestedType = eventSourceType.GetNestedType(providerEnumKind);
                        if (nestedType != null)
                        {
                            if (eventSourceType.IsAbstract)
                            {
                                manifest.ManifestError(SR.Format(SR.EventSource_AbstractMustNotDeclareKTOC, nestedType.Name));
                            }
                            else
                            {
                                foreach (FieldInfo staticField in nestedType.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static))
                                {
                                    AddProviderEnumKind(manifest, staticField, providerEnumKind);
                                }
                            }
                        }
                    }
                    // ensure we have keywords for the session-filtering reserved bits
                    {
                        manifest.AddKeyword("Session3", (long)0x1000 << 32);
                        manifest.AddKeyword("Session2", (long)0x2000 << 32);
                        manifest.AddKeyword("Session1", (long)0x4000 << 32);
                        manifest.AddKeyword("Session0", (long)0x8000 << 32);
                    }

                    if (eventSourceType != typeof(EventSource))
                    {
                        for (int i = 0; i < methods.Length; i++)
                        {
                            MethodInfo method = methods[i];

                            // Compat: until v4.5.1 we ignored any non-void returning methods as well as virtual methods for
                            // the only reason of limiting the number of methods considered to be events. This broke a common
                            // design of having event sources implement specific interfaces. To fix this in a compatible way
                            // we will now allow both non-void returning and virtual methods to be Event methods, as long
                            // as they are marked with the [Event] attribute
                            if (/* method.IsVirtual || */ method.IsStatic)
                            {
                                continue;
                            }

                            // Get the EventDescriptor (from the Custom attributes)
                            EventAttribute? eventAttribute = (EventAttribute?)GetCustomAttributeHelper(method, typeof(EventAttribute), flags);

                            if (eventSourceType.IsAbstract)
                            {
                                if (eventAttribute != null)
                                {
                                    manifest.ManifestError(SR.Format(SR.EventSource_AbstractMustNotDeclareEventMethods, method.Name, eventAttribute.EventId));
                                }
                                continue;
                            }
                            else if (eventAttribute == null)
                            {
                                // Methods that don't return void can't be events, if they're NOT marked with [Event].
                                // (see Compat comment above)
                                if (method.ReturnType != typeof(void))
                                {
                                    continue;
                                }

                                // Continue to ignore virtual methods if they do NOT have the [Event] attribute
                                // (see Compat comment above)
                                if (method.IsVirtual)
                                {
                                    continue;
                                }

                                // If we explicitly mark the method as not being an event, then honor that.
                                if (IsCustomAttributeDefinedHelper(method, typeof(NonEventAttribute), flags))
                                    continue;

                                defaultEventAttribute = new EventAttribute(eventId);
                                eventAttribute = defaultEventAttribute;
                            }
                            else if (eventAttribute.EventId <= 0)
                            {
                                manifest.ManifestError(SR.EventSource_NeedPositiveId, true);
                                continue;   // don't validate anything else for this event
                            }
                            if (method.Name.LastIndexOf('.') >= 0)
                            {
                                manifest.ManifestError(SR.Format(SR.EventSource_EventMustNotBeExplicitImplementation, method.Name, eventAttribute.EventId));
                            }

                            eventId++;
                            string eventName = method.Name;

                            if (eventAttribute.Opcode == EventOpcode.Info)      // We are still using the default opcode.
                            {
                                // By default pick a task ID derived from the EventID, starting with the highest task number and working back
                                bool noTask = (eventAttribute.Task == EventTask.None);
                                if (noTask)
                                    eventAttribute.Task = (EventTask)(0xFFFE - eventAttribute.EventId);

                                // Unless we explicitly set the opcode to Info (to override the auto-generate of Start or Stop opcodes,
                                // pick a default opcode based on the event name (either Info or start or stop if the name ends with that suffix).
                                if (!eventAttribute.IsOpcodeSet)
                                    eventAttribute.Opcode = GetOpcodeWithDefault(EventOpcode.Info, eventName);

                                // Make the stop opcode have the same task as the start opcode.
                                if (noTask)
                                {
                                    if (eventAttribute.Opcode == EventOpcode.Start)
                                    {
                                        if (eventName.EndsWith(ActivityStartSuffix, StringComparison.Ordinal))
                                        {
                                            string taskName = eventName[..^ActivityStartSuffix.Length]; // Remove the Start suffix to get the task name

                                            // Add a task that is just the task name for the start event.   This suppress the auto-task generation
                                            // That would otherwise happen (and create 'TaskName'Start as task name rather than just 'TaskName'
                                            manifest.AddTask(taskName, (int)eventAttribute.Task);
                                        }
                                    }
                                    else if (eventAttribute.Opcode == EventOpcode.Stop)
                                    {
                                        // Find the start associated with this stop event.  We require start to be immediately before the stop
                                        int startEventId = eventAttribute.EventId - 1;
                                        Debug.Assert(0 <= startEventId);
                                        if (eventData != null)
                                        {
                                            ref EventMetadata startEventMetadata = ref CollectionsMarshal.GetValueRefOrNullRef(eventData, startEventId);
                                            if (!Unsafe.IsNullRef(ref startEventMetadata))
                                            {
                                                // If you remove the Stop and add a Start does that name match the Start Event's Name?
                                                // Ideally we would throw an error
                                                if (startEventMetadata.Descriptor.Opcode == (byte)EventOpcode.Start &&
                                                    startEventMetadata.Name.EndsWith(ActivityStartSuffix, StringComparison.Ordinal) &&
                                                    eventName.EndsWith(ActivityStopSuffix, StringComparison.Ordinal) &&
                                                    startEventMetadata.Name.AsSpan()[..^ActivityStartSuffix.Length].SequenceEqual(
                                                        eventName.AsSpan()[..^ActivityStopSuffix.Length]))
                                                {
                                                    // Make the stop event match the start event
                                                    eventAttribute.Task = (EventTask)startEventMetadata.Descriptor.Task;
                                                    noTask = false;
                                                }
                                            }
                                        }
                                        if (noTask && (flags & EventManifestOptions.Strict) != 0)        // Throw an error if we can compatibly.
                                        {
                                            throw new ArgumentException(SR.EventSource_StopsFollowStarts);
                                        }
                                    }
                                }
                            }

                            ParameterInfo[] args = method.GetParameters();

                            bool hasRelatedActivityID = RemoveFirstArgIfRelatedActivityId(ref args);
                            if (!(source != null && source.SelfDescribingEvents))
                            {
                                manifest.StartEvent(eventName, eventAttribute);
                                for (int fieldIdx = 0; fieldIdx < args.Length; fieldIdx++)
                                {
                                    manifest.AddEventParameter(args[fieldIdx].ParameterType, args[fieldIdx].Name!);
                                }
                                manifest.EndEvent();
                            }

                            if (source != null || (flags & EventManifestOptions.Strict) != 0)
                            {
                                Debug.Assert(eventData != null);
                                // Do checking for user errors (optional, but not a big deal so we do it).
                                DebugCheckEvent(ref eventsByName, eventData, method, eventAttribute, manifest, flags);

                                // add the channel keyword for Event Viewer channel based filters. This is added for creating the EventDescriptors only
                                // and is not required for the manifest
                                if (eventAttribute.Channel != EventChannel.None)
                                {
                                    unchecked
                                    {
                                        eventAttribute.Keywords |= (EventKeywords)manifest.GetChannelKeyword(eventAttribute.Channel, (ulong)eventAttribute.Keywords);
                                    }
                                }

                                if (manifest.HasResources)
                                {
                                    string eventKey = "event_" + eventName;
                                    if (manifest.GetLocalizedMessage(eventKey, CultureInfo.CurrentUICulture, etwFormat: false) is string msg)
                                    {
                                        // overwrite inline message with the localized message
                                        eventAttribute.Message = msg;
                                    }
                                }

                                AddEventDescriptor(ref eventData, eventName, eventAttribute, args, hasRelatedActivityID);
                            }
                        }
                    }

                    // Tell the TraceLogging stuff where to start allocating its own IDs.
                    NameInfo.ReserveEventIDsBelow(eventId);

                    if (source != null)
                    {
                        Debug.Assert(eventData != null);
                        source.m_eventData = eventData;     // officially initialize it. We do this at most once (it is racy otherwise).
                        source.m_channelData = manifest.GetChannelData();
                    }

                    // if this is an abstract event source we've already performed all the validation we can
                    if (!eventSourceType.IsAbstract && (source == null || !source.SelfDescribingEvents))
                    {
                        bNeedsManifest = (flags & EventManifestOptions.OnlyIfNeededForRegistration) == 0 || manifest.GetChannelData().Length > 0;

                        // if the manifest is not needed and we're not requested to validate the event source return early
                        if (!bNeedsManifest && (flags & EventManifestOptions.Strict) == 0)
                            return null;

                        res = manifest.CreateManifest();
                        res = (res.Length > 0) ? res : null;
                    }
                }
                catch (Exception e)
                {
                    // if this is a runtime manifest generation let the exception propagate
                    if ((flags & EventManifestOptions.Strict) == 0)
                        throw;
                    // else store it to include it in the Argument exception we raise below
                    exception = e;
                }

                if ((flags & EventManifestOptions.Strict) != 0 && (manifest?.Errors.Count > 0 || exception != null))
                {
                    string msg = string.Empty;

                    if (manifest?.Errors.Count > 0)
                    {
                        bool firstError = true;
                        foreach (string error in manifest.Errors)
                        {
                            if (!firstError)
                                msg += Environment.NewLine;
                            firstError = false;
                            msg += error;
                        }
                    }
                    else
                        msg = "Unexpected error: " + exception!.Message;

                    throw new ArgumentException(msg, exception);
                }

                return bNeedsManifest ? res : null;
            }


            // Helper to deal with the fact that the type we are reflecting over might be loaded in the ReflectionOnly context.
            // When that is the case, we have the build the custom assemblies on a member by hand.
            internal static Attribute? GetCustomAttributeHelper(
                MemberInfo member,
                [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties)]
                Type attributeType,
                EventManifestOptions flags = EventManifestOptions.None)
            {
                Debug.Assert(attributeType == typeof(EventAttribute) || attributeType == typeof(EventSourceAttribute));
                // AllowEventSourceOverride is an option that allows either Microsoft.Diagnostics.Tracing or
                // System.Diagnostics.Tracing EventSource to be considered valid.  This should not mattter anywhere but in Microsoft.Diagnostics.Tracing (nuget package).
                if (!member.Module.Assembly.ReflectionOnly && (flags & EventManifestOptions.AllowEventSourceOverride) == 0)
                {
                    // Let the runtime do the work for us, since we can execute code in this context.
                    return member.GetCustomAttribute(attributeType, inherit: false);
                }

                foreach (CustomAttributeData data in CustomAttributeData.GetCustomAttributes(member))
                {
                    if (AttributeTypeNamesMatch(attributeType, data.Constructor.ReflectedType!))
                    {
                        Attribute? attr = null;

                        Debug.Assert(data.ConstructorArguments.Count <= 1);

                        if (data.ConstructorArguments.Count == 1)
                        {
                            attr = (Attribute?)Activator.CreateInstance(attributeType, [data.ConstructorArguments[0].Value]);
                        }
                        else if (data.ConstructorArguments.Count == 0)
                        {
                            attr = (Attribute?)Activator.CreateInstance(attributeType);
                        }

                        if (attr != null)
                        {
                            foreach (CustomAttributeNamedArgument namedArgument in data.NamedArguments)
                            {
                                PropertyInfo p = attributeType.GetProperty(namedArgument.MemberInfo.Name, BindingFlags.Public | BindingFlags.Instance)!;
                                object value = namedArgument.TypedValue.Value!;

                                if (p.PropertyType.IsEnum)
                                {
                                    string val = value.ToString()!;
                                    value = Enum.Parse(p.PropertyType, val);
                                }

                                p.SetValue(attr, value, null);
                            }

                            return attr;
                        }
                    }
                }

                return null;
            }
        }

        /// <summary>
        /// Evaluates if two related "EventSource"-domain types should be considered the same
        /// </summary>
        /// <param name="attributeType">The attribute type in the load context - it's associated with the running
        /// EventSource type. This type may be different fromt he base type of the user-defined EventSource.</param>
        /// <param name="reflectedAttributeType">The attribute type in the reflection context - it's associated with
        /// the user-defined EventSource, and is in the same assembly as the eventSourceType passed to
        /// </param>
        /// <returns>True - if the types should be considered equivalent, False - otherwise</returns>
        private static bool AttributeTypeNamesMatch(Type attributeType, Type reflectedAttributeType)
        {
            return
                // are these the same type?
                attributeType == reflectedAttributeType ||
                // are the full typenames equal?
                string.Equals(attributeType.FullName, reflectedAttributeType.FullName, StringComparison.Ordinal) ||
                    // are the typenames equal and the namespaces under "Diagnostics.Tracing" (typically
                    // either Microsoft.Diagnostics.Tracing or System.Diagnostics.Tracing)?
                    string.Equals(attributeType.Name, reflectedAttributeType.Name, StringComparison.Ordinal) &&
                    attributeType.Namespace!.EndsWith("Diagnostics.Tracing", StringComparison.Ordinal) &&
                    reflectedAttributeType.Namespace!.EndsWith("Diagnostics.Tracing", StringComparison.Ordinal);
        }

        private static Type? GetEventSourceBaseType(Type eventSourceType, bool allowEventSourceOverride, bool reflectionOnly)
        {
            Type? ret = eventSourceType;

            // return false for "object" and interfaces
            if (ret.BaseType == null)
                return null;

            // now go up the inheritance chain until hitting a concrete type ("object" at worse)
            do
            {
                ret = ret.BaseType;
            }
            while (ret != null && ret.IsAbstract);

            if (ret != null)
            {
                if (!allowEventSourceOverride)
                {
                    if (reflectionOnly && ret.FullName != typeof(EventSource).FullName ||
                        !reflectionOnly && ret != typeof(EventSource))
                        return null;
                }
                else
                {
                    if (ret.Name != "EventSource")
                        return null;
                }
            }
            return ret;
        }

        private static bool RemoveFirstArgIfRelatedActivityId(ref ParameterInfo[] args)
        {
            // If the first parameter is (case insensitive) 'relatedActivityId' then skip it.
            if (args.Length > 0 && args[0].ParameterType == typeof(Guid) &&
                string.Equals(args[0].Name, "relatedActivityId", StringComparison.OrdinalIgnoreCase))
            {
                var newargs = new ParameterInfo[args.Length - 1];
                Array.Copy(args, 1, newargs, 0, args.Length - 1);
                args = newargs;

                return true;
            }

            return false;
        }

        // adds a enumeration (keyword, opcode, task or channel) represented by 'staticField'
        // to the manifest.
        private static void AddProviderEnumKind(ManifestBuilder manifest, FieldInfo staticField, string providerEnumKind)
        {
            bool reflectionOnly = staticField.Module.Assembly.ReflectionOnly;
            Type staticFieldType = staticField.FieldType;
            if (!reflectionOnly && (staticFieldType == typeof(EventOpcode)) || AttributeTypeNamesMatch(staticFieldType, typeof(EventOpcode)))
            {
                if (providerEnumKind != "Opcodes") goto Error;
                int value = (int)staticField.GetRawConstantValue()!;
                manifest.AddOpcode(staticField.Name, value);
            }
            else if (!reflectionOnly && (staticFieldType == typeof(EventTask)) || AttributeTypeNamesMatch(staticFieldType, typeof(EventTask)))
            {
                if (providerEnumKind != "Tasks") goto Error;
                int value = (int)staticField.GetRawConstantValue()!;
                manifest.AddTask(staticField.Name, value);
            }
            else if (!reflectionOnly && (staticFieldType == typeof(EventKeywords)) || AttributeTypeNamesMatch(staticFieldType, typeof(EventKeywords)))
            {
                if (providerEnumKind != "Keywords") goto Error;
                ulong value = unchecked((ulong)(long)staticField.GetRawConstantValue()!);
                manifest.AddKeyword(staticField.Name, value);
            }
            return;
            Error:
            manifest.ManifestError(SR.Format(SR.EventSource_EnumKindMismatch, staticField.FieldType.Name, providerEnumKind));
        }

        // Helper used by code:CreateManifestAndDescriptors to add a code:EventData descriptor for a method
        // with the code:EventAttribute 'eventAttribute'.  resourceManger may be null in which case we populate it
        // it is populated if we need to look up message resources
        private static void AddEventDescriptor(
            [NotNull] ref Dictionary<int, EventMetadata> eventData,
            string eventName,
            EventAttribute eventAttribute,
            ParameterInfo[] eventParameters,
            bool hasRelatedActivityID)
        {
            ref EventMetadata metadata = ref CollectionsMarshal.GetValueRefOrAddDefault(eventData, eventAttribute.EventId, out _);
            metadata.Descriptor = new EventDescriptor(
                    eventAttribute.EventId,
                    eventAttribute.Version,
                    (byte)eventAttribute.Channel,
                    (byte)eventAttribute.Level,
                    (byte)eventAttribute.Opcode,
                    (int)eventAttribute.Task,
                    unchecked((long)((ulong)eventAttribute.Keywords | SessionMask.All.ToEventKeywords())));

            metadata.Tags = eventAttribute.Tags;
            metadata.Name = eventName;
            metadata.Parameters = eventParameters;
            metadata.Message = eventAttribute.Message;
            metadata.ActivityOptions = eventAttribute.ActivityOptions;
            metadata.HasRelatedActivityID = hasRelatedActivityID;
            metadata.EventHandle = IntPtr.Zero;

            // We represent a byte[] with 2 EventData entries: an integer denoting the length and a blob of bytes in the data pointer.
            // This causes a spurious warning because eventDataCount is off by one for the byte[] case.
            // When writing to EventListeners, we want to check that the number of parameters is correct against the byte[] case.
            int eventListenerParameterCount = eventParameters.Length;
            bool allParametersAreInt32 = true;
            bool allParametersAreString = true;

            foreach (ParameterInfo parameter in eventParameters)
            {
                Type dataType = parameter.ParameterType;
                if (dataType == typeof(string))
                {
                    allParametersAreInt32 = false;
                }
                else if (dataType == typeof(int) ||
                    (dataType.IsEnum && Type.GetTypeCode(dataType.GetEnumUnderlyingType()) <= TypeCode.UInt32))
                {
                    // Int32 or an enum with a 1/2/4 byte backing type
                    allParametersAreString = false;
                }
                else
                {
                    if (dataType == typeof(byte[]))
                    {
                        eventListenerParameterCount++;
                    }

                    allParametersAreInt32 = false;
                    allParametersAreString = false;
                }
            }

            metadata.AllParametersAreInt32 = allParametersAreInt32;
            metadata.AllParametersAreString = allParametersAreString;
            metadata.EventListenerParameterCount = eventListenerParameterCount;
        }

        // Helper used by code:EventListener.AddEventSource and code:EventListener.EventListener
        // when a listener gets attached to a eventSource
        internal void AddListener(EventListener listener)
        {
            lock (EventListener.EventListenersLock)
            {
                Dictionary<int, bool>? enabledDict = null;
                if (m_eventData != null)
                {
                    enabledDict = new Dictionary<int, bool>(m_eventData.Count);
                    foreach (int eventId in m_eventData.Keys)
                    {
                        enabledDict[eventId] = false;
                    }
                }
                m_Dispatchers = new EventDispatcher(m_Dispatchers, enabledDict, listener);
                listener.OnEventSourceCreated(this);
            }
        }

        // Helper used by code:CreateManifestAndDescriptors to find user mistakes like reusing an event
        // index for two distinct events etc.  Throws exceptions when it finds something wrong.
        private static void DebugCheckEvent(ref Dictionary<string, string>? eventsByName,
            Dictionary<int, EventMetadata> eventData, MethodInfo method, EventAttribute eventAttribute,
            ManifestBuilder manifest, EventManifestOptions options)
        {
            int evtId = eventAttribute.EventId;
            string evtName = method.Name;
            int eventArg = GetHelperCallFirstArg(method);
            if (eventArg >= 0 && evtId != eventArg)
            {
                manifest.ManifestError(SR.Format(SR.EventSource_MismatchIdToWriteEvent, evtName, evtId, eventArg), true);
            }

            if (eventData.TryGetValue(evtId, out EventMetadata metadata) && metadata.Descriptor.EventId != 0)
            {
                manifest.ManifestError(SR.Format(SR.EventSource_EventIdReused, evtName, evtId), true);
            }

            // We give a task to things if they don't have one.
            // TODO this is moderately expensive (N*N).   We probably should not even bother....
            Debug.Assert(eventAttribute.Task != EventTask.None || eventAttribute.Opcode != EventOpcode.Info);
            foreach (int idx in eventData.Keys)
            {
                // skip unused Event IDs.
                if (eventData[idx].Name == null)
                    continue;

                if (eventData[idx].Descriptor.Task == (int)eventAttribute.Task && eventData[idx].Descriptor.Opcode == (int)eventAttribute.Opcode)
                {
                    manifest.ManifestError(SR.Format(SR.EventSource_TaskOpcodePairReused,
                                            evtName, evtId, eventData[idx].Name, idx));
                    // If we are not strict stop on first error.   We have had problems with really large providers taking forever.  because of many errors.
                    if ((options & EventManifestOptions.Strict) == 0)
                        break;
                }
            }

            // for non-default event opcodes the user must define a task!
            if (eventAttribute.Opcode != EventOpcode.Info)
            {
                bool failure = false;
                if (eventAttribute.Task == EventTask.None)
                    failure = true;
                else
                {
                    // If you have the auto-assigned Task, then you did not explicitly set one.
                    // This is OK for Start events because we have special logic to assign the task to a prefix derived from the event name
                    // But all other cases we want to catch the omission.
                    var autoAssignedTask = (EventTask)(0xFFFE - evtId);
                    if (eventAttribute.Opcode != EventOpcode.Start && eventAttribute.Opcode != EventOpcode.Stop && eventAttribute.Task == autoAssignedTask)
                        failure = true;
                }
                if (failure)
                {
                    manifest.ManifestError(SR.Format(SR.EventSource_EventMustHaveTaskIfNonDefaultOpcode, evtName, evtId));
                }
            }

            // If we ever want to enforce the rule: MethodName = TaskName + OpcodeName here's how:
            //  (the reason we don't is backwards compat and the need for handling this as a non-fatal error
            //  by eventRegister.exe)
            // taskName & opcodeName could be passed in by the caller which has opTab & taskTab handy
            // if (!(((int)eventAttribute.Opcode == 0 && evtName == taskName) || (evtName == taskName+opcodeName)))
            // {
            //     throw new WarningException(SR.EventSource_EventNameDoesNotEqualTaskPlusOpcode);
            // }

            eventsByName ??= new Dictionary<string, string>();

            if (eventsByName.ContainsKey(evtName))
            {
                manifest.ManifestError(SR.Format(SR.EventSource_EventNameReused, evtName), true);
            }

            eventsByName[evtName] = evtName;
        }

        /// <summary>
        /// This method looks at the IL and tries to pattern match against the standard
        /// 'boilerplate' event body
        /// <code>
        ///     { if (Enabled()) WriteEvent(#, ...) }
        /// </code>
        /// If the pattern matches, it returns the literal number passed as the first parameter to
        /// the WriteEvent.  This is used to find common user errors (mismatching this
        /// number with the EventAttribute ID).  It is only used for validation.
        /// </summary>
        /// <param name="method">The method to probe.</param>
        /// <returns>The literal value or -1 if the value could not be determined. </returns>
#if !NATIVEAOT
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
                    Justification = "The method calls MethodBase.GetMethodBody. Trimming application can change IL of various methods" +
                                    "which can lead to change of behavior. This method only uses this to validate usage of event source APIs." +
                                    "In the worst case it will not be able to determine the value it's looking for and will not perform" +
                                    "any validation.")]
#endif
        private static int GetHelperCallFirstArg(MethodInfo method)
        {
#if !NATIVEAOT
            // Currently searches for the following pattern
            //
            // ...     // CAN ONLY BE THE INSTRUCTIONS BELOW
            // LDARG0
            // LDC.I4 XXX
            // ...     // CAN ONLY BE THE INSTRUCTIONS BELOW CAN'T BE A BRANCH OR A CALL
            // CALL
            // NOP     // 0 or more times
            // RET
            //
            // If we find this pattern we return the XXX.  Otherwise we return -1.
            byte[] instrs = method.GetMethodBody()!.GetILAsByteArray()!;
            int retVal = -1;
            for (int idx = 0; idx < instrs.Length;)
            {
                switch (instrs[idx])
                {
                    case 0: // NOP
                    case 1: // BREAK
                    case 2: // LDARG_0
                    case 3: // LDARG_1
                    case 4: // LDARG_2
                    case 5: // LDARG_3
                    case 6: // LDLOC_0
                    case 7: // LDLOC_1
                    case 8: // LDLOC_2
                    case 9: // LDLOC_3
                    case 10: // STLOC_0
                    case 11: // STLOC_1
                    case 12: // STLOC_2
                    case 13: // STLOC_3
                        break;
                    case 14: // LDARG_S
                    case 16: // STARG_S
                        idx++;
                        break;
                    case 20: // LDNULL
                        break;
                    case 21: // LDC_I4_M1
                    case 22: // LDC_I4_0
                    case 23: // LDC_I4_1
                    case 24: // LDC_I4_2
                    case 25: // LDC_I4_3
                    case 26: // LDC_I4_4
                    case 27: // LDC_I4_5
                    case 28: // LDC_I4_6
                    case 29: // LDC_I4_7
                    case 30: // LDC_I4_8
                        if (idx > 0 && instrs[idx - 1] == 2)  // preceded by LDARG0
                            retVal = instrs[idx] - 22;
                        break;
                    case 31: // LDC_I4_S
                        if (idx > 0 && instrs[idx - 1] == 2)  // preceded by LDARG0
                            retVal = instrs[idx + 1];
                        idx++;
                        break;
                    case 32: // LDC_I4
                        idx += 4;
                        break;
                    case 37: // DUP
                        break;
                    case 40: // CALL
                        idx += 4;

                        if (retVal >= 0)
                        {
                            // Is this call just before return?
                            for (int search = idx + 1; search < instrs.Length; search++)
                            {
                                if (instrs[search] == 42)  // RET
                                    return retVal;
                                if (instrs[search] != 0)   // NOP
                                    break;
                            }
                        }
                        retVal = -1;
                        break;
                    case 44: // BRFALSE_S
                    case 45: // BRTRUE_S
                        retVal = -1;
                        idx++;
                        break;
                    case 57: // BRFALSE
                    case 58: // BRTRUE
                        retVal = -1;
                        idx += 4;
                        break;
                    case 103: // CONV_I1
                    case 104: // CONV_I2
                    case 105: // CONV_I4
                    case 106: // CONV_I8
                    case 109: // CONV_U4
                    case 110: // CONV_U8
                        break;
                    case 140: // BOX
                    case 141: // NEWARR
                        idx += 4;
                        break;
                    case 162: // STELEM_REF
                        break;
                    case 254: // PREFIX
                        idx++;
                        // Covers the CEQ instructions used in debug code for some reason.
                        if (idx >= instrs.Length || instrs[idx] >= 6)
                            goto default;
                        break;
                    default:
                        /* Debug.Fail("Warning: User validation code sub-optimial: Unsupported opcode " + instrs[idx] +
                            " at " + idx + " in method " + method.Name); */
                        return -1;
                }
                idx++;
            }
#endif
            return -1;
        }

        /// <summary>
        /// Sends an error message to the debugger (outputDebugString), as well as the EventListeners
        /// It will do this even if the EventSource is not enabled.
        /// </summary>
        internal void ReportOutOfBandMessage(string msg)
        {
            try
            {
                if (m_outOfBandMessageCount < 16 - 1)     // Note this is only if size byte
                {
                    m_outOfBandMessageCount++;
                }
                else
                {
                    if (m_outOfBandMessageCount == 16)
                        return;
                    m_outOfBandMessageCount = 16;    // Mark that we hit the limit.  Notify them that this is the case.
                    msg = "Reached message limit.   End of EventSource error messages.";
                }

                // send message to debugger
                Debugger.Log(0, null, $"EventSource Error: {msg}{Environment.NewLine}");

                // Send it to all listeners.
                WriteEventString(msg);
                WriteStringToAllListeners("EventSourceMessage", msg);
            }
            catch { }      // If we fail during last chance logging, well, we have to give up....
        }

        private static EventSourceSettings ValidateSettings(EventSourceSettings settings)
        {
            const EventSourceSettings evtFormatMask = EventSourceSettings.EtwManifestEventFormat |
                                EventSourceSettings.EtwSelfDescribingEventFormat;
            if ((settings & evtFormatMask) == evtFormatMask)
            {
                throw new ArgumentException(SR.EventSource_InvalidEventFormat, nameof(settings));
            }

            // If you did not explicitly ask for manifest, you get self-describing.
            if ((settings & evtFormatMask) == 0)
                settings |= EventSourceSettings.EtwSelfDescribingEventFormat;
            return settings;
        }

        private bool ThrowOnEventWriteErrors => (m_config & EventSourceSettings.ThrowOnEventWriteErrors) != 0;

        private bool SelfDescribingEvents
        {
            get
            {
                Debug.Assert(((m_config & EventSourceSettings.EtwManifestEventFormat) != 0) !=
                                ((m_config & EventSourceSettings.EtwSelfDescribingEventFormat) != 0));
                return (m_config & EventSourceSettings.EtwSelfDescribingEventFormat) != 0;
            }
        }

#if NATIVEAOT
        // If EventSource feature is enabled, default EventSources need to be initialized for NativeAOT
        // In CoreCLR, this is done via a call from the runtime as part of coreclr_initialize
#pragma warning disable CA2255
        [ModuleInitializer]
#pragma warning restore CA2255
#endif
        internal static void InitializeDefaultEventSources()
        {
            if (!EventSource.IsSupported)
            {
                return;
            }

            // NOTE: this define is being used inconsistently. Most places mean just EventPipe support, but then a few places use
            // it to mean other aspects of tracing such as these EventSources.
#if FEATURE_PERFTRACING
            _ = NativeRuntimeEventSource.Log;
            // ThreadPoolWorkQueue uses FrameworkEventSource during ThreadPool initialization. If that is the first time
            // the FrameworkEventSource is being used, creating it on-demand will acquire the EventListener lock which can deadlock.
            // See https://github.com/dotnet/runtime/issues/126591. We avoid that by pre-creating the FrameworkEventSource here.
            _ = FrameworkEventSource.Log;
#if !TARGET_BROWSER
            _ = RuntimeEventSource.Log;
#endif
#endif
            // System.Diagnostics.MetricsEventSource allows listening to Meters and indirectly
            // also creates the System.Runtime Meter.

            // Functionally we could preregister NativeRuntimeEventSource and RuntimeEventSource as well, but it may not provide
            // much benefit. The main benefit for MetricsEventSource is that the app may never use it and it defers
            // pulling the System.Diagnostics.DiagnosticSource assembly into the process until it is needed.
            if (IsMeterSupported)
            {
                const string name = "System.Diagnostics.Metrics";

                // 20752bc4-c151-50f5-f27b-df92d8af5a61
                Guid id = new Guid(0x20752BC4, 0xC151, 0x50F5, 0xF2, 0x7B, 0xDF, 0x92, 0xD8, 0xAF, 0x5A, 0x61);
                EventSourceInitHelper.PreregisterEventProviders(id, name, EventSourceInitHelper.GetMetricsEventSource);
            }
        }

#if CORECLR
        [UnmanagedCallersOnly]
        private static unsafe void InitializeDefaultEventSources(Exception* pException)
        {
            try
            {
                InitializeDefaultEventSources();
            }
            catch (Exception ex)
            {
                *pException = ex;
            }
        }
#endif
#endregion
    }

    // This type is logically just more static EventSource functionality but it needs to be a separate class
    // to ensure that the IL linker can remove unused methods in it. Methods defined within the EventSource type
    // are never removed because EventSource has the potential to reflect over its own members.
    internal static class EventSourceInitHelper
    {
        private static List<Func<EventSource?>> s_preregisteredEventSourceFactories = new List<Func<EventSource?>>();
        private static readonly Dictionary<Guid, EventSource.OverrideEventProvider> s_preregisteredEtwProviders = new Dictionary<Guid, EventSource.OverrideEventProvider>();
#if FEATURE_PERFTRACING
        private static readonly Dictionary<string, EventSource.OverrideEventProvider> s_preregisteredEventPipeProviders = new Dictionary<string, EventSource.OverrideEventProvider>();
#endif

        internal static EventSource? GetMetricsEventSource()
        {
            return GetInstance(null) as EventSource;

            [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "GetInstance")]
            [return: UnsafeAccessorType("System.Diagnostics.Metrics.MetricsEventSource, System.Diagnostics.DiagnosticSource")]
            static extern object GetInstance(
                [UnsafeAccessorType("System.Diagnostics.Metrics.MetricsEventSource, System.Diagnostics.DiagnosticSource")] object? _);
        }

        // Pre-registration creates and registers an EventProvider prior to the EventSource being constructed.
        // If a tracing session is started using the provider then the EventSource will be constructed on demand.
        internal static unsafe void PreregisterEventProviders(Guid id, string name, Func<EventSource?> eventSourceFactory)
        {
            // NOTE: Pre-registration has some minor limitations and variations to normal EventSource behavior:
            // 1. Instead of delivering OnEventCommand callbacks during the EventSource constructor it may deliver them after
            //    the constructor instead. This occurs because the EventProvider callback might create the EventSource instance
            //    in the factory method, then deliver the callback.
            // 2. EventSource traits aren't supported. Normally EventSource.Initialize() would init provider metadata including
            //    traits but the SetInformation() call we use below generates metadata from name only. If we want traits support
            //    in the future it could be added.

            // NOTE: You might think this preregister logic could be simplified by using an Action to create the EventSource instead of
            // Func<EventSource> and then allow the EventSource to initialize as normal. This doesn't work however because calling
            // EtwEventProvider.Register() inside of an ETW callback deadlocks. Instead we have to bind the EventSource to the
            // EtwEventProvider that was already registered and use the callback we got on that provider to invoke EventSource.SendCommand().
            try
            {
                s_preregisteredEventSourceFactories.Add(eventSourceFactory);

                EventSource.OverrideEventProvider etwProvider = new EventSource.OverrideEventProvider(eventSourceFactory, EventProviderType.ETW);
                etwProvider.Register(id, name);
#if TARGET_WINDOWS
                byte[] providerMetadata = Statics.MetadataForString(name, 0, 0, 0);
                fixed (byte* pMetadata = providerMetadata)
                {
                    etwProvider.SetInformation(
                        Interop.Advapi32.EVENT_INFO_CLASS.SetTraits,
                        pMetadata,
                        (uint)providerMetadata.Length);
                }
#endif // TARGET_WINDOWS
                lock (s_preregisteredEtwProviders)
                {
                    s_preregisteredEtwProviders[id] = etwProvider;
                }

#if FEATURE_PERFTRACING
                EventSource.OverrideEventProvider eventPipeProvider = new EventSource.OverrideEventProvider(eventSourceFactory, EventProviderType.EventPipe);
                eventPipeProvider.Register(id, name);
                lock (s_preregisteredEventPipeProviders)
                {
                    s_preregisteredEventPipeProviders[name] = eventPipeProvider;
                }
#endif
            }
            catch (Exception)
            {
                // If there is a failure registering then the normal EventSource.Initialize() path can try to register
                // again if/when the EventSource is constructed.
            }
        }

        internal static void EnsurePreregisteredEventSourcesExist()
        {
            if (!EventSource.IsSupported)
            {
                return;
            }

            // In a multi-threaded race its possible that one thread will be creating the EventSources while a 2nd thread
            // exits this function and observes the s_EventSources list without the new EventSources in it.
            // There is no known issue here having a small window of time where the pre-registered EventSources are not in
            // the list as long as we still guarantee they get initialized in the near future and reported to the
            // same EventListener.OnEventSourceCreated() callback.
            Func<EventSource?>[] factories;
            lock (s_preregisteredEventSourceFactories)
            {
                factories = s_preregisteredEventSourceFactories.ToArray();
                s_preregisteredEventSourceFactories.Clear();
            }
            foreach (Func<EventSource?> factory in factories)
            {
                factory();
            }
        }

        internal static EventSource.OverrideEventProvider? TryGetPreregisteredEtwProvider(Guid id)
        {
            lock (s_preregisteredEtwProviders)
            {
                s_preregisteredEtwProviders.Remove(id, out EventSource.OverrideEventProvider? provider);
                return provider;
            }
        }

#if FEATURE_PERFTRACING
        internal static EventSource.OverrideEventProvider? TryGetPreregisteredEventPipeProvider(string name)
        {
            lock (s_preregisteredEventPipeProviders)
            {
                s_preregisteredEventPipeProviders.Remove(name, out EventSource.OverrideEventProvider? provider);
                return provider;
            }
        }
#endif
    }

    /// <summary>
    /// Enables specifying event source configuration options to be used in the EventSource constructor.
    /// </summary>
    [Flags]
    public enum EventSourceSettings
    {
        /// <summary>
        /// This specifies none of the special configuration options should be enabled.
        /// </summary>
        Default = 0,
        /// <summary>
        /// Normally an EventSource NEVER throws; setting this option will tell it to throw when it encounters errors.
        /// </summary>
        ThrowOnEventWriteErrors = 1,
        /// <summary>
        /// Setting this option is a directive to the ETW listener should use manifest-based format when
        /// firing events. This is the default option when defining a type derived from EventSource
        /// (using the protected EventSource constructors).
        /// Only one of EtwManifestEventFormat or EtwSelfDescribingEventFormat should be specified
        /// </summary>
        EtwManifestEventFormat = 4,
        /// <summary>
        /// Setting this option is a directive to the ETW listener should use self-describing event format
        /// when firing events. This is the default option when creating a new instance of the EventSource
        /// type (using the public EventSource constructors).
        /// Only one of EtwManifestEventFormat or EtwSelfDescribingEventFormat should be specified
        /// </summary>
        EtwSelfDescribingEventFormat = 8,
    }

    /// <summary>
    /// Passed to the code:EventSource.OnEventCommand callback
    /// </summary>
    public class EventCommandEventArgs : EventArgs
    {
        /// <summary>
        /// Gets the command for the callback.
        /// </summary>
        public EventCommand Command { get; internal set; }

        /// <summary>
        /// Gets the arguments for the callback.
        /// </summary>
        public IDictionary<string, string?>? Arguments { get; internal set; }

        /// <summary>
        /// Enables the event that has the specified identifier.
        /// </summary>
        /// <param name="eventId">Event ID of event to be enabled</param>
        /// <returns>true if eventId is in range</returns>
        public bool EnableEvent(int eventId)
        {
            if (Command != EventCommand.Enable && Command != EventCommand.Disable)
                throw new InvalidOperationException();
            return eventSource.EnableEventForDispatcher(dispatcher, eventProviderType, eventId, true);
        }

        /// <summary>
        /// Disables the event that have the specified identifier.
        /// </summary>
        /// <param name="eventId">Event ID of event to be disabled</param>
        /// <returns>true if eventId is in range</returns>
        public bool DisableEvent(int eventId)
        {
            if (Command != EventCommand.Enable && Command != EventCommand.Disable)
                throw new InvalidOperationException();
            return eventSource.EnableEventForDispatcher(dispatcher, eventProviderType, eventId, false);
        }

#region private

        internal EventCommandEventArgs(EventCommand command, IDictionary<string, string?>? arguments, EventSource eventSource,
            EventListener? listener, EventProviderType eventProviderType, int perEventSourceSessionId, bool enable, EventLevel level, EventKeywords matchAnyKeyword)
        {
            this.Command = command;
            this.Arguments = arguments;
            this.eventSource = eventSource;
            this.listener = listener;
            this.eventProviderType = eventProviderType;
            this.perEventSourceSessionId = perEventSourceSessionId;
            this.enable = enable;
            this.level = level;
            this.matchAnyKeyword = matchAnyKeyword;
        }

        internal EventSource eventSource;
        internal EventDispatcher? dispatcher;
        internal EventProviderType eventProviderType;

        // These are the arguments of sendCommand and are only used for deferring commands until after we are fully initialized.
        internal EventListener? listener;
        internal int perEventSourceSessionId;
        internal bool enable;
        internal EventLevel level;
        internal EventKeywords matchAnyKeyword;
        internal EventCommandEventArgs? nextCommand;     // We form a linked list of these deferred commands.

#endregion
    }

    /// <summary>
    /// EventSourceCreatedEventArgs is passed to <see cref="EventListener.EventSourceCreated"/>
    /// </summary>
    public class EventSourceCreatedEventArgs : EventArgs
    {
        /// <summary>
        /// The EventSource that is attaching to the listener.
        /// </summary>
        public EventSource? EventSource
        {
            get;
            internal set;
        }
    }

    /// <summary>
    /// EventWrittenEventArgs is passed to the user-provided override for
    /// <see cref="EventListener.OnEventWritten"/> when an event is fired.
    /// </summary>
    public class EventWrittenEventArgs : EventArgs
    {
        private ref EventSource.EventMetadata Metadata => ref CollectionsMarshal.GetValueRefOrNullRef(EventSource.m_eventData!, EventId);

        /// <summary>
        /// The name of the event.
        /// </summary>
        public string? EventName
        {
            get => _moreInfo?.EventName ?? (EventId <= 0 ? null : Metadata.Name);
            internal set => MoreInfo.EventName = value;
        }

        /// <summary>
        /// Gets the event ID for the event that was written.
        /// </summary>
        public int EventId { get; }

        private Guid _activityId;

        /// <summary>
        /// Gets the activity ID for the thread on which the event was written.
        /// </summary>
        public Guid ActivityId
        {
            get
            {
                if (_activityId == Guid.Empty)
                {
                    _activityId = EventSource.CurrentThreadActivityId;
                }

                return _activityId;
            }
        }

        /// <summary>
        /// Gets the related activity ID if one was specified when the event was written.
        /// </summary>
        public Guid RelatedActivityId => _moreInfo?.RelatedActivityId ?? default;

        /// <summary>
        /// Gets the payload for the event.
        /// </summary>
        public ReadOnlyCollection<object?>? Payload { get; internal set; }

        /// <summary>
        /// Gets the payload argument names.
        /// </summary>
        public ReadOnlyCollection<string>? PayloadNames
        {
            get => _moreInfo?.PayloadNames ?? (EventId <= 0 ? null : Metadata.ParameterNames);
            internal set => MoreInfo.PayloadNames = value;
        }

        /// <summary>
        /// Gets the event source object.
        /// </summary>
        public EventSource EventSource { get; }

        /// <summary>
        /// Gets the keywords for the event.
        /// </summary>
        public EventKeywords Keywords
        {
            get => EventId <= 0 ? (_moreInfo?.Keywords ?? default) : (EventKeywords)Metadata.Descriptor.Keywords;
            internal set => MoreInfo.Keywords = value;
        }

        /// <summary>
        /// Gets the operation code for the event.
        /// </summary>
        public EventOpcode Opcode
        {
            get => EventId <= 0 ? (_moreInfo?.Opcode ?? default) : (EventOpcode)Metadata.Descriptor.Opcode;
            internal set => MoreInfo.Opcode = value;
        }

        /// <summary>
        /// Gets the task for the event.
        /// </summary>
        public EventTask Task => EventId <= 0 ? EventTask.None : (EventTask)Metadata.Descriptor.Task;

        /// <summary>
        /// Any provider/user defined options associated with the event.
        /// </summary>
        public EventTags Tags
        {
            get => EventId <= 0 ? (_moreInfo?.Tags ?? default) : Metadata.Tags;
            internal set => MoreInfo.Tags = value;
        }

        /// <summary>
        /// Gets the message for the event.  If the message has {N} parameters they are NOT substituted.
        /// </summary>
        public string? Message
        {
            get => _moreInfo?.Message ?? (EventId <= 0 ? null : Metadata.Message);
            internal set => MoreInfo.Message = value;
        }

        /// <summary>
        /// Gets the channel for the event.
        /// </summary>
        public EventChannel Channel => EventId <= 0 ? EventChannel.None : (EventChannel)Metadata.Descriptor.Channel;

        /// <summary>
        /// Gets the version of the event.
        /// </summary>
        public byte Version => EventId <= 0 ? (byte)0 : Metadata.Descriptor.Version;

        /// <summary>
        /// Gets the level for the event.
        /// </summary>
        public EventLevel Level
        {
            get => EventId <= 0 ? (_moreInfo?.Level ?? default) : (EventLevel)Metadata.Descriptor.Level;
            internal set => MoreInfo.Level = value;
        }

        /// <summary>
        /// Gets the identifier for the OS thread that wrote the event.
        /// </summary>
        public long OSThreadId
        {
            get
            {
                ref long? osThreadId = ref MoreInfo.OsThreadId;
                if (!osThreadId.HasValue)
                {
                    osThreadId = (long)Thread.CurrentOSThreadId;
                }

                return osThreadId.Value;
            }
            internal set => MoreInfo.OsThreadId = value;
        }

        /// <summary>
        /// Gets a UTC DateTime that specifies when the event was written.
        /// </summary>
        public DateTime TimeStamp { get; internal set; }

        internal EventWrittenEventArgs(EventSource eventSource, int eventId)
        {
            EventSource = eventSource;
            EventId = eventId;
            TimeStamp = DateTime.UtcNow;
        }

        internal unsafe EventWrittenEventArgs(EventSource eventSource, int eventId, Guid* pActivityID, Guid* pChildActivityID)
            : this(eventSource, eventId)
        {
            if (pActivityID != null)
            {
                _activityId = *pActivityID;
            }

            if (pChildActivityID != null)
            {
                MoreInfo.RelatedActivityId = *pChildActivityID;
            }
        }

        private MoreEventInfo? _moreInfo;
        private MoreEventInfo MoreInfo => _moreInfo ??= new MoreEventInfo();

        private sealed class MoreEventInfo
        {
            public string? Message;
            public string? EventName;
            public ReadOnlyCollection<string>? PayloadNames;
            public Guid RelatedActivityId;
            public long? OsThreadId;
            public EventTags Tags;
            public EventOpcode Opcode;
            public EventLevel Level;
            public EventKeywords Keywords;
        }
    }

    /// <summary>
    /// Allows customizing defaults and specifying localization support for the event source class to which it is applied.
    /// </summary>
    [AttributeUsage(AttributeTargets.Class)]
    public sealed class EventSourceAttribute : Attribute
    {
        /// <summary>
        /// Overrides the ETW name of the event source (which defaults to the class name)
        /// </summary>
        public string? Name { get; set; }

        /// <summary>
        /// Overrides the default (calculated) Guid of an EventSource type. Explicitly defining a GUID is discouraged,
        /// except when upgrading existing ETW providers to using event sources.
        /// </summary>
        public string? Guid { get; set; }

        /// <summary>
        /// <para>
        /// EventSources support localization of events. The names used for events, opcodes, tasks, keywords and maps
        /// can be localized to several languages if desired. This works by creating a ResX style string table
        /// (by simply adding a 'Resource File' to your project). This resource file is given a name e.g.
        /// 'DefaultNameSpace.ResourceFileName' which can be passed to the ResourceManager constructor to read the
        /// resources. This name is the value of the LocalizationResources property.
        /// </para><para>
        /// If LocalizationResources property is non-null, then EventSource will look up the localized strings for events by
        /// using the following resource naming scheme
        /// </para>
        ///     <para>* event_EVENTNAME</para>
        ///     <para>* task_TASKNAME</para>
        ///     <para>* keyword_KEYWORDNAME</para>
        ///     <para>* map_MAPNAME</para>
        /// <para>
        /// where the capitalized name is the name of the event, task, keyword, or map value that should be localized.
        /// Note that the localized string for an event corresponds to the Message string, and can have {0} values
        /// which represent the payload values.
        /// </para>
        /// </summary>
        public string? LocalizationResources { get; set; }
    }

    /// <summary>
    /// Any instance methods in a class that subclasses <see cref="EventSource"/> and that return void are
    /// assumed by default to be methods that generate an ETW event. Enough information can be deduced from the
    /// name of the method and its signature to generate basic schema information for the event. The
    /// <see cref="EventAttribute"/> class allows you to specify additional event schema information for an event if
    /// desired.
    /// </summary>
    [AttributeUsage(AttributeTargets.Method)]
    public sealed class EventAttribute : Attribute
    {
        /// <summary>Construct an EventAttribute with specified eventId</summary>
        /// <param name="eventId">ID of the ETW event (an integer between 1 and 65535)</param>
        public EventAttribute(int eventId)
        {
            EventId = eventId;
            Level = EventLevel.Informational;
        }

        /// <summary>Event's ID</summary>
        public int EventId { get; }
        /// <summary>Event's severity level: indicates the severity or verbosity of the event</summary>
        public EventLevel Level { get; set; }
        /// <summary>Event's keywords: allows classification of events by "categories"</summary>
        public EventKeywords Keywords { get; set; }
        /// <summary>Event's operation code: allows defining operations, generally used with Tasks</summary>
        public EventOpcode Opcode
        {
            get => m_opcode;
            set
            {
                m_opcode = value;
                m_opcodeSet = true;
            }
        }

        internal bool IsOpcodeSet => m_opcodeSet;

        /// <summary>Event's task: allows logical grouping of events</summary>
        public EventTask Task { get; set; }

        /// <summary>Event's channel: defines an event log as an additional destination for the event</summary>
        public EventChannel Channel { get; set; }

        /// <summary>Event's version</summary>
        public byte Version { get; set; }

        /// <summary>
        /// This can be specified to enable formatting and localization of the event's payload. You can
        /// use standard .NET substitution operators (eg {1}) in the string and they will be replaced
        /// with the 'ToString()' of the corresponding part of the  event payload.
        /// </summary>
        public string? Message { get; set; }

        /// <summary>
        /// User defined options associated with the event.  These do not have meaning to the EventSource but
        /// are passed through to listeners which given them semantics.
        /// </summary>
        public EventTags Tags { get; set; }

        /// <summary>
        /// Allows fine control over the Activity IDs generated by start and stop events
        /// </summary>
        public EventActivityOptions ActivityOptions { get; set; }

#region private
        private EventOpcode m_opcode;
        private bool m_opcodeSet;
#endregion
    }

    /// <summary>
    /// By default all instance methods in a class that subclasses code:EventSource that and return
    /// void are assumed to be methods that generate an event. This default can be overridden by specifying
    /// the code:NonEventAttribute
    /// </summary>
    [AttributeUsage(AttributeTargets.Method)]
    public sealed class NonEventAttribute : Attribute
    {
        /// <summary>
        /// Constructs a default NonEventAttribute
        /// </summary>
        public NonEventAttribute() { }
    }

    /// <summary>
    /// EventChannelAttribute allows customizing channels supported by an EventSource. This attribute must be
    /// applied to an member of type EventChannel defined in a Channels class nested in the EventSource class:
    /// <code>
    ///     public static class Channels
    ///     {
    ///         [Channel(Enabled = true, EventChannelType = EventChannelType.Admin)]
    ///         public const EventChannel Admin = (EventChannel)16;
    ///
    ///         [Channel(Enabled = false, EventChannelType = EventChannelType.Operational)]
    ///         public const EventChannel Operational = (EventChannel)17;
    ///     }
    /// </code>
    /// </summary>
    [AttributeUsage(AttributeTargets.Field)]
    internal sealed class EventChannelAttribute : Attribute
    {
        /// <summary>
        /// Specified whether the channel is enabled by default
        /// </summary>
        public bool Enabled { get; set; }

        /// <summary>
        /// Legal values are in EventChannelType
        /// </summary>
        public EventChannelType EventChannelType { get; set; }

        // TODO: there is a convention that the name is the Provider/Type   Should we provide an override?
        // public string Name { get; set; }
    }

    /// <summary>
    /// Allowed channel types
    /// </summary>
    internal enum EventChannelType
    {
        /// <summary>The admin channel</summary>
        Admin = 1,
        /// <summary>The operational channel</summary>
        Operational,
        /// <summary>The Analytic channel</summary>
        Analytic,
        /// <summary>The debug channel</summary>
        Debug,
    }

    /// <summary>
    /// Describes the pre-defined command (EventCommandEventArgs.Command property) that is passed to the OnEventCommand callback.
    /// </summary>
    public enum EventCommand
    {
        /// <summary>
        /// Update EventSource state
        /// </summary>
        Update = 0,
        /// <summary>
        /// Request EventSource to generate and send its manifest
        /// </summary>
        SendManifest = -1,
        /// <summary>
        /// Enable event
        /// </summary>
        Enable = -2,
        /// <summary>
        /// Disable event
        /// </summary>
        Disable = -3
    }

#region private classes

    // holds a bitfield representing a session mask
    /// <summary>
    /// A SessionMask represents a set of (at most MAX) sessions as a bit mask. The perEventSourceSessionId
    /// is the index in the SessionMask of the bit that will be set. These can translate to
    /// EventSource's reserved keywords bits using the provided ToEventKeywords() and
    /// FromEventKeywords() methods.
    /// </summary>
    internal struct SessionMask
    {
        public SessionMask(SessionMask m)
        { m_mask = m.m_mask; }

        public SessionMask(uint mask = 0)
        { m_mask = mask & MASK; }

        public bool IsEqualOrSupersetOf(SessionMask m)
        {
            return (this.m_mask | m.m_mask) == this.m_mask;
        }

        public static SessionMask All => new SessionMask(MASK);

        public static SessionMask FromId(int perEventSourceSessionId)
        {
            Debug.Assert(perEventSourceSessionId < MAX);
            return new SessionMask((uint)1 << perEventSourceSessionId);
        }

        public ulong ToEventKeywords()
        {
            return (ulong)m_mask << SHIFT_SESSION_TO_KEYWORD;
        }

        public static SessionMask FromEventKeywords(ulong m)
        {
            return new SessionMask((uint)(m >> SHIFT_SESSION_TO_KEYWORD));
        }

        public bool this[int perEventSourceSessionId]
        {
            get
            {
                Debug.Assert(perEventSourceSessionId < MAX);
                return (m_mask & (1 << perEventSourceSessionId)) != 0;
            }
            set
            {
                Debug.Assert(perEventSourceSessionId < MAX);
                if (value) m_mask |= ((uint)1 << perEventSourceSessionId);
                else m_mask &= ~((uint)1 << perEventSourceSessionId);
            }
        }

        public static SessionMask operator |(SessionMask m1, SessionMask m2) =>
            new SessionMask(m1.m_mask | m2.m_mask);

        public static SessionMask operator &(SessionMask m1, SessionMask m2) =>
            new SessionMask(m1.m_mask & m2.m_mask);

        public static SessionMask operator ^(SessionMask m1, SessionMask m2) =>
            new SessionMask(m1.m_mask ^ m2.m_mask);

        public static SessionMask operator ~(SessionMask m) =>
            new SessionMask(MASK & ~(m.m_mask));

        public static explicit operator ulong(SessionMask m) => m.m_mask;

        public static explicit operator uint(SessionMask m) => m.m_mask;

        private uint m_mask;

        internal const int SHIFT_SESSION_TO_KEYWORD = 44;         // bits 44-47 inclusive are reserved
        internal const uint MASK = 0x0fU;                         // the mask of 4 reserved bits
        internal const uint MAX = 4;                              // maximum number of simultaneous ETW sessions supported
    }

    /// <summary>
    /// code:EventDispatchers are a simple 'helper' structure that holds the filtering state
    /// (m_EventEnabled) for a particular EventSource X EventListener tuple
    ///
    /// Thus a single EventListener may have many EventDispatchers (one for every EventSource
    /// that EventListener has activate) and a Single EventSource may also have many
    /// event Dispatchers (one for every EventListener that has activated it).
    ///
    /// Logically a particular EventDispatcher belongs to exactly one EventSource and exactly
    /// one EventListener (although EventDispatcher does not 'remember' the EventSource it is
    /// associated with.
    /// </summary>
    internal sealed class EventDispatcher
    {
        internal EventDispatcher(EventDispatcher? next, Dictionary<int, bool>? eventEnabled, EventListener listener)
        {
            m_Next = next;
            m_EventEnabled = eventEnabled;
            m_Listener = listener;
        }

        // Instance fields
        internal readonly EventListener m_Listener;   // The dispatcher this entry is for
        internal Dictionary<int, bool>? m_EventEnabled;              // For every event in a the eventSource, is it enabled?

        // Only guaranteed to exist after a EnsureInit()
        internal EventDispatcher? m_Next;              // These form a linked list in code:EventSource.m_Dispatchers
        // Of all listeners for that eventSource.
    }

    /// <summary>
    /// Flags that can be used with EventSource.GenerateManifest to control how the ETW manifest for the EventSource is
    /// generated.
    /// </summary>
    [Flags]
    public enum EventManifestOptions
    {
        /// <summary>
        /// Only the resources associated with current UI culture are included in the  manifest
        /// </summary>
        None = 0x0,
        /// <summary>
        /// Throw exceptions for any inconsistency encountered
        /// </summary>
        Strict = 0x1,
        /// <summary>
        /// Generate a "resources" node under "localization" for every satellite assembly provided
        /// </summary>
        AllCultures = 0x2,
        /// <summary>
        /// Generate the manifest only if the event source needs to be registered on the machine,
        /// otherwise return null (but still perform validation if Strict is specified)
        /// </summary>
        OnlyIfNeededForRegistration = 0x4,
        /// <summary>
        /// When generating the manifest do *not* enforce the rule that the current EventSource class
        /// must be the base class for the user-defined type passed in. This allows validation of .net
        /// event sources using the new validation code
        /// </summary>
        AllowEventSourceOverride = 0x8,
    }

    /// <summary>
    /// Used to send the m_rawManifest into the event dispatcher as a series of events.
    /// </summary>
    internal struct ManifestEnvelope
    {
        public const int MaxChunkSize = 0xF700;
        public enum ManifestFormats : byte
        {
            SimpleXmlFormat = 1,          // simply dump the XML manifest as UTF8
        }

        public ManifestFormats Format;
        public byte MajorVersion;
        public byte MinorVersion;
        public byte Magic;
        public ushort TotalChunks;
        public ushort ChunkNumber;
    }

#endregion
}