File: src\libraries\System.Private.CoreLib\src\System\Diagnostics\Tracing\TraceLogging\TraceLoggingEventSource.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\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
 
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
 
namespace System.Diagnostics.Tracing
{
    public partial class EventSource
    {
        private byte[]? m_providerMetadata;
        private protected virtual ReadOnlySpan<byte> ProviderMetadata => m_providerMetadata;
        private const string EventSourceRequiresUnreferenceMessage = "EventSource will serialize the whole object graph. Trimmer will not safely handle this case because properties may be trimmed. This can be suppressed if the object is a primitive type";
        private const string EventSourceSuppressMessage = "Parameters to this method are primitive and are trimmer safe";
 
#if FEATURE_PERFTRACING
        private readonly TraceLoggingEventHandleTable m_eventHandleTable = null!;
#endif
 
        /// <summary>
        /// Construct an EventSource with a given name for non-contract based events (e.g. those using the Write() API).
        /// </summary>
        /// <param name="eventSourceName">
        /// The name of the event source. Must not be null.
        /// </param>
        public EventSource(
            string eventSourceName)
            : this(eventSourceName, EventSourceSettings.EtwSelfDescribingEventFormat)
        { }
 
        /// <summary>
        /// Construct an EventSource with a given name for non-contract based events (e.g. those using the Write() API).
        /// </summary>
        /// <param name="eventSourceName">
        /// The name of the event source. Must not be null.
        /// </param>
        /// <param name="config">
        /// Configuration options for the EventSource as a whole.
        /// </param>
        public EventSource(
            string eventSourceName,
            EventSourceSettings config)
            : this(eventSourceName, config, null) { }
 
        /// <summary>
        /// Construct an EventSource with a given name for non-contract based events (e.g. those using the Write() API).
        ///
        /// 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="config">
        /// 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,
            EventSourceSettings config,
            params string[]? traits)
            : this(
                eventSourceName,
                GenerateGuidFromName((eventSourceName ?? throw new ArgumentNullException(nameof(eventSourceName))).ToUpperInvariant()),
                config, traits)
        {
        }
 
        /// <summary>
        /// Writes an event with no fields and default options.
        /// (Native API: EventWriteTransfer)
        /// </summary>
        /// <param name="eventName">The name of the event.</param>
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
                   Justification = EventSourceSuppressMessage)]
        public unsafe void Write(string? eventName)
        {
            if (!this.IsEnabled())
            {
                return;
            }
 
            EventSourceOptions options = default;
            this.WriteImpl(eventName, ref options, null, null, null, SimpleEventTypes<EmptyStruct>.Instance);
        }
 
        /// <summary>
        /// Writes an event with no fields.
        /// (Native API: EventWriteTransfer)
        /// </summary>
        /// <param name="eventName">The name of the event.</param>
        /// <param name="options">
        /// Options for the event, such as the level, keywords, and opcode. Unset
        /// options will be set to default values.
        /// </param>
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
                   Justification = EventSourceSuppressMessage)]
        public unsafe void Write(string? eventName, EventSourceOptions options)
        {
            if (!this.IsEnabled())
            {
                return;
            }
 
            this.WriteImpl(eventName, ref options, null, null, null, SimpleEventTypes<EmptyStruct>.Instance);
        }
 
        /// <summary>
        /// Writes an event.
        /// (Native API: EventWriteTransfer)
        /// </summary>
        /// <typeparam name="T">
        /// The type that defines the event and its payload. This must be an
        /// anonymous type or a type with an [EventData] attribute.
        /// </typeparam>
        /// <param name="eventName">
        /// The name for the event. If null, the event name is automatically
        /// determined based on T, either from the Name property of T's EventData
        /// attribute or from typeof(T).Name.
        /// </param>
        /// <param name="data">
        /// The object containing the event payload data. The type T must be
        /// an anonymous type or a type with an [EventData] attribute. The
        /// public instance properties of data will be written recursively to
        /// create the fields of the event.
        /// </param>
        [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)]
        public unsafe void Write<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(
            string? eventName,
            T data)
        {
            if (!this.IsEnabled())
            {
                return;
            }
 
            EventSourceOptions options = default;
            this.WriteImpl(eventName, ref options, data, null, null, SimpleEventTypes<T>.Instance);
        }
 
        /// <summary>
        /// Writes an event.
        /// (Native API: EventWriteTransfer)
        /// </summary>
        /// <typeparam name="T">
        /// The type that defines the event and its payload. This must be an
        /// anonymous type or a type with an [EventData] attribute.
        /// </typeparam>
        /// <param name="eventName">
        /// The name for the event. If null, the event name is automatically
        /// determined based on T, either from the Name property of T's EventData
        /// attribute or from typeof(T).Name.
        /// </param>
        /// <param name="options">
        /// Options for the event, such as the level, keywords, and opcode. Unset
        /// options will be set to default values.
        /// </param>
        /// <param name="data">
        /// The object containing the event payload data. The type T must be
        /// an anonymous type or a type with an [EventData] attribute. The
        /// public instance properties of data will be written recursively to
        /// create the fields of the event.
        /// </param>
        [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)]
        public unsafe void Write<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(
            string? eventName,
            EventSourceOptions options,
            T data)
        {
            if (!this.IsEnabled())
            {
                return;
            }
 
            this.WriteImpl(eventName, ref options, data, null, null, SimpleEventTypes<T>.Instance);
        }
 
        /// <summary>
        /// Writes an event.
        /// This overload is for use with extension methods that wish to efficiently
        /// forward the options or data parameter without performing an extra copy.
        /// (Native API: EventWriteTransfer)
        /// </summary>
        /// <typeparam name="T">
        /// The type that defines the event and its payload. This must be an
        /// anonymous type or a type with an [EventData] attribute.
        /// </typeparam>
        /// <param name="eventName">
        /// The name for the event. If null, the event name is automatically
        /// determined based on T, either from the Name property of T's EventData
        /// attribute or from typeof(T).Name.
        /// </param>
        /// <param name="options">
        /// Options for the event, such as the level, keywords, and opcode. Unset
        /// options will be set to default values.
        /// </param>
        /// <param name="data">
        /// The object containing the event payload data. The type T must be
        /// an anonymous type or a type with an [EventData] attribute. The
        /// public instance properties of data will be written recursively to
        /// create the fields of the event.
        /// </param>
        [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)]
        public unsafe void Write<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(
            string? eventName,
            ref EventSourceOptions options,
            ref T data)
        {
            if (!this.IsEnabled())
            {
                return;
            }
 
            this.WriteImpl(eventName, ref options, data, null, null, SimpleEventTypes<T>.Instance);
        }
 
        /// <summary>
        /// Writes an event.
        /// This overload is meant for clients that need to manipuate the activityId
        /// and related ActivityId for the event.
        /// </summary>
        /// <typeparam name="T">
        /// The type that defines the event and its payload. This must be an
        /// anonymous type or a type with an [EventData] attribute.
        /// </typeparam>
        /// <param name="eventName">
        /// The name for the event. If null, the event name is automatically
        /// determined based on T, either from the Name property of T's EventData
        /// attribute or from typeof(T).Name.
        /// </param>
        /// <param name="options">
        /// Options for the event, such as the level, keywords, and opcode. Unset
        /// options will be set to default values.
        /// </param>
        /// <param name="activityId">
        /// The GUID of the activity associated with this event.
        /// </param>
        /// <param name="relatedActivityId">
        /// The GUID of another activity that is related to this activity, or Guid.Empty
        /// if there is no related activity. Most commonly, the Start operation of a
        /// new activity specifies a parent activity as its related activity.
        /// </param>
        /// <param name="data">
        /// The object containing the event payload data. The type T must be
        /// an anonymous type or a type with an [EventData] attribute. The
        /// public instance properties of data will be written recursively to
        /// create the fields of the event.
        /// </param>
        [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)]
        public unsafe void Write<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(
            string? eventName,
            ref EventSourceOptions options,
            ref Guid activityId,
            ref Guid relatedActivityId,
            ref T data)
        {
            if (!this.IsEnabled())
            {
                return;
            }
 
            fixed (Guid* pActivity = &activityId, pRelated = &relatedActivityId)
            {
                this.WriteImpl(
                    eventName,
                    ref options,
                    data,
                    pActivity,
                    relatedActivityId == Guid.Empty ? null : pRelated,
                    SimpleEventTypes<T>.Instance);
            }
        }
 
        /// <summary>
        /// Writes an extended event, where the values of the event are the
        /// combined properties of any number of values. This method is
        /// intended for use in advanced logging scenarios that support a
        /// dynamic set of event context providers.
        /// This method does a quick check on whether this event is enabled.
        /// </summary>
        /// <param name="eventName">
        /// The name for the event. If null, the name from eventTypes is used.
        /// (Note that providing the event name via the name parameter is slightly
        /// less efficient than using the name from eventTypes.)
        /// </param>
        /// <param name="options">
        /// Optional overrides for the event, such as the level, keyword, opcode,
        /// activityId, and relatedActivityId. Any settings not specified by options
        /// are obtained from eventTypes.
        /// </param>
        /// <param name="eventTypes">
        /// Information about the event and the types of the values in the event.
        /// Must not be null. Note that the eventTypes object should be created once and
        /// saved. It should not be recreated for each event.
        /// </param>
        /// <param name="activityID">
        /// A pointer to the activity ID GUID to log
        /// </param>
        /// <param name="childActivityID">
        /// A pointer to the child activity ID to log (can be null) </param>
        /// <param name="values">
        /// The values to include in the event. Must not be null. The number and types of
        /// the values must match the number and types of the fields described by the
        /// eventTypes parameter.
        /// </param>
        private unsafe void WriteMultiMerge(
            string? eventName,
            ref EventSourceOptions options,
            TraceLoggingEventTypes eventTypes,
             Guid* activityID,
             Guid* childActivityID,
            params object?[] values)
        {
            if (!this.IsEnabled())
            {
                return;
            }
            byte level = (options.valuesSet & EventSourceOptions.levelSet) != 0
                ? options.level
                : eventTypes.level;
            EventKeywords keywords = (options.valuesSet & EventSourceOptions.keywordsSet) != 0
                ? options.keywords
                : eventTypes.keywords;
 
            if (this.IsEnabled((EventLevel)level, keywords))
            {
                WriteMultiMergeInner(eventName, ref options, eventTypes, activityID, childActivityID, values);
            }
        }
 
        /// <summary>
        /// Writes an extended event, where the values of the event are the
        /// combined properties of any number of values. This method is
        /// intended for use in advanced logging scenarios that support a
        /// dynamic set of event context providers.
        /// Attention: This API does not check whether the event is enabled or not.
        /// Please use WriteMultiMerge to avoid spending CPU cycles for events that are
        /// not enabled.
        /// </summary>
        /// <param name="eventName">
        /// The name for the event. If null, the name from eventTypes is used.
        /// (Note that providing the event name via the name parameter is slightly
        /// less efficient than using the name from eventTypes.)
        /// </param>
        /// <param name="options">
        /// Optional overrides for the event, such as the level, keyword, opcode,
        /// activityId, and relatedActivityId. Any settings not specified by options
        /// are obtained from eventTypes.
        /// </param>
        /// <param name="eventTypes">
        /// Information about the event and the types of the values in the event.
        /// Must not be null. Note that the eventTypes object should be created once and
        /// saved. It should not be recreated for each event.
        /// </param>
        /// <param name="activityID">
        /// A pointer to the activity ID GUID to log
        /// </param>
        /// <param name="childActivityID">
        /// A pointer to the child activity ID to log (can be null)
        /// </param>
        /// <param name="values">
        /// The values to include in the event. Must not be null. The number and types of
        /// the values must match the number and types of the fields described by the
        /// eventTypes parameter.
        /// </param>
        private unsafe void WriteMultiMergeInner(
            string? eventName,
            ref EventSourceOptions options,
            TraceLoggingEventTypes eventTypes,
            Guid* activityID,
            Guid* childActivityID,
            params object?[] values)
        {
            int identity = 0;
            byte level = (options.valuesSet & EventSourceOptions.levelSet) != 0
                ? options.level
                : eventTypes.level;
            byte opcode = (options.valuesSet & EventSourceOptions.opcodeSet) != 0
                ? options.opcode
                : eventTypes.opcode;
            EventTags tags = (options.valuesSet & EventSourceOptions.tagsSet) != 0
                ? options.tags
                : eventTypes.Tags;
            EventKeywords keywords = (options.valuesSet & EventSourceOptions.keywordsSet) != 0
                ? options.keywords
                : eventTypes.keywords;
 
            NameInfo nameInfo = eventTypes.GetNameInfo(eventName ?? eventTypes.Name, tags);
            if (nameInfo == null)
            {
                return;
            }
            identity = nameInfo.identity;
            EventDescriptor descriptor = new EventDescriptor(identity, level, opcode, (long)keywords);
 
#if FEATURE_PERFTRACING
            IntPtr eventHandle = nameInfo.GetOrCreateEventHandle(m_eventPipeProvider, m_eventHandleTable, descriptor, eventTypes);
            Debug.Assert(eventHandle != IntPtr.Zero);
#else
            IntPtr eventHandle = IntPtr.Zero;
#endif
 
            int pinCount = eventTypes.pinCount;
            byte* scratch = stackalloc byte[eventTypes.scratchSize];
            EventData* descriptors = stackalloc EventData[eventTypes.dataCount + 3];
            for (int i = 0; i < eventTypes.dataCount + 3; i++)
                descriptors[i] = default;
 
            GCHandle* pins = stackalloc GCHandle[pinCount];
            for (int i = 0; i < pinCount; i++)
                pins[i] = default;
 
            var providerMetadata = ProviderMetadata;
            fixed (byte*
                pMetadata0 = providerMetadata,
                pMetadata1 = nameInfo.nameMetadata,
                pMetadata2 = eventTypes.typeMetadata)
            {
                descriptors[0].SetMetadata(pMetadata0, providerMetadata.Length, 2);
                descriptors[1].SetMetadata(pMetadata1, nameInfo.nameMetadata.Length, 1);
                descriptors[2].SetMetadata(pMetadata2, eventTypes.typeMetadata.Length, 1);
 
                try
                {
                    DataCollector.ThreadInstance.Enable(
                        scratch,
                        eventTypes.scratchSize,
                        descriptors + 3,
                        eventTypes.dataCount,
                        pins,
                        pinCount);
 
                    for (int i = 0; i < eventTypes.typeInfos.Length; i++)
                    {
                        TraceLoggingTypeInfo info = eventTypes.typeInfos[i];
                        info.WriteData(info.PropertyValueFactory(values[i]));
                    }
 
                    this.WriteEventRaw(
                        eventName,
                        ref descriptor,
                        eventHandle,
                        activityID,
                        childActivityID,
                        (int)(DataCollector.ThreadInstance.Finish() - descriptors),
                        (IntPtr)descriptors);
                }
                finally
                {
                    WriteCleanup(pins, pinCount);
                }
            }
        }
 
        /// <summary>
        /// Writes an extended event, where the values of the event have already
        /// been serialized in "data".
        /// </summary>
        /// <param name="eventName">
        /// The name for the event. If null, the name from eventTypes is used.
        /// (Note that providing the event name via the name parameter is slightly
        /// less efficient than using the name from eventTypes.)
        /// </param>
        /// <param name="options">
        /// Optional overrides for the event, such as the level, keyword, opcode,
        /// activityId, and relatedActivityId. Any settings not specified by options
        /// are obtained from eventTypes.
        /// </param>
        /// <param name="eventTypes">
        /// Information about the event and the types of the values in the event.
        /// Must not be null. Note that the eventTypes object should be created once and
        /// saved. It should not be recreated for each event.
        /// </param>
        /// <param name="activityID">
        /// A pointer to the activity ID GUID to log
        /// </param>
        /// <param name="childActivityID">
        /// A pointer to the child activity ID to log (can be null)
        /// </param>
        /// <param name="data">
        /// The previously serialized values to include in the event. Must not be null.
        /// The number and types of the values must match the number and types of the
        /// fields described by the eventTypes parameter.
        /// </param>
        internal unsafe void WriteMultiMerge(
            string? eventName,
            ref EventSourceOptions options,
            TraceLoggingEventTypes eventTypes,
            Guid* activityID,
            Guid* childActivityID,
            EventData* data)
        {
            if (!this.IsEnabled())
            {
                return;
            }
 
            fixed (EventSourceOptions* pOptions = &options)
            {
                NameInfo? nameInfo = this.UpdateDescriptor(eventName, eventTypes, ref options, out EventDescriptor descriptor);
                if (nameInfo == null)
                {
                    return;
                }
 
#if FEATURE_PERFTRACING
                IntPtr eventHandle = nameInfo.GetOrCreateEventHandle(m_eventPipeProvider, m_eventHandleTable, descriptor, eventTypes);
                Debug.Assert(eventHandle != IntPtr.Zero);
#else
                IntPtr eventHandle = IntPtr.Zero;
#endif
 
                // We make a descriptor for each EventData, and because we morph strings to counted strings
                // we may have 2 for each arg, so we allocate enough for this.
                int descriptorsLength = eventTypes.dataCount + eventTypes.typeInfos.Length * 2 + 3;
                EventData* descriptors = stackalloc EventData[descriptorsLength];
                for (int i = 0; i < descriptorsLength; i++)
                    descriptors[i] = default;
 
                var providerMetadata = ProviderMetadata;
                fixed (byte*
                    pMetadata0 = providerMetadata,
                    pMetadata1 = nameInfo.nameMetadata,
                    pMetadata2 = eventTypes.typeMetadata)
                {
                    descriptors[0].SetMetadata(pMetadata0, providerMetadata.Length, 2);
                    descriptors[1].SetMetadata(pMetadata1, nameInfo.nameMetadata.Length, 1);
                    descriptors[2].SetMetadata(pMetadata2, eventTypes.typeMetadata.Length, 1);
                    int numDescrs = 3;
 
                    for (int i = 0; i < eventTypes.typeInfos.Length; i++)
                    {
                        descriptors[numDescrs].m_Ptr = data[i].m_Ptr;
                        descriptors[numDescrs].m_Size = data[i].m_Size;
 
                        // old conventions for bool is 4 bytes, but meta-data assumes 1.
                        if (data[i].m_Size == 4 && eventTypes.typeInfos[i].DataType == typeof(bool))
                            descriptors[numDescrs].m_Size = 1;
 
                        numDescrs++;
                    }
 
                    this.WriteEventRaw(
                        eventName,
                        ref descriptor,
                        eventHandle,
                        activityID,
                        childActivityID,
                        numDescrs,
                        (IntPtr)descriptors);
                }
            }
        }
 
        private unsafe void WriteImpl(
            string? eventName,
            ref EventSourceOptions options,
            object? data,
            Guid* pActivityId,
            Guid* pRelatedActivityId,
            TraceLoggingEventTypes eventTypes)
        {
            try
            {
                fixed (EventSourceOptions* pOptions = &options)
                {
                    options.Opcode = options.IsOpcodeSet ? options.Opcode : GetOpcodeWithDefault(options.Opcode, eventName);
                    NameInfo? nameInfo = this.UpdateDescriptor(eventName, eventTypes, ref options, out EventDescriptor descriptor);
                    if (nameInfo == null)
                    {
                        return;
                    }
 
#if FEATURE_PERFTRACING
                    IntPtr eventHandle = nameInfo.GetOrCreateEventHandle(m_eventPipeProvider, m_eventHandleTable, descriptor, eventTypes);
                    Debug.Assert(eventHandle != IntPtr.Zero);
#else
                    IntPtr eventHandle = IntPtr.Zero;
#endif
 
                    int pinCount = eventTypes.pinCount;
                    byte* scratch = stackalloc byte[eventTypes.scratchSize];
                    EventData* descriptors = stackalloc EventData[eventTypes.dataCount + 3];
                    for (int i = 0; i < eventTypes.dataCount + 3; i++)
                        descriptors[i] = default;
 
                    GCHandle* pins = stackalloc GCHandle[pinCount];
                    for (int i = 0; i < pinCount; i++)
                        pins[i] = default;
 
                    var providerMetadata = ProviderMetadata;
                    fixed (byte*
                        pMetadata0 = providerMetadata,
                        pMetadata1 = nameInfo.nameMetadata,
                        pMetadata2 = eventTypes.typeMetadata)
                    {
                        descriptors[0].SetMetadata(pMetadata0, providerMetadata.Length, 2);
                        descriptors[1].SetMetadata(pMetadata1, nameInfo.nameMetadata.Length, 1);
                        descriptors[2].SetMetadata(pMetadata2, eventTypes.typeMetadata.Length, 1);
 
                        EventOpcode opcode = (EventOpcode)descriptor.Opcode;
 
                        Guid activityId = Guid.Empty;
                        Guid relatedActivityId = Guid.Empty;
                        if (pActivityId == null && pRelatedActivityId == null &&
                           ((options.ActivityOptions & EventActivityOptions.Disable) == 0))
                        {
                            if (opcode == EventOpcode.Start)
                            {
                                Debug.Assert(eventName != null, "GetOpcodeWithDefault should not returned Start when eventName is null");
                                m_activityTracker.OnStart(m_name, eventName, 0, ref activityId, ref relatedActivityId, options.ActivityOptions);
                            }
                            else if (opcode == EventOpcode.Stop)
                            {
                                Debug.Assert(eventName != null, "GetOpcodeWithDefault should not returned Stop when eventName is null");
                                m_activityTracker.OnStop(m_name, eventName, 0, ref activityId);
                            }
                            if (activityId != Guid.Empty)
                                pActivityId = &activityId;
                            if (relatedActivityId != Guid.Empty)
                                pRelatedActivityId = &relatedActivityId;
                        }
 
                        try
                        {
                            DataCollector.ThreadInstance.Enable(
                                scratch,
                                eventTypes.scratchSize,
                                descriptors + 3,
                                eventTypes.dataCount,
                                pins,
                                pinCount);
 
                            TraceLoggingTypeInfo info = eventTypes.typeInfos[0];
                            info.WriteData(info.PropertyValueFactory(data));
 
                            this.WriteEventRaw(
                                eventName,
                                ref descriptor,
                                eventHandle,
                                pActivityId,
                                pRelatedActivityId,
                                (int)(DataCollector.ThreadInstance.Finish() - descriptors),
                                (IntPtr)descriptors);
 
                            // TODO enable filtering for listeners.
                            if (m_Dispatchers != null)
                            {
                                var eventData = (EventPayload?)(eventTypes.typeInfos[0].GetData(data));
                                WriteToAllListeners(eventName, ref descriptor, nameInfo.tags, pActivityId, pRelatedActivityId, eventData);
                            }
                        }
                        catch (Exception ex)
                        {
                            if (ex is EventSourceException)
                                throw;
                            else
                                ThrowEventSourceException(eventName, ex);
                        }
                        finally
                        {
                            WriteCleanup(pins, pinCount);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                if (ex is EventSourceException)
                    throw;
                else
                    ThrowEventSourceException(eventName, ex);
            }
        }
 
        private unsafe void WriteToAllListeners(string? eventName, ref EventDescriptor eventDescriptor, EventTags tags, Guid* pActivityId, Guid* pChildActivityId, EventPayload? payload)
        {
            // Self described events do not have an id attached. We mark it internally with -1.
            var eventCallbackArgs = new EventWrittenEventArgs(this, -1, pActivityId, pChildActivityId)
            {
                EventName = eventName,
                Level = (EventLevel)eventDescriptor.Level,
                Keywords = (EventKeywords)eventDescriptor.Keywords,
                Opcode = (EventOpcode)eventDescriptor.Opcode,
                Tags = tags
            };
 
            if (payload != null)
            {
                eventCallbackArgs.Payload = new ReadOnlyCollection<object?>((IList<object?>)payload.Values);
                eventCallbackArgs.PayloadNames = new ReadOnlyCollection<string>((IList<string>)payload.Keys);
            }
 
            DispatchToAllListeners(eventCallbackArgs);
        }
 
        [NonEvent]
        private static unsafe void WriteCleanup(GCHandle* pPins, int cPins)
        {
            DataCollector.ThreadInstance.Disable();
 
            for (int i = 0; i < cPins; i++)
            {
                if (pPins[i].IsAllocated)
                {
                    pPins[i].Free();
                }
            }
        }
 
        private void InitializeProviderMetadata()
        {
            bool hasProviderMetadata = ProviderMetadata.Length > 0;
#if !DEBUG
            if (hasProviderMetadata)
            {
                // Already set
                return;
            }
#endif
            if (m_traits != null)
            {
                List<byte> traitMetaData = new List<byte>(100);
                for (int i = 0; i < m_traits.Length - 1; i += 2)
                {
                    if (m_traits[i].StartsWith("ETW_", StringComparison.Ordinal))
                    {
                        ReadOnlySpan<char> etwTrait = m_traits[i].AsSpan(4);
                        if (!byte.TryParse(etwTrait, out byte traitNum))
                        {
                            if (etwTrait is "GROUP")
                            {
                                traitNum = 1;
                            }
                            else
                            {
                                throw new ArgumentException(SR.Format(SR.EventSource_UnknownEtwTrait, etwTrait.ToString()), "traits");
                            }
                        }
                        string value = m_traits[i + 1];
                        int lenPos = traitMetaData.Count;
                        traitMetaData.Add(0);                                           // Emit size (to be filled in later)
                        traitMetaData.Add(0);
                        traitMetaData.Add(traitNum);                                    // Emit Trait number
                        int valueLen = AddValueToMetaData(traitMetaData, value) + 3;    // Emit the value bytes +3 accounts for 3 bytes we emitted above.
                        traitMetaData[lenPos] = unchecked((byte)valueLen);              // Fill in size
                        traitMetaData[lenPos + 1] = unchecked((byte)(valueLen >> 8));
                    }
                }
                byte[] providerMetadata = Statics.MetadataForString(this.Name, 0, traitMetaData.Count, 0);
                int startPos = providerMetadata.Length - traitMetaData.Count;
                foreach (byte b in traitMetaData)
                {
                    providerMetadata[startPos++] = b;
                }
 
                m_providerMetadata = providerMetadata;
            }
            else
            {
                m_providerMetadata = Statics.MetadataForString(this.Name, 0, 0, 0);
            }
 
#if DEBUG
            if (hasProviderMetadata)
            {
                // Validate the provided ProviderMetadata still matches in debug
                Debug.Assert(ProviderMetadata.SequenceEqual(m_providerMetadata));
            }
#endif
        }
 
        private static int AddValueToMetaData(List<byte> metaData, string value)
        {
            if (value.Length == 0)
                return 0;
 
            int startPos = metaData.Count;
            char firstChar = value[0];
 
            if (firstChar == '@')
                metaData.AddRange(Encoding.UTF8.GetBytes(value.Substring(1)));
            else if (firstChar == '{')
                metaData.AddRange(new Guid(value).ToByteArray());
            else if (firstChar == '#')
            {
                for (int i = 1; i < value.Length; i++)
                {
                    if (value[i] != ' ')        // Skip spaces between bytes.
                    {
                        if (!(i + 1 < value.Length))
                        {
                            throw new ArgumentException(SR.EventSource_EvenHexDigits, "traits");
                        }
                        metaData.Add((byte)(HexDigit(value[i]) * 16 + HexDigit(value[i + 1])));
                        i++;
                    }
                }
            }
            else if ('A' <= firstChar || ' ' == firstChar)  // Is it alphabetic or space (excludes digits and most punctuation).
            {
                metaData.AddRange(Encoding.UTF8.GetBytes(value));
            }
            else
            {
                throw new ArgumentException(SR.Format(SR.EventSource_IllegalValue, value), "traits");
            }
 
            return metaData.Count - startPos;
        }
 
        /// <summary>
        /// Returns a value 0-15 if 'c' is a hexadecimal digit.   If  it throws an argument exception.
        /// </summary>
        private static int HexDigit(char c)
        {
            if ('0' <= c && c <= '9')
            {
                return c - '0';
            }
            if ('a' <= c)
            {
                c = unchecked((char)(c - ('a' - 'A')));        // Convert to lower case
            }
            if ('A' <= c && c <= 'F')
            {
                return c - 'A' + 10;
            }
 
            throw new ArgumentException(SR.Format(SR.EventSource_BadHexDigit, c), "traits");
        }
 
        private NameInfo? UpdateDescriptor(
            string? name,
            TraceLoggingEventTypes eventInfo,
            ref EventSourceOptions options,
            out EventDescriptor descriptor)
        {
            NameInfo? nameInfo = null;
            int identity = 0;
            byte level = (options.valuesSet & EventSourceOptions.levelSet) != 0
                ? options.level
                : eventInfo.level;
            byte opcode = (options.valuesSet & EventSourceOptions.opcodeSet) != 0
                ? options.opcode
                : eventInfo.opcode;
            EventTags tags = (options.valuesSet & EventSourceOptions.tagsSet) != 0
                ? options.tags
                : eventInfo.Tags;
            EventKeywords keywords = (options.valuesSet & EventSourceOptions.keywordsSet) != 0
                ? options.keywords
                : eventInfo.keywords;
 
            if (this.IsEnabled((EventLevel)level, keywords))
            {
                nameInfo = eventInfo.GetNameInfo(name ?? eventInfo.Name, tags);
                identity = nameInfo.identity;
            }
 
            descriptor = new EventDescriptor(identity, level, opcode, (long)keywords);
            return nameInfo;
        }
    }
}