Skip to content

Commit

Permalink
Add DiagnosticSource.Write<T> API to assist with trimming (#76395)
Browse files Browse the repository at this point in the history
* Add DiagnosticSource.Write<T> API to assist with trimming

Fix #50454

* Add tests
  • Loading branch information
eerhardt authored Oct 27, 2022
1 parent dc27e26 commit 8ddb0c4
Show file tree
Hide file tree
Showing 12 changed files with 151 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,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<T>(
private static void Write<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(
DiagnosticSource diagnosticSource,
string name,
T value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) { }
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum)</TargetFrameworks>
<CLSCompliant>false</CLSCompliant>
Expand All @@ -11,6 +11,8 @@
</ItemGroup>

<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\DynamicallyAccessedMemberTypes.cs" />
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\DynamicallyAccessedMembersAttribute.cs" />
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\RequiresUnreferencedCodeAttribute.cs" />
<Compile Include="$(CoreLibSharedDir)System\Runtime\CompilerServices\IsExternalInit.cs" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ System.Diagnostics.DiagnosticSource</PackageDescription>

<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\DynamicDependencyAttribute.cs" />
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\DynamicallyAccessedMemberTypes.cs" />
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\DynamicallyAccessedMembersAttribute.cs" />
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\RequiresUnreferencedCodeAttribute.cs" />
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\UnconditionalSuppressMessageAttribute.cs" />
<Compile Include="$(CoreLibSharedDir)System\Runtime\CompilerServices\IsExternalInit.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.";

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

/// <inheritdoc cref="Write"/>
/// <typeparam name="T">The type of the value being passed as a payload for the event.</typeparam>
[RequiresUnreferencedCode(WriteOfTRequiresUnreferencedCode)]
public void Write<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string name, T value) =>
Write(name, (object?)value);

/// <summary>
/// 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ public Activity StartActivity(Activity activity, object? args)
return activity;
}

/// <inheritdoc cref="StartActivity"/>
/// <typeparam name="T">The type of the value being passed as a payload for the event.</typeparam>
[RequiresUnreferencedCode(WriteOfTRequiresUnreferencedCode)]
public Activity StartActivity<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(Activity activity, T args)
=> StartActivity(activity, (object?)args);

/// <summary>
/// Stops given Activity: maintains global Current Activity and notifies consumers
/// that Activity was stopped. Consumers could access <see cref="Activity.Current"/>
Expand All @@ -54,6 +60,12 @@ public void StopActivity(Activity activity, object? args)
activity.Stop(); // Resets Activity.Current (we want this after the Write)
}

/// <inheritdoc cref="StartActivity"/>
/// <typeparam name="T">The type of the value being passed as a payload for the event.</typeparam>
[RequiresUnreferencedCode(WriteOfTRequiresUnreferencedCode)]
public void StopActivity<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(Activity activity, T args)
=> StopActivity(activity, (object?)args);

/// <summary>
/// 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,34 @@ public void IntPayload()
}
}

/// <summary>
/// Trivial example of passing an object
/// </summary>
[Fact]
public void ObjectPayload()
{
using (DiagnosticListener listener = new DiagnosticListener("TestingObjectPayload"))
{
DiagnosticSource source = listener;
var result = new List<KeyValuePair<string, object>>();
var observer = new ObserverToList<TelemData>(result);

using (listener.Subscribe(new ObserverToList<TelemData>(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);
}
}

/// <summary>
/// slightly less trivial of passing a structure with a couple of fields
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
using System.Diagnostics.Tracing;

/// <summary>
/// Tests that using writing to a DiagnosticSource writes the correct payloads
/// Tests that writing to a DiagnosticSource writes the correct payloads
/// to the DiagnosticSourceEventSource.
/// </summary>
internal class Program
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project DefaultTargets="Build">
<Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Build.props))" />

<ItemGroup>
<TestConsoleAppSourceFiles Include="WritePreservesAnonymousProperties.cs" />
</ItemGroup>

<Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Build.targets))" />
</Project>
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Tests that writing an anonymous type to a DiagnosticSource preserves the anonymous type's properties
/// correctly, so they are written to the EventSource correctly.
/// </summary>
internal class Program
{
private class TestEventListener : EventListener
{
public ReadOnlyCollection<object> LogDataPayload { get; set; }

protected override void OnEventSourceCreated(EventSource eventSource)
{
if (eventSource.Name == "Microsoft-Diagnostics-DiagnosticSource")
{
EnableEvents(eventSource, EventLevel.Verbose, EventKeywords.All, new Dictionary<string, string>
{
{ "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<string, object> arg = (IDictionary<string, object>)args[0];
if (!((string)arg["Key"] == "Id" && (string)arg["Value"] == data.Id.ToString()))
{
return -3;
}

arg = (IDictionary<string, object>)args[1];
if (!((string)arg["Key"] == "Name" && (string)arg["Value"] == "EventName"))
{
return -4;
}

return 100;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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))]
Expand All @@ -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)
Expand Down

0 comments on commit 8ddb0c4

Please sign in to comment.