// 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 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)] 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)] 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); } } } } |