Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DiagnosticSource.Write<T> API to assist with trimming #50454

Closed
eerhardt opened this issue Mar 30, 2021 · 4 comments · Fixed by #76395
Closed

Add DiagnosticSource.Write<T> API to assist with trimming #50454

eerhardt opened this issue Mar 30, 2021 · 4 comments · Fixed by #76395
Assignees
Labels
api-approved API was approved in API review, it can be implemented area-System.Diagnostics feature-request
Milestone

Comments

@eerhardt
Copy link
Member

Background and Motivation

The DiagnosticSource.Write API is not trim-friendly because the type of object being passed in cannot be statically discovered.

Internally, we have a DiagnosticSourceEventSource class which listens to events being written to a DiagnosticSource and will forward the event on to an EventSource. This functionality assumes that all the properties on the object, and any properties recursively on the Types of those properties, are available at runtime. It uses Reflection to access the property values based on FilterAndPayloadSpecs strings that are passed to the DiagnosticSourceEventSource.

In order to resolve the ILLink warnings in System.Diagnostics.DiagnosticSource, we are marking DiagnosticSource.Write with RequiresUnreferencedCode. This means any caller of this API will get ILLink warnings themselves, and will need to preserve the necessary properties with [DynamicDependency] attributes, or some other mechanism.

However, a common usage of DiagnosticSource.Write is to pass in an anonymous type, for example this is how ASP.NET uses the API:

https://github.com/dotnet/aspnetcore/blob/85a6cb07ae2e1adf5a03740f8d40333b7b8e360c/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs#L212-L218

        private void RecordBeginRequestDiagnostics(HttpContext httpContext, long startTimestamp)
        {
            _diagnosticListener.Write(
                DeprecatedDiagnosticsBeginRequestKey,
                new
                {
                    httpContext = httpContext,
                    timestamp = startTimestamp
                });

Since the type is anonymous, there isn't a way to add a [DynamicDependency] pointing to the type.

To resolve this issue, we should add a new overload to the Write method that is generic, so we can annotate the T type with a [DynamicallyAccessedMembers] attribute. Unfortunately, this won't make the API 100% trim compatible because recursive properties aren't supported (see dotnet/linker#1087), thus it will still need to be marked as [RequiresUnreferencedCode]. However, it will be one less set of properties callers will need to manually preserve themselves, and it will allow anonymous types to be used.

See this conversation for more background and reasoning.

Proposed API

namespace System.Diagnostics
{
    public abstract class DiagnosticSource
    {
        [RequiresUnreferencedCode]
        public abstract void Write(string name, object? value);

+       [RequiresUnreferencedCode]
+       public void Write<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string name, T? value);

        [RequiresUnreferencedCode]
        public Activity StartActivity(Activity activity, object? args);

+       [RequiresUnreferencedCode]
+       public Activity StartActivity<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(Activity activity, T? args);

        [RequiresUnreferencedCode]
        public void StopActivity(Activity activity, object? args);

+       [RequiresUnreferencedCode]
+       public void StopActivity<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(Activity activity, T? args);
     }
}

Usage Examples

The usage is the same as the existing APIs.

            _diagnosticListener.Write(
                DeprecatedDiagnosticsBeginRequestKey,
                new
                {
                    httpContext = httpContext,
                    timestamp = startTimestamp
                });

Alternative Designs

N/A

Risks

Is it possible that caller may still bind to the API accepting object? and they won't get the DynamicallyAccessedMembers attribute applied to their types?

@eerhardt eerhardt added api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Diagnostics labels Mar 30, 2021
@ghost
Copy link

ghost commented Mar 30, 2021

Tagging subscribers to this area: @tommcdon, @krwq
See info in area-owners.md if you want to be subscribed.

Issue Details

Background and Motivation

The DiagnosticSource.Write API is not trim-friendly because the type of object being passed in cannot be statically discovered.

Internally, we have a DiagnosticSourceEventSource class which listens to events being written to a DiagnosticSource and will forward the event on to an EventSource. This functionality assumes that all the properties on the object, and any properties recursively on the Types of those properties, are available at runtime. It uses Reflection to access the property values based on FilterAndPayloadSpecs strings that are passed to the DiagnosticSourceEventSource.

In order to resolve the ILLink warnings in System.Diagnostics.DiagnosticSource, we are marking DiagnosticSource.Write with RequiresUnreferencedCode. This means any caller of this API will get ILLink warnings themselves, and will need to preserve the necessary properties with [DynamicDependency] attributes, or some other mechanism.

However, a common usage of DiagnosticSource.Write is to pass in an anonymous type, for example this is how ASP.NET uses the API:

https://github.com/dotnet/aspnetcore/blob/85a6cb07ae2e1adf5a03740f8d40333b7b8e360c/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs#L212-L218

        private void RecordBeginRequestDiagnostics(HttpContext httpContext, long startTimestamp)
        {
            _diagnosticListener.Write(
                DeprecatedDiagnosticsBeginRequestKey,
                new
                {
                    httpContext = httpContext,
                    timestamp = startTimestamp
                });

Since the type is anonymous, there isn't a way to add a [DynamicDependency] pointing to the type.

To resolve this issue, we should add a new overload to the Write method that is generic, so we can annotate the T type with a [DynamicallyAccessedMembers] attribute. Unfortunately, this won't make the API 100% trim compatible because recursive properties aren't supported (see dotnet/linker#1087), thus it will still need to be marked as [RequiresUnreferencedCode]. However, it will be one less set of properties callers will need to manually preserve themselves, and it will allow anonymous types to be used.

See this conversation for more background and reasoning.

Proposed API

namespace System.Diagnostics
{
    public abstract class DiagnosticSource
    {
        [RequiresUnreferencedCode]
        public abstract void Write(string name, object? value);

+       [RequiresUnreferencedCode]
+       public void Write<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string name, T? value);

        [RequiresUnreferencedCode]
        public Activity StartActivity(Activity activity, object? args);

+       [RequiresUnreferencedCode]
+       public Activity StartActivity<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(Activity activity, T? args);

        [RequiresUnreferencedCode]
        public void StopActivity(Activity activity, object? args);

+       [RequiresUnreferencedCode]
+       public void StopActivity<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(Activity activity, T? args);
     }
}

Usage Examples

The usage is the same as the existing APIs.

            _diagnosticListener.Write(
                DeprecatedDiagnosticsBeginRequestKey,
                new
                {
                    httpContext = httpContext,
                    timestamp = startTimestamp
                });

Alternative Designs

N/A

Risks

Is it possible that caller may still bind to the API accepting object? and they won't get the DynamicallyAccessedMembers attribute applied to their types?

Author: eerhardt
Assignees: -
Labels:

api-suggestion, area-System.Diagnostics

Milestone: -

@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged New issue has not been triaged by the area owner label Mar 30, 2021
@tommcdon tommcdon added this to the 6.0.0 milestone Apr 2, 2021
@tommcdon tommcdon removed the untriaged New issue has not been triaged by the area owner label Apr 2, 2021
@noahfalk
Copy link
Member

@eerhardt - I am guessing this work is not planned for 6.0 at this point? I'm changing the milestone but if this was something you plan to do you are welcome to change it further.

@noahfalk noahfalk modified the milestones: 6.0.0, Future Jul 28, 2021
@eerhardt
Copy link
Member Author

I am guessing this work is not planned for 6.0 at this point?

Correct. This is a new API and 6.0 has reached feature complete.

@eerhardt eerhardt added api-ready-for-review API is ready for review, it is NOT ready for implementation and removed api-suggestion Early API idea and discussion, it is NOT ready for implementation labels Jul 28, 2021
@bartonjs
Copy link
Member

bartonjs commented Aug 17, 2021

Video

These new overloads don't quite solve the problem (recursive preservation, polymorphism), but it's an improvement at low cost.

namespace System.Diagnostics
{
    public abstract class DiagnosticSource
    {
        [RequiresUnreferencedCode]
        public abstract void Write(string name, object? value);

+       [RequiresUnreferencedCode]
+       public void Write<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string name, T? value);

        [RequiresUnreferencedCode]
        public Activity StartActivity(Activity activity, object? args);

+       [RequiresUnreferencedCode]
+       public Activity StartActivity<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(Activity activity, T? args);

        [RequiresUnreferencedCode]
        public void StopActivity(Activity activity, object? args);

+       [RequiresUnreferencedCode]
+       public void StopActivity<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(Activity activity, T? args);
     }
}

@bartonjs bartonjs added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for review, it is NOT ready for implementation labels Aug 17, 2021
eerhardt added a commit to eerhardt/runtime that referenced this issue Sep 29, 2022
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Sep 29, 2022
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Oct 27, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Nov 26, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented area-System.Diagnostics feature-request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants