// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// ReSharper disable RedundantUsingDirective
// ReSharper disable RedundantExtendsListEntry
// ReSharper disable UnusedMember.Local
// ReSharper disable InterpolatedStringExpressionIsNotIFormattable
// ReSharper disable ConditionIsAlwaysTrueOrFalse
// ReSharper disable PartialMethodWithSinglePart
#if DEBUG
// Uncomment to enable runtime checks to help validate that NetEventSource isn't being misused
// in a way that will cause performance problems, e.g. unexpected boxing of value types.
//#define DEBUG_NETEVENTSOURCE_MISUSE
#endif
#nullable enable
using System.Collections;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if NET46
using System.Security;
#endif
#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.
/// Provides logging facilities for System.Net libraries.
#if NET46
[SecuritySafeCritical]
#endif
internal sealed partial class NetEventSource : EventSource
{
/// The single event source instance to use for all logging.
public static readonly NetEventSource Log = new NetEventSource();
#region Metadata
public class Keywords
{
public const EventKeywords Default = (EventKeywords)0x0001;
public const EventKeywords Debug = (EventKeywords)0x0002;
// No longer used:
// EnterExit = (EventKeywords)0x0004;
}
private const string MissingMember = "(?)";
private const string NullInstance = "(null)";
private const string StaticMethodObject = "(static)";
private const string NoParameters = "";
private const int MaxDumpSize = 1024;
// No longer used:
// EnterEventId = 1;
// ExitEventId = 2;
private const int AssociateEventId = 3;
private const int InfoEventId = 4;
private const int ErrorEventId = 5;
private const int DumpArrayEventId = 7;
// These events are implemented in NetEventSource.Security.cs.
// Define the ids here so that projects that include NetEventSource.Security.cs will not have conflicts.
private const int EnumerateSecurityPackagesId = 8;
private const int SspiPackageNotFoundId = 9;
private const int AcquireDefaultCredentialId = 10;
private const int AcquireCredentialsHandleId = 11;
private const int InitializeSecurityContextId = 12;
private const int SecurityContextInputBufferId = 13;
private const int SecurityContextInputBuffersId = 14;
private const int AcceptSecuritContextId = 15;
private const int OperationReturnedSomethingId = 16;
private const int NextAvailableEventId = 17; // Update this value whenever new events are added. Derived types should base all events off of this to avoid conflicts.
#endregion
#region Events
#region Info
/// Logs an information message.
/// `this`, or another object that serves to provide context for the operation.
/// The message to be logged.
/// The calling member.
[NonEvent]
public static void Info(object? thisOrContextObject, FormattableString? formattableString = null, [CallerMemberName] string? memberName = null)
{
DebugValidateArg(thisOrContextObject);
DebugValidateArg(formattableString);
if (IsEnabled) Log.Info(IdOf(thisOrContextObject), memberName, formattableString != null ? Format(formattableString) : NoParameters);
}
/// Logs an information message.
/// `this`, or another object that serves to provide context for the operation.
/// The message to be logged.
/// The calling member.
[NonEvent]
public static void Info(object? thisOrContextObject, object? message, [CallerMemberName] string? memberName = null)
{
DebugValidateArg(thisOrContextObject);
DebugValidateArg(message);
if (IsEnabled) Log.Info(IdOf(thisOrContextObject), memberName, Format(message).ToString());
}
[Event(InfoEventId, Level = EventLevel.Informational, Keywords = Keywords.Default)]
private void Info(string thisOrContextObject, string? memberName, string? message) =>
WriteEvent(InfoEventId, thisOrContextObject, memberName ?? MissingMember, message);
#endregion
#region Error
/// Logs an error message.
/// `this`, or another object that serves to provide context for the operation.
/// The message to be logged.
/// The calling member.
[NonEvent]
public static void Error(object? thisOrContextObject, FormattableString formattableString, [CallerMemberName] string? memberName = null)
{
DebugValidateArg(thisOrContextObject);
DebugValidateArg(formattableString);
if (IsEnabled) Log.ErrorMessage(IdOf(thisOrContextObject), memberName, Format(formattableString));
}
/// Logs an error message.
/// `this`, or another object that serves to provide context for the operation.
/// The message to be logged.
/// The calling member.
[NonEvent]
public static void Error(object? thisOrContextObject, object message, [CallerMemberName] string? memberName = null)
{
DebugValidateArg(thisOrContextObject);
DebugValidateArg(message);
if (IsEnabled) Log.ErrorMessage(IdOf(thisOrContextObject), memberName, Format(message).ToString());
}
[Event(ErrorEventId, Level = EventLevel.Error, Keywords = Keywords.Default)]
private void ErrorMessage(string thisOrContextObject, string? memberName, string? message) =>
WriteEvent(ErrorEventId, thisOrContextObject, memberName ?? MissingMember, message);
#endregion
#region Verbose
/// Logs an info message at verbose mode.
/// `this`, or another object that serves to provide context for the operation.
/// The message to be logged.
/// The calling member.
[NonEvent]
public static void Verbose(object? thisOrContextObject, FormattableString formattableString, [CallerMemberName] string? memberName = null)
{
DebugValidateArg(thisOrContextObject);
DebugValidateArg(formattableString);
if (IsEnabled) Log.ErrorMessage(IdOf(thisOrContextObject), memberName, Format(formattableString));
}
/// Logs an info at verbose mode.
/// `this`, or another object that serves to provide context for the operation.
/// The message to be logged.
/// The calling member.
[NonEvent]
public static void Verbose(object? thisOrContextObject, object message, [CallerMemberName] string? memberName = null)
{
DebugValidateArg(thisOrContextObject);
DebugValidateArg(message);
if (IsEnabled) Log.VerboseMessage(IdOf(thisOrContextObject), memberName, Format(message).ToString());
}
[Event(ErrorEventId, Level = EventLevel.Verbose, Keywords = Keywords.Default)]
private void VerboseMessage(string thisOrContextObject, string? memberName, string? message) =>
WriteEvent(ErrorEventId, thisOrContextObject, memberName ?? MissingMember, message);
#endregion
#region DumpBuffer
/// Logs the contents of a buffer.
/// `this`, or another object that serves to provide context for the operation.
/// The buffer to be logged.
/// The calling member.
[NonEvent]
public static void DumpBuffer(object? thisOrContextObject, byte[] buffer, [CallerMemberName] string? memberName = null)
{
DumpBuffer(thisOrContextObject, buffer, 0, buffer.Length, memberName);
}
/// Logs the contents of a buffer.
/// `this`, or another object that serves to provide context for the operation.
/// The buffer to be logged.
/// The starting offset from which to log.
/// The number of bytes to log.
/// The calling member.
[NonEvent]
public static void DumpBuffer(object? thisOrContextObject, byte[] buffer, int offset, int count, [CallerMemberName] string? memberName = null)
{
if (IsEnabled && offset >= 0 && offset <= buffer.Length - count)
{
count = Math.Min(count, MaxDumpSize);
byte[] slice = buffer;
if (offset != 0 || count != buffer.Length)
{
slice = new byte[count];
Buffer.BlockCopy(buffer, offset, slice, 0, count);
}
Log.DumpBuffer(IdOf(thisOrContextObject), memberName, slice);
}
}
/// Logs the contents of a buffer.
/// `this`, or another object that serves to provide context for the operation.
/// The starting location of the buffer to be logged.
/// The number of bytes to log.
/// The calling member.
[NonEvent]
public static unsafe void DumpBuffer(object? thisOrContextObject, IntPtr bufferPtr, int count, [CallerMemberName] string? memberName = null)
{
Debug.Assert(bufferPtr != IntPtr.Zero);
Debug.Assert(count >= 0);
if (IsEnabled)
{
var buffer = new byte[Math.Min(count, MaxDumpSize)];
fixed (byte* targetPtr = buffer)
{
Buffer.MemoryCopy((byte*)bufferPtr, targetPtr, buffer.Length, buffer.Length);
}
Log.DumpBuffer(IdOf(thisOrContextObject), memberName, buffer);
}
}
[Event(DumpArrayEventId, Level = EventLevel.Verbose, Keywords = Keywords.Debug)]
private void DumpBuffer(string thisOrContextObject, string? memberName, byte[] buffer) =>
WriteEvent(DumpArrayEventId, thisOrContextObject, memberName ?? MissingMember, buffer);
#endregion
#region Associate
/// Logs a relationship between two objects.
/// The first object.
/// The second object.
/// The calling member.
[NonEvent]
public static void Associate(object first, object second, [CallerMemberName] string? memberName = null)
{
DebugValidateArg(first);
DebugValidateArg(second);
if (IsEnabled) Log.Associate(IdOf(first), memberName, IdOf(first), IdOf(second));
}
/// Logs a relationship between two objects.
/// `this`, or another object that serves to provide context for the operation.
/// The first object.
/// The second object.
/// The calling member.
[NonEvent]
public static void Associate(object? thisOrContextObject, object first, object second, [CallerMemberName] string? memberName = null)
{
DebugValidateArg(thisOrContextObject);
DebugValidateArg(first);
DebugValidateArg(second);
if (IsEnabled) Log.Associate(IdOf(thisOrContextObject), memberName, IdOf(first), IdOf(second));
}
[Event(AssociateEventId, Level = EventLevel.Informational, Keywords = Keywords.Default, Message = "[{2}]<-->[{3}]")]
private void Associate(string thisOrContextObject, string? memberName, string first, string second) =>
WriteEvent(AssociateEventId, thisOrContextObject, memberName ?? MissingMember, first, second);
#endregion
#endregion
#region Helpers
[Conditional("DEBUG_NETEVENTSOURCE_MISUSE")]
private static void DebugValidateArg(object? arg)
{
if (!IsEnabled)
{
Debug.Assert(!(arg is ValueType), $"Should not be passing value type {arg?.GetType()} to logging without IsEnabled check");
Debug.Assert(!(arg is FormattableString), $"Should not be formatting FormattableString \"{arg}\" if tracing isn't enabled");
}
}
[Conditional("DEBUG_NETEVENTSOURCE_MISUSE")]
private static void DebugValidateArg(FormattableString? arg)
{
Debug.Assert(IsEnabled || arg == null, $"Should not be formatting FormattableString \"{arg}\" if tracing isn't enabled");
}
public static new bool IsEnabled =>
Log.IsEnabled();
[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 object 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 != 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;
}
[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:
object?[] args = s.GetArguments();
object[] formattedArgs = new object[args.Length];
for (int i = 0; i < args.Length; i++)
{
formattedArgs[i] = Format(args[i]);
}
return string.Format(s.Format, formattedArgs);
}
}
static partial void AdditionalCustomizedToString(T value, ref string? result);
#endregion
#region Custom WriteEvent overloads
[NonEvent]
private unsafe void WriteEvent(int eventId, string? arg1, string? arg2, string? arg3, string? arg4)
{
if (IsEnabled())
{
if (arg1 == null) arg1 = "";
if (arg2 == null) arg2 = "";
if (arg3 == null) arg3 = "";
if (arg4 == null) arg4 = "";
fixed (char* string1Bytes = arg1)
fixed (char* string2Bytes = arg2)
fixed (char* string3Bytes = arg3)
fixed (char* string4Bytes = arg4)
{
const int NumEventDatas = 4;
var descrs = stackalloc EventData[NumEventDatas];
descrs[0] = new EventData
{
DataPointer = (IntPtr)string1Bytes,
Size = ((arg1.Length + 1) * 2)
};
descrs[1] = new EventData
{
DataPointer = (IntPtr)string2Bytes,
Size = ((arg2.Length + 1) * 2)
};
descrs[2] = new EventData
{
DataPointer = (IntPtr)string3Bytes,
Size = ((arg3.Length + 1) * 2)
};
descrs[3] = new EventData
{
DataPointer = (IntPtr)string4Bytes,
Size = ((arg4.Length + 1) * 2)
};
WriteEventCore(eventId, NumEventDatas, descrs);
}
}
}
[NonEvent]
private unsafe void WriteEvent(int eventId, string? arg1, string? arg2, byte[]? arg3)
{
if (IsEnabled())
{
if (arg1 == null) arg1 = "";
if (arg2 == null) arg2 = "";
if (arg3 == null) arg3 = Array.Empty();
fixed (char* arg1Ptr = arg1)
fixed (char* arg2Ptr = arg2)
fixed (byte* arg3Ptr = arg3)
{
int bufferLength = arg3.Length;
const int NumEventDatas = 4;
var 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)(&bufferLength),
Size = 4
};
descrs[3] = new EventData
{
DataPointer = (IntPtr)arg3Ptr,
Size = bufferLength
};
WriteEventCore(eventId, NumEventDatas, descrs);
}
}
}
[NonEvent]
private unsafe void WriteEvent(int eventId, string? arg1, int arg2, int arg3, int arg4)
{
if (IsEnabled())
{
if (arg1 == null) arg1 = "";
fixed (char* arg1Ptr = arg1)
{
const int NumEventDatas = 4;
var descrs = stackalloc EventData[NumEventDatas];
descrs[0] = new EventData
{
DataPointer = (IntPtr)(arg1Ptr),
Size = (arg1.Length + 1) * sizeof(char)
};
descrs[1] = new EventData
{
DataPointer = (IntPtr)(&arg2),
Size = sizeof(int)
};
descrs[2] = new EventData
{
DataPointer = (IntPtr)(&arg3),
Size = sizeof(int)
};
descrs[3] = new EventData
{
DataPointer = (IntPtr)(&arg4),
Size = sizeof(int)
};
WriteEventCore(eventId, NumEventDatas, descrs);
}
}
}
[NonEvent]
private unsafe void WriteEvent(int eventId, string? arg1, int arg2, string? arg3)
{
if (IsEnabled())
{
if (arg1 == null) arg1 = "";
if (arg3 == null) arg3 = "";
fixed (char* arg1Ptr = arg1)
fixed (char* arg3Ptr = arg3)
{
const int NumEventDatas = 3;
var descrs = stackalloc EventData[NumEventDatas];
descrs[0] = new EventData
{
DataPointer = (IntPtr)(arg1Ptr),
Size = (arg1.Length + 1) * sizeof(char)
};
descrs[1] = new EventData
{
DataPointer = (IntPtr)(&arg2),
Size = sizeof(int)
};
descrs[2] = new EventData
{
DataPointer = (IntPtr)(arg3Ptr),
Size = (arg3.Length + 1) * sizeof(char)
};
WriteEventCore(eventId, NumEventDatas, descrs);
}
}
}
[NonEvent]
private unsafe void WriteEvent(int eventId, string? arg1, string? arg2, int arg3)
{
if (IsEnabled())
{
if (arg1 == null) arg1 = "";
if (arg2 == null) arg2 = "";
fixed (char* arg1Ptr = arg1)
fixed (char* arg2Ptr = arg2)
{
const int NumEventDatas = 3;
var 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);
}
}
}
[NonEvent]
private unsafe void WriteEvent(int eventId, string? arg1, string? arg2, string? arg3, int arg4)
{
if (IsEnabled())
{
if (arg1 == null) arg1 = "";
if (arg2 == null) arg2 = "";
if (arg3 == null) arg3 = "";
fixed (char* arg1Ptr = arg1)
fixed (char* arg2Ptr = arg2)
fixed (char* arg3Ptr = arg3)
{
const int NumEventDatas = 4;
var 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)(arg3Ptr),
Size = (arg3.Length + 1) * sizeof(char)
};
descrs[3] = new EventData
{
DataPointer = (IntPtr)(&arg4),
Size = sizeof(int)
};
WriteEventCore(eventId, NumEventDatas, descrs);
}
}
}
[NonEvent]
private unsafe void WriteEvent(int eventId, string arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8)
{
if (IsEnabled())
{
if (arg1 == null) arg1 = "";
fixed (char* arg1Ptr = arg1)
{
const int NumEventDatas = 8;
var descrs = stackalloc EventData[NumEventDatas];
descrs[0] = new EventData
{
DataPointer = (IntPtr)(arg1Ptr),
Size = (arg1.Length + 1) * sizeof(char)
};
descrs[1] = new EventData
{
DataPointer = (IntPtr)(&arg2),
Size = sizeof(int)
};
descrs[2] = new EventData
{
DataPointer = (IntPtr)(&arg3),
Size = sizeof(int)
};
descrs[3] = new EventData
{
DataPointer = (IntPtr)(&arg4),
Size = sizeof(int)
};
descrs[4] = new EventData
{
DataPointer = (IntPtr)(&arg5),
Size = sizeof(int)
};
descrs[5] = new EventData
{
DataPointer = (IntPtr)(&arg6),
Size = sizeof(int)
};
descrs[6] = new EventData
{
DataPointer = (IntPtr)(&arg7),
Size = sizeof(int)
};
descrs[7] = new EventData
{
DataPointer = (IntPtr)(&arg8),
Size = sizeof(int)
};
WriteEventCore(eventId, NumEventDatas, descrs);
}
}
}
[NonEvent]
private unsafe void WriteEvent(int eventId, string arg1, string arg2, int arg3, int arg4, int arg5)
{
if (IsEnabled())
{
if (arg1 == null) arg1 = "";
if (arg2 == null) arg2 = "";
fixed (char* arg1Ptr = arg1)
fixed (char* arg2Ptr = arg2)
{
const int NumEventDatas = 5;
var 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)
};
descrs[3] = new EventData
{
DataPointer = (IntPtr)(&arg4),
Size = sizeof(int)
};
descrs[4] = new EventData
{
DataPointer = (IntPtr)(&arg5),
Size = sizeof(int)
};
WriteEventCore(eventId, NumEventDatas, descrs);
}
}
}
#endregion
}
}