File: src\libraries\Common\src\System\Net\Logging\NetEventSource.Common.cs
Web Access
Project: src\src\libraries\System.Net.Mail\src\System.Net.Mail.csproj (System.Net.Mail)
// 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;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
 
#pragma warning disable CA1823 // not all IDs are used by all partial providers
 
namespace System.Net
{
    // Implementation:
    // This partial file is meant to be consumed into each System.Net.* assembly that needs to log.  Each such assembly also provides
    // its own NetEventSource partial class that adds an appropriate [EventSource] attribute, giving it a unique name for that assembly.
    // Those partials can then also add additional events if needed, starting numbering from the NextAvailableEventId defined by this partial.
 
    // Usage:
    // - Operations that may allocate (e.g. boxing a value type, using string interpolation, etc.) or that may have computations
    //   at call sites should guard access like:
    //       if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"Found certificate: {cert}"); // info logging with a formattable string
    // - Operations that have zero allocations / measurable computations at call sites can use a simpler pattern, calling methods like:
    //       NetEventSource.Info(this, "literal string");  // arbitrary message with a literal string
    //   Debug.Asserts inside the logging methods will help to flag some misuse if the DEBUG_NETEVENTSOURCE_MISUSE compilation constant is defined.
    //   However, because it can be difficult by observation to understand all of the costs involved, guarding can be done everywhere.
    // - Messages can be strings, formattable strings, or any other object.  Objects (including those used in formattable strings) have special
    //   formatting applied, controlled by the Format method.  Partial specializations can also override this formatting by implementing a partial
    //   method that takes an object and optionally provides a string representation of it, in case a particular library wants to customize further.
 
    /// <summary>Provides logging facilities for System.Net libraries.</summary>
    internal sealed partial class NetEventSource : EventSource
    {
        private const string EventSourceSuppressMessage = "Parameters to this method are primitive and are trimmer safe";
 
        /// <summary>The single event source instance to use for all logging.</summary>
        public static readonly NetEventSource Log = new NetEventSource();
 
        #region Metadata
        public static class Keywords
        {
            public const EventKeywords Default = (EventKeywords)0x0001;
            public const EventKeywords Debug = (EventKeywords)0x0002;
        }
 
        private const string MissingMember = "(?)";
        private const string NullInstance = "(null)";
        private const string StaticMethodObject = "(static)";
        private const string NoParameters = "";
 
        private const int InfoEventId = 1;
        private const int ErrorEventId = 2;
        // private const int AssociateEventId = 3; // Defined in NetEventSource.Common.Associate.cs
        // private const int DumpArrayEventId = 4; // Defined in NetEventSource.Common.DumpBuffer.cs
 
        private const int NextAvailableEventId = 5; // Update this value whenever new events are added.  Derived types should base all events off of this to avoid conflicts.
        #endregion
 
        #region Info
        /// <summary>Logs an information message.</summary>
        /// <param name="thisOrContextObject">`this`, or another object that serves to provide context for the operation.</param>
        /// <param name="formattableString">The message to be logged.</param>
        /// <param name="memberName">The calling member.</param>
        [NonEvent]
        public static void Info(object? thisOrContextObject, FormattableString? formattableString = null, [CallerMemberName] string? memberName = null) =>
            Log.Info(IdOf(thisOrContextObject), memberName, formattableString != null ? Format(formattableString) : NoParameters);
 
        /// <summary>Logs an information message.</summary>
        /// <param name="thisOrContextObject">`this`, or another object that serves to provide context for the operation.</param>
        /// <param name="message">The message to be logged.</param>
        /// <param name="memberName">The calling member.</param>
        [NonEvent]
        public static void Info(object? thisOrContextObject, object? message, [CallerMemberName] string? memberName = null) =>
            Log.Info(IdOf(thisOrContextObject), memberName, Format(message));
 
        [Event(InfoEventId, Level = EventLevel.Informational, Keywords = Keywords.Default)]
        private void Info(string thisOrContextObject, string? memberName, string? message)
        {
            Debug.Assert(IsEnabled());
            WriteEvent(InfoEventId, thisOrContextObject, memberName ?? MissingMember, message);
        }
        #endregion
 
        #region Error
        /// <summary>Logs an error message.</summary>
        /// <param name="thisOrContextObject">`this`, or another object that serves to provide context for the operation.</param>
        /// <param name="formattableString">The message to be logged.</param>
        /// <param name="memberName">The calling member.</param>
        [NonEvent]
        public static void Error(object? thisOrContextObject, FormattableString formattableString, [CallerMemberName] string? memberName = null) =>
            Log.ErrorMessage(IdOf(thisOrContextObject), memberName, Format(formattableString));
 
        /// <summary>Logs an error message.</summary>
        /// <param name="thisOrContextObject">`this`, or another object that serves to provide context for the operation.</param>
        /// <param name="message">The message to be logged.</param>
        /// <param name="memberName">The calling member.</param>
        [NonEvent]
        public static void Error(object? thisOrContextObject, object message, [CallerMemberName] string? memberName = null) =>
            Log.ErrorMessage(IdOf(thisOrContextObject), memberName, Format(message));
 
        [Event(ErrorEventId, Level = EventLevel.Error, Keywords = Keywords.Default)]
        private void ErrorMessage(string thisOrContextObject, string? memberName, string? message)
        {
            Debug.Assert(IsEnabled());
            WriteEvent(ErrorEventId, thisOrContextObject, memberName ?? MissingMember, message);
        }
        #endregion
 
        #region Helpers
        [NonEvent]
        public static string IdOf(object? value) => value != null ? value.GetType().Name + "#" + GetHashCode(value) : NullInstance;
 
        [NonEvent]
        public static int GetHashCode(object? value) => value?.GetHashCode() ?? 0;
 
        [NonEvent]
        public static string? Format(object? value)
        {
            // If it's null, return a known string for null values
            if (value == null)
            {
                return NullInstance;
            }
 
            // Give another partial implementation a chance to provide its own string representation
            string? result = null;
            AdditionalCustomizedToString(value, ref result);
            if (result is not null)
            {
                return result;
            }
 
            // Format arrays with their element type name and length
            if (value is Array arr)
            {
                return $"{arr.GetType().GetElementType()}[{((Array)value).Length}]";
            }
 
            // Format ICollections as the name and count
            if (value is ICollection c)
            {
                return $"{c.GetType().Name}({c.Count})";
            }
 
            // Format SafeHandles as their type, hash code, and pointer value
            if (value is SafeHandle handle)
            {
                return $"{handle.GetType().Name}:{handle.GetHashCode()}(0x{handle.DangerousGetHandle():X})";
            }
 
            // Format IntPtrs as hex
            if (value is IntPtr)
            {
                return $"0x{value:X}";
            }
 
            // If the string representation of the instance would just be its type name,
            // use its id instead.
            string? toString = value.ToString();
            if (toString == null || toString == value.GetType().FullName)
            {
                return IdOf(value);
            }
 
            // Otherwise, return the original object so that the caller does default formatting.
            return value.ToString();
        }
 
        [NonEvent]
        private static string Format(FormattableString s)
        {
            switch (s.ArgumentCount)
            {
                case 0: return s.Format;
                case 1: return string.Format(s.Format, Format(s.GetArgument(0)));
                case 2: return string.Format(s.Format, Format(s.GetArgument(0)), Format(s.GetArgument(1)));
                case 3: return string.Format(s.Format, Format(s.GetArgument(0)), Format(s.GetArgument(1)), Format(s.GetArgument(2)));
                default:
                    string?[] formattedArgs = new string?[s.ArgumentCount];
                    for (int i = 0; i < formattedArgs.Length; i++)
                    {
                        formattedArgs[i] = Format(s.GetArgument(i));
                    }
                    return string.Format(s.Format, formattedArgs);
            }
        }
 
        static partial void AdditionalCustomizedToString(object value, ref string? result);
        #endregion
 
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
                   Justification = EventSourceSuppressMessage)]
        [NonEvent]
        private unsafe void WriteEvent(int eventId, string? arg1, string? arg2, int arg3)
        {
            arg1 ??= "";
            arg2 ??= "";
 
            fixed (char* arg1Ptr = arg1)
            fixed (char* arg2Ptr = arg2)
            {
                const int NumEventDatas = 3;
                EventData* descrs = stackalloc EventData[NumEventDatas];
 
                descrs[0] = new EventData
                {
                    DataPointer = (IntPtr)(arg1Ptr),
                    Size = (arg1.Length + 1) * sizeof(char)
                };
                descrs[1] = new EventData
                {
                    DataPointer = (IntPtr)(arg2Ptr),
                    Size = (arg2.Length + 1) * sizeof(char)
                };
                descrs[2] = new EventData
                {
                    DataPointer = (IntPtr)(&arg3),
                    Size = sizeof(int)
                };
 
                WriteEventCore(eventId, NumEventDatas, descrs);
            }
        }
    }
}