diff --git a/src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs b/src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs index 9c864e489ab139..d9adaca7d7d062 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs +++ b/src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs @@ -190,7 +190,7 @@ internal static DiagnosticListener LogHostBuilding(HostApplicationBuilder hostAp [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", Justification = "The values being passed into Write are being consumed by the application already.")] - private static void Write( + private static void Write<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>( DiagnosticSource diagnosticSource, string name, T value) diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSource.cs b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSource.cs index 1b08291cccd2ab..47a4dd4672a313 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSource.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSource.cs @@ -29,5 +29,7 @@ protected DiagnosticSource() { } public virtual bool IsEnabled(string name, object? arg1, object? arg2 = null) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The type of object being written to DiagnosticSource cannot be discovered statically.")] public abstract void Write(string name, object? value); + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Only the properties of the T type will be preserved. Properties of referenced types and properties of derived types may be trimmed.")] + public void Write<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] T>(string name, T value) { } } } diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSource.csproj b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSource.csproj index c82926c2d6eeb0..44db1cff62518a 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSource.csproj +++ b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSource.csproj @@ -1,4 +1,4 @@ - + $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) false @@ -11,6 +11,8 @@ + + diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs index 488e6c39224fe0..71c2a9d7b8b59f 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs @@ -198,8 +198,12 @@ public virtual void OnActivityExport(System.Diagnostics.Activity activity, objec public virtual void OnActivityImport(System.Diagnostics.Activity activity, object? payload) { } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The type of object being written to DiagnosticSource cannot be discovered statically.")] public System.Diagnostics.Activity StartActivity(System.Diagnostics.Activity activity, object? args) { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Only the properties of the T type will be preserved. Properties of referenced types and properties of derived types may be trimmed.")] + public System.Diagnostics.Activity StartActivity<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] T>(Activity activity, T args) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The type of object being written to DiagnosticSource cannot be discovered statically.")] public void StopActivity(System.Diagnostics.Activity activity, object? args) { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Only the properties of the T type will be preserved. Properties of referenced types and properties of derived types may be trimmed.")] + public void StopActivity<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] T>(Activity activity, T args) { throw null; } } public enum ActivitySamplingResult { diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj index 4c20a73469985b..b3f55c9254d7c2 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj @@ -99,6 +99,7 @@ System.Diagnostics.DiagnosticSource + diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSource.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSource.cs index 89e62d4102c988..3d6b5e02fc3b88 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSource.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSource.cs @@ -17,6 +17,7 @@ namespace System.Diagnostics public abstract partial class DiagnosticSource { internal const string WriteRequiresUnreferencedCode = "The type of object being written to DiagnosticSource cannot be discovered statically."; + internal const string WriteOfTRequiresUnreferencedCode = "Only the properties of the T type will be preserved. Properties of referenced types and properties of derived types may be trimmed."; /// /// Write is a generic way of logging complex payloads. Each notification @@ -37,6 +38,12 @@ public abstract partial class DiagnosticSource [RequiresUnreferencedCode(WriteRequiresUnreferencedCode)] public abstract void Write(string name, object? value); + /// + /// The type of the value being passed as a payload for the event. + [RequiresUnreferencedCode(WriteOfTRequiresUnreferencedCode)] + public void Write<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string name, T value) => + Write(name, (object?)value); + /// /// Optional: if there is expensive setup for the notification, you can call IsEnabled /// before doing this setup. Consumers should not be assuming that they only get notifications diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceActivity.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceActivity.cs index 5bf9fa66916692..f607816e2c2b01 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceActivity.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceActivity.cs @@ -33,6 +33,12 @@ public Activity StartActivity(Activity activity, object? args) return activity; } + /// + /// The type of the value being passed as a payload for the event. + [RequiresUnreferencedCode(WriteOfTRequiresUnreferencedCode)] + public Activity StartActivity<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(Activity activity, T args) + => StartActivity(activity, (object?)args); + /// /// Stops given Activity: maintains global Current Activity and notifies consumers /// that Activity was stopped. Consumers could access @@ -54,6 +60,12 @@ public void StopActivity(Activity activity, object? args) activity.Stop(); // Resets Activity.Current (we want this after the Write) } + /// + /// The type of the value being passed as a payload for the event. + [RequiresUnreferencedCode(WriteOfTRequiresUnreferencedCode)] + public void StopActivity<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(Activity activity, T args) + => StopActivity(activity, (object?)args); + /// /// Optional: If an instrumentation site creating an new activity that was caused /// by something outside the process (e.g. an incoming HTTP request), then that site diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/DiagnosticSourceTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/DiagnosticSourceTests.cs index fa683bd2ddd9f0..ae93b7a2e4f761 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/DiagnosticSourceTests.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/DiagnosticSourceTests.cs @@ -41,6 +41,34 @@ public void IntPayload() } } + /// + /// Trivial example of passing an object + /// + [Fact] + public void ObjectPayload() + { + using (DiagnosticListener listener = new DiagnosticListener("TestingObjectPayload")) + { + DiagnosticSource source = listener; + var result = new List>(); + var observer = new ObserverToList(result); + + using (listener.Subscribe(new ObserverToList(result))) + { + object o = new object(); + + listener.Write("ObjectPayload", o); + Assert.Equal(1, result.Count); + Assert.Equal("ObjectPayload", result[0].Key); + Assert.Same(o, result[0].Value); + } // unsubscribe + + // Make sure that after unsubscribing, we don't get more events. + source.Write("ObjectPayload", new object()); + Assert.Equal(1, result.Count); + } + } + /// /// slightly less trivial of passing a structure with a couple of fields /// diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/NativeAotTests/DiagnosticSourceEventSourceTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/NativeAotTests/DiagnosticSourceEventSourceTests.cs index fab21b5a7b716c..50582fdaf398b9 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/NativeAotTests/DiagnosticSourceEventSourceTests.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/NativeAotTests/DiagnosticSourceEventSourceTests.cs @@ -9,7 +9,7 @@ using System.Diagnostics.Tracing; /// -/// Tests that using writing to a DiagnosticSource writes the correct payloads +/// Tests that writing to a DiagnosticSource writes the correct payloads /// to the DiagnosticSourceEventSource. /// internal class Program diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/TrimmingTests/System.Diagnostics.DiagnosticSource.proj b/src/libraries/System.Diagnostics.DiagnosticSource/tests/TrimmingTests/System.Diagnostics.DiagnosticSource.proj new file mode 100644 index 00000000000000..ca9483e224ffb4 --- /dev/null +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/TrimmingTests/System.Diagnostics.DiagnosticSource.proj @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/TrimmingTests/WritePreservesAnonymousProperties.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/TrimmingTests/WritePreservesAnonymousProperties.cs new file mode 100644 index 00000000000000..ee0f633c5d171a --- /dev/null +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/TrimmingTests/WritePreservesAnonymousProperties.cs @@ -0,0 +1,83 @@ +// 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.ObjectModel; +using System.Diagnostics; +using System.Diagnostics.Tracing; + +/// +/// Tests that writing an anonymous type to a DiagnosticSource preserves the anonymous type's properties +/// correctly, so they are written to the EventSource correctly. +/// +internal class Program +{ + private class TestEventListener : EventListener + { + public ReadOnlyCollection LogDataPayload { get; set; } + + protected override void OnEventSourceCreated(EventSource eventSource) + { + if (eventSource.Name == "Microsoft-Diagnostics-DiagnosticSource") + { + EnableEvents(eventSource, EventLevel.Verbose, EventKeywords.All, new Dictionary + { + { "FilterAndPayloadSpecs", "TestDiagnosticListener/Test.Start@Activity2Start:-Id;Name"} + }); + } + + base.OnEventSourceCreated(eventSource); + } + + protected override void OnEventWritten(EventWrittenEventArgs eventData) + { + if (eventData.EventName == "Activity2Start") + { + LogDataPayload = eventData.Payload; + } + + base.OnEventWritten(eventData); + } + } + + public static int Main() + { + DiagnosticSource diagnosticSource = new DiagnosticListener("TestDiagnosticListener"); + using (var listener = new TestEventListener()) + { + var data = new + { + Id = Guid.NewGuid(), + Name = "EventName" + }; + + diagnosticSource.Write("Test.Start", data); + + if (!(listener.LogDataPayload?.Count == 3 && + (string)listener.LogDataPayload[0] == "TestDiagnosticListener" && + (string)listener.LogDataPayload[1] == "Test.Start")) + { + return -1; + } + + object[] args = (object[])listener.LogDataPayload[2]; + if (args.Length != 2) + { + return -2; + } + + IDictionary arg = (IDictionary)args[0]; + if (!((string)arg["Key"] == "Id" && (string)arg["Value"] == data.Id.ToString())) + { + return -3; + } + + arg = (IDictionary)args[1]; + if (!((string)arg["Key"] == "Name" && (string)arg["Value"] == "EventName")) + { + return -4; + } + + return 100; + } + } +} diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs index 16ce1141e2b081..fd9d9420f7a222 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs @@ -217,7 +217,6 @@ private sealed class ActivityStartData // matches the properties selected in https://github.com/dotnet/diagnostics/blob/ffd0254da3bcc47847b1183fa5453c0877020abd/src/Microsoft.Diagnostics.Monitoring.EventPipe/Configuration/HttpRequestSourceConfiguration.cs#L36-L40 [DynamicDependency(nameof(HttpRequestMessage.RequestUri), typeof(HttpRequestMessage))] [DynamicDependency(nameof(HttpRequestMessage.Method), typeof(HttpRequestMessage))] - [DynamicDependency(nameof(HttpRequestMessage.RequestUri), typeof(HttpRequestMessage))] [DynamicDependency(nameof(Uri.Host), typeof(Uri))] [DynamicDependency(nameof(Uri.Port), typeof(Uri))] internal ActivityStartData(HttpRequestMessage request) @@ -251,7 +250,6 @@ private sealed class ExceptionData // preserve the same properties as ActivityStartData above + common Exception properties [DynamicDependency(nameof(HttpRequestMessage.RequestUri), typeof(HttpRequestMessage))] [DynamicDependency(nameof(HttpRequestMessage.Method), typeof(HttpRequestMessage))] - [DynamicDependency(nameof(HttpRequestMessage.RequestUri), typeof(HttpRequestMessage))] [DynamicDependency(nameof(Uri.Host), typeof(Uri))] [DynamicDependency(nameof(Uri.Port), typeof(Uri))] [DynamicDependency(nameof(System.Exception.Message), typeof(Exception))] @@ -273,7 +271,6 @@ private sealed class RequestData // preserve the same properties as ActivityStartData above [DynamicDependency(nameof(HttpRequestMessage.RequestUri), typeof(HttpRequestMessage))] [DynamicDependency(nameof(HttpRequestMessage.Method), typeof(HttpRequestMessage))] - [DynamicDependency(nameof(HttpRequestMessage.RequestUri), typeof(HttpRequestMessage))] [DynamicDependency(nameof(Uri.Host), typeof(Uri))] [DynamicDependency(nameof(Uri.Port), typeof(Uri))] internal RequestData(HttpRequestMessage request, Guid loggingRequestId, long timestamp)