Skip to content

Commit 8ddb0c4

Browse files
authored
Add DiagnosticSource.Write<T> API to assist with trimming (#76395)
* Add DiagnosticSource.Write<T> API to assist with trimming Fix #50454 * Add tests
1 parent dc27e26 commit 8ddb0c4

12 files changed

+151
-6
lines changed

src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ internal static DiagnosticListener LogHostBuilding(HostApplicationBuilder hostAp
193193

194194
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
195195
Justification = "The values being passed into Write are being consumed by the application already.")]
196-
private static void Write<T>(
196+
private static void Write<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(
197197
DiagnosticSource diagnosticSource,
198198
string name,
199199
T value)

src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSource.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,7 @@ protected DiagnosticSource() { }
2929
public virtual bool IsEnabled(string name, object? arg1, object? arg2 = null) { throw null; }
3030
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The type of object being written to DiagnosticSource cannot be discovered statically.")]
3131
public abstract void Write(string name, object? value);
32+
[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.")]
33+
public void Write<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] T>(string name, T value) { }
3234
}
3335
}

src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSource.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<TargetFrameworks>$(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum)</TargetFrameworks>
44
<CLSCompliant>false</CLSCompliant>
@@ -11,6 +11,8 @@
1111
</ItemGroup>
1212

1313
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
14+
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\DynamicallyAccessedMemberTypes.cs" />
15+
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\DynamicallyAccessedMembersAttribute.cs" />
1416
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\RequiresUnreferencedCodeAttribute.cs" />
1517
<Compile Include="$(CoreLibSharedDir)System\Runtime\CompilerServices\IsExternalInit.cs" />
1618
</ItemGroup>

src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,12 @@ public virtual void OnActivityExport(System.Diagnostics.Activity activity, objec
198198
public virtual void OnActivityImport(System.Diagnostics.Activity activity, object? payload) { }
199199
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The type of object being written to DiagnosticSource cannot be discovered statically.")]
200200
public System.Diagnostics.Activity StartActivity(System.Diagnostics.Activity activity, object? args) { throw null; }
201+
[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.")]
202+
public System.Diagnostics.Activity StartActivity<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] T>(Activity activity, T args) { throw null; }
201203
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The type of object being written to DiagnosticSource cannot be discovered statically.")]
202204
public void StopActivity(System.Diagnostics.Activity activity, object? args) { }
205+
[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.")]
206+
public void StopActivity<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] T>(Activity activity, T args) { throw null; }
203207
}
204208
public enum ActivitySamplingResult
205209
{

src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ System.Diagnostics.DiagnosticSource</PackageDescription>
9999

100100
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\DynamicDependencyAttribute.cs" />
101101
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\DynamicallyAccessedMemberTypes.cs" />
102+
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\DynamicallyAccessedMembersAttribute.cs" />
102103
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\RequiresUnreferencedCodeAttribute.cs" />
103104
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\UnconditionalSuppressMessageAttribute.cs" />
104105
<Compile Include="$(CoreLibSharedDir)System\Runtime\CompilerServices\IsExternalInit.cs" />

src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSource.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ namespace System.Diagnostics
1717
public abstract partial class DiagnosticSource
1818
{
1919
internal const string WriteRequiresUnreferencedCode = "The type of object being written to DiagnosticSource cannot be discovered statically.";
20+
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.";
2021

2122
/// <summary>
2223
/// Write is a generic way of logging complex payloads. Each notification
@@ -37,6 +38,12 @@ public abstract partial class DiagnosticSource
3738
[RequiresUnreferencedCode(WriteRequiresUnreferencedCode)]
3839
public abstract void Write(string name, object? value);
3940

41+
/// <inheritdoc cref="Write"/>
42+
/// <typeparam name="T">The type of the value being passed as a payload for the event.</typeparam>
43+
[RequiresUnreferencedCode(WriteOfTRequiresUnreferencedCode)]
44+
public void Write<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string name, T value) =>
45+
Write(name, (object?)value);
46+
4047
/// <summary>
4148
/// Optional: if there is expensive setup for the notification, you can call IsEnabled
4249
/// before doing this setup. Consumers should not be assuming that they only get notifications

src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceActivity.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ public Activity StartActivity(Activity activity, object? args)
3333
return activity;
3434
}
3535

36+
/// <inheritdoc cref="StartActivity"/>
37+
/// <typeparam name="T">The type of the value being passed as a payload for the event.</typeparam>
38+
[RequiresUnreferencedCode(WriteOfTRequiresUnreferencedCode)]
39+
public Activity StartActivity<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(Activity activity, T args)
40+
=> StartActivity(activity, (object?)args);
41+
3642
/// <summary>
3743
/// Stops given Activity: maintains global Current Activity and notifies consumers
3844
/// that Activity was stopped. Consumers could access <see cref="Activity.Current"/>
@@ -54,6 +60,12 @@ public void StopActivity(Activity activity, object? args)
5460
activity.Stop(); // Resets Activity.Current (we want this after the Write)
5561
}
5662

63+
/// <inheritdoc cref="StartActivity"/>
64+
/// <typeparam name="T">The type of the value being passed as a payload for the event.</typeparam>
65+
[RequiresUnreferencedCode(WriteOfTRequiresUnreferencedCode)]
66+
public void StopActivity<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(Activity activity, T args)
67+
=> StopActivity(activity, (object?)args);
68+
5769
/// <summary>
5870
/// Optional: If an instrumentation site creating an new activity that was caused
5971
/// by something outside the process (e.g. an incoming HTTP request), then that site

src/libraries/System.Diagnostics.DiagnosticSource/tests/DiagnosticSourceTests.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,34 @@ public void IntPayload()
4141
}
4242
}
4343

44+
/// <summary>
45+
/// Trivial example of passing an object
46+
/// </summary>
47+
[Fact]
48+
public void ObjectPayload()
49+
{
50+
using (DiagnosticListener listener = new DiagnosticListener("TestingObjectPayload"))
51+
{
52+
DiagnosticSource source = listener;
53+
var result = new List<KeyValuePair<string, object>>();
54+
var observer = new ObserverToList<TelemData>(result);
55+
56+
using (listener.Subscribe(new ObserverToList<TelemData>(result)))
57+
{
58+
object o = new object();
59+
60+
listener.Write("ObjectPayload", o);
61+
Assert.Equal(1, result.Count);
62+
Assert.Equal("ObjectPayload", result[0].Key);
63+
Assert.Same(o, result[0].Value);
64+
} // unsubscribe
65+
66+
// Make sure that after unsubscribing, we don't get more events.
67+
source.Write("ObjectPayload", new object());
68+
Assert.Equal(1, result.Count);
69+
}
70+
}
71+
4472
/// <summary>
4573
/// slightly less trivial of passing a structure with a couple of fields
4674
/// </summary>

src/libraries/System.Diagnostics.DiagnosticSource/tests/NativeAotTests/DiagnosticSourceEventSourceTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
using System.Diagnostics.Tracing;
1010

1111
/// <summary>
12-
/// Tests that using writing to a DiagnosticSource writes the correct payloads
12+
/// Tests that writing to a DiagnosticSource writes the correct payloads
1313
/// to the DiagnosticSourceEventSource.
1414
/// </summary>
1515
internal class Program
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Project DefaultTargets="Build">
2+
<Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Build.props))" />
3+
4+
<ItemGroup>
5+
<TestConsoleAppSourceFiles Include="WritePreservesAnonymousProperties.cs" />
6+
</ItemGroup>
7+
8+
<Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Build.targets))" />
9+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.ObjectModel;
5+
using System.Diagnostics;
6+
using System.Diagnostics.Tracing;
7+
8+
/// <summary>
9+
/// Tests that writing an anonymous type to a DiagnosticSource preserves the anonymous type's properties
10+
/// correctly, so they are written to the EventSource correctly.
11+
/// </summary>
12+
internal class Program
13+
{
14+
private class TestEventListener : EventListener
15+
{
16+
public ReadOnlyCollection<object> LogDataPayload { get; set; }
17+
18+
protected override void OnEventSourceCreated(EventSource eventSource)
19+
{
20+
if (eventSource.Name == "Microsoft-Diagnostics-DiagnosticSource")
21+
{
22+
EnableEvents(eventSource, EventLevel.Verbose, EventKeywords.All, new Dictionary<string, string>
23+
{
24+
{ "FilterAndPayloadSpecs", "TestDiagnosticListener/Test.Start@Activity2Start:-Id;Name"}
25+
});
26+
}
27+
28+
base.OnEventSourceCreated(eventSource);
29+
}
30+
31+
protected override void OnEventWritten(EventWrittenEventArgs eventData)
32+
{
33+
if (eventData.EventName == "Activity2Start")
34+
{
35+
LogDataPayload = eventData.Payload;
36+
}
37+
38+
base.OnEventWritten(eventData);
39+
}
40+
}
41+
42+
public static int Main()
43+
{
44+
DiagnosticSource diagnosticSource = new DiagnosticListener("TestDiagnosticListener");
45+
using (var listener = new TestEventListener())
46+
{
47+
var data = new
48+
{
49+
Id = Guid.NewGuid(),
50+
Name = "EventName"
51+
};
52+
53+
diagnosticSource.Write("Test.Start", data);
54+
55+
if (!(listener.LogDataPayload?.Count == 3 &&
56+
(string)listener.LogDataPayload[0] == "TestDiagnosticListener" &&
57+
(string)listener.LogDataPayload[1] == "Test.Start"))
58+
{
59+
return -1;
60+
}
61+
62+
object[] args = (object[])listener.LogDataPayload[2];
63+
if (args.Length != 2)
64+
{
65+
return -2;
66+
}
67+
68+
IDictionary<string, object> arg = (IDictionary<string, object>)args[0];
69+
if (!((string)arg["Key"] == "Id" && (string)arg["Value"] == data.Id.ToString()))
70+
{
71+
return -3;
72+
}
73+
74+
arg = (IDictionary<string, object>)args[1];
75+
if (!((string)arg["Key"] == "Name" && (string)arg["Value"] == "EventName"))
76+
{
77+
return -4;
78+
}
79+
80+
return 100;
81+
}
82+
}
83+
}

src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,6 @@ private sealed class ActivityStartData
217217
// matches the properties selected in https://github.com/dotnet/diagnostics/blob/ffd0254da3bcc47847b1183fa5453c0877020abd/src/Microsoft.Diagnostics.Monitoring.EventPipe/Configuration/HttpRequestSourceConfiguration.cs#L36-L40
218218
[DynamicDependency(nameof(HttpRequestMessage.RequestUri), typeof(HttpRequestMessage))]
219219
[DynamicDependency(nameof(HttpRequestMessage.Method), typeof(HttpRequestMessage))]
220-
[DynamicDependency(nameof(HttpRequestMessage.RequestUri), typeof(HttpRequestMessage))]
221220
[DynamicDependency(nameof(Uri.Host), typeof(Uri))]
222221
[DynamicDependency(nameof(Uri.Port), typeof(Uri))]
223222
internal ActivityStartData(HttpRequestMessage request)
@@ -251,7 +250,6 @@ private sealed class ExceptionData
251250
// preserve the same properties as ActivityStartData above + common Exception properties
252251
[DynamicDependency(nameof(HttpRequestMessage.RequestUri), typeof(HttpRequestMessage))]
253252
[DynamicDependency(nameof(HttpRequestMessage.Method), typeof(HttpRequestMessage))]
254-
[DynamicDependency(nameof(HttpRequestMessage.RequestUri), typeof(HttpRequestMessage))]
255253
[DynamicDependency(nameof(Uri.Host), typeof(Uri))]
256254
[DynamicDependency(nameof(Uri.Port), typeof(Uri))]
257255
[DynamicDependency(nameof(System.Exception.Message), typeof(Exception))]
@@ -273,7 +271,6 @@ private sealed class RequestData
273271
// preserve the same properties as ActivityStartData above
274272
[DynamicDependency(nameof(HttpRequestMessage.RequestUri), typeof(HttpRequestMessage))]
275273
[DynamicDependency(nameof(HttpRequestMessage.Method), typeof(HttpRequestMessage))]
276-
[DynamicDependency(nameof(HttpRequestMessage.RequestUri), typeof(HttpRequestMessage))]
277274
[DynamicDependency(nameof(Uri.Host), typeof(Uri))]
278275
[DynamicDependency(nameof(Uri.Port), typeof(Uri))]
279276
internal RequestData(HttpRequestMessage request, Guid loggingRequestId, long timestamp)

0 commit comments

Comments
 (0)