File: src\libraries\System.Private.CoreLib\src\System\Diagnostics\Tracing\TraceLogging\XplatEventLogger.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.
 
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
 
#if FEATURE_EVENTSOURCE_XPLAT
 
namespace System.Diagnostics.Tracing
{
    internal sealed partial class XplatEventLogger : EventListener
    {
        public XplatEventLogger() { }
 
        private static readonly string s_eventSourceNameFilter = GetClrConfig("EventSourceFilter");
        private static readonly string s_eventSourceEventFilter = GetClrConfig("EventNameFilter");
 
        private static unsafe string GetClrConfig(string configName) => new string(EventSource_GetClrConfig(configName));
 
        [LibraryImport(RuntimeHelpers.QCall, StringMarshalling = StringMarshalling.Utf16)]
        private static unsafe partial char* EventSource_GetClrConfig(string configName);
 
        private static bool initializedPersistentListener;
 
        public static EventListener? InitializePersistentListener()
        {
            try
            {
                if (!initializedPersistentListener && XplatEventLogger.IsEventSourceLoggingEnabled())
                {
                    initializedPersistentListener = true;
                    return new XplatEventLogger();
                }
            }
            catch (Exception) { }
 
            return null;
        }
 
        [LibraryImport(RuntimeHelpers.QCall)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static partial bool IsEventSourceLoggingEnabled();
 
        [LibraryImport(RuntimeHelpers.QCall, StringMarshalling = StringMarshalling.Utf16)]
        private static partial void LogEventSource(int eventID, string? eventName, string eventSourceName, string payload);
 
        private static readonly List<char> escape_seq = new List<char> { '\b', '\f', '\n', '\r', '\t', '\"', '\\' };
        private static readonly Dictionary<char, string> seq_mapping = new Dictionary<char, string>()
        {
            {'\b', "b"},
            {'\f', "f"},
            {'\n', "n"},
            {'\r', "r"},
            {'\t', "t"},
            {'\"', "\\\""},
            {'\\', "\\\\"}
        };
 
        private static void MinimalJsonserializer(string payload, ref ValueStringBuilder sb)
        {
            foreach (var elem in payload)
            {
                if (escape_seq.Contains(elem))
                {
                    sb.Append("\\\\");
                    sb.Append(seq_mapping[elem]);
                }
                else
                {
                    sb.Append(elem);
                }
            }
        }
 
        private static string Serialize(ReadOnlyCollection<string>? payloadName, ReadOnlyCollection<object?>? payload, string? eventMessage)
        {
            if (payloadName == null || payload == null)
                return string.Empty;
 
            if (payloadName.Count == 0 || payload.Count == 0)
                return string.Empty;
 
            int eventDataCount = payloadName.Count;
 
            if (payloadName.Count != payload.Count)
            {
                eventDataCount = Math.Min(payloadName.Count, payload.Count);
            }
 
            var sb = new ValueStringBuilder(stackalloc char[256]);
 
            sb.Append('{');
 
            // If the event has a message, send that as well as a pseudo-field
            if (!string.IsNullOrEmpty(eventMessage))
            {
                sb.Append("\\\"EventSource_Message\\\":\\\"");
                MinimalJsonserializer(eventMessage, ref sb);
                sb.Append("\\\"");
                if (eventDataCount != 0)
                    sb.Append(", ");
            }
 
            for (int i = 0; i < eventDataCount; i++)
            {
                if (i != 0)
                    sb.Append(", ");
 
                var fieldstr = payloadName[i].ToString();
 
                sb.Append("\\\"");
                sb.Append(fieldstr);
                sb.Append("\\\"");
                sb.Append(':');
 
                switch (payload[i])
                {
                    case string str:
                    {
                        sb.Append("\\\"");
                        MinimalJsonserializer(str, ref sb);
                        sb.Append("\\\"");
                        break;
                    }
                    case byte[] byteArr:
                    {
                        sb.Append("\\\"");
                        AppendByteArrayAsHexString(ref sb, byteArr);
                        sb.Append("\\\"");
                        break;
                    }
                    default:
                    {
                        if (payload[i] is object o)
                        {
                            sb.Append(o.ToString());
                        }
                        break;
                    }
                }
            }
            sb.Append('}');
            return sb.ToString();
        }
 
        private static void AppendByteArrayAsHexString(ref ValueStringBuilder builder, byte[] byteArray)
        {
            Debug.Assert(byteArray != null);
 
            HexConverter.EncodeToUtf16(byteArray, builder.AppendSpan(byteArray.Length * 2));
        }
 
        protected internal override void OnEventSourceCreated(EventSource eventSource)
        {
            if (string.IsNullOrEmpty(s_eventSourceNameFilter) || (eventSource.Name.Contains(s_eventSourceNameFilter, StringComparison.OrdinalIgnoreCase)))
            {
                EnableEvents(eventSource, EventLevel.LogAlways, EventKeywords.All, null);
            }
        }
 
        protected internal override void OnEventWritten(EventWrittenEventArgs eventData)
        {
            // Don't enable forwarding of NativeRuntimeEventSource events.
            // NativeRuntimeEventSource events are written to the native side via QCalls (see NativeRuntimeEventSource.cs/nativeeventsource.cpp)
            // and is forwarded back to this EventListener via NativeRuntimeEventSource.ProcessEvent. We need to filter these events here
            // because then these events can get logged back into the native runtime as EventSourceEvent events which we use to log
            // managed events from other EventSources to LTTng.
            if (eventData.EventSource.GetType() == typeof(NativeRuntimeEventSource))
            {
                return;
            }
 
            if (string.IsNullOrEmpty(s_eventSourceEventFilter) || (eventData.EventName!.Contains(s_eventSourceEventFilter, StringComparison.OrdinalIgnoreCase)))
            {
                LogOnEventWritten(eventData);
            }
        }
 
        private static void LogOnEventWritten(EventWrittenEventArgs eventData)
        {
            string payload = "";
            if (eventData.Payload != null)
            {
                try
                {
                    payload = Serialize(eventData.PayloadNames, eventData.Payload, eventData.Message);
                }
                catch (Exception ex)
                {
                    payload = "XplatEventLogger failed with Exception " + ex.ToString();
                }
            }
 
            LogEventSource(eventData.EventId, eventData.EventName, eventData.EventSource.Name, payload);
        }
    }
}
#endif //FEATURE_EVENTSOURCE_XPLAT