diff --git a/build/test-aot-compatibility.ps1 b/build/test-aot-compatibility.ps1 index cde8dfc9839..c1c92a0b3b8 100644 --- a/build/test-aot-compatibility.ps1 +++ b/build/test-aot-compatibility.ps1 @@ -29,7 +29,7 @@ if ($LastExitCode -ne 0) popd Write-Host "Actual warning count is:", $actualWarningCount -$expectedWarningCount = 26 +$expectedWarningCount = 19 $testPassed = 0 if ($actualWarningCount -ne $expectedWarningCount) diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs index 50a1242e421..40bf9a3fab6 100644 --- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs +++ b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs @@ -15,6 +15,9 @@ // using System.Diagnostics; +#if NET6_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; +#endif #if NETFRAMEWORK using System.Net.Http; #endif @@ -40,10 +43,10 @@ internal sealed class HttpHandlerDiagnosticListener : ListenerHandler private const string OnStopEvent = "System.Net.Http.HttpRequestOut.Stop"; private const string OnUnhandledExceptionEvent = "System.Net.Http.Exception"; - private readonly PropertyFetcher startRequestFetcher = new("Request"); - private readonly PropertyFetcher stopResponseFetcher = new("Response"); - private readonly PropertyFetcher stopExceptionFetcher = new("Exception"); - private readonly PropertyFetcher stopRequestStatusFetcher = new("RequestTaskStatus"); + private static readonly PropertyFetcher StartRequestFetcher = new("Request"); + private static readonly PropertyFetcher StopResponseFetcher = new("Response"); + private static readonly PropertyFetcher StopExceptionFetcher = new("Exception"); + private static readonly PropertyFetcher StopRequestStatusFetcher = new("RequestTaskStatus"); private readonly HttpClientInstrumentationOptions options; private readonly bool emitOldAttributes; private readonly bool emitNewAttributes; @@ -112,7 +115,7 @@ public void OnStartActivity(Activity activity, object payload) return; } - if (!this.startRequestFetcher.TryFetch(payload, out HttpRequestMessage request) || request == null) + if (!TryFetchRequest(payload, out HttpRequestMessage request)) { HttpInstrumentationEventSource.Log.NullPayload(nameof(HttpHandlerDiagnosticListener), nameof(this.OnStartActivity)); return; @@ -211,15 +214,28 @@ public void OnStartActivity(Activity activity, object payload) HttpInstrumentationEventSource.Log.EnrichmentException(ex); } } + + // The AOT-annotation DynamicallyAccessedMembers in System.Net.Http library ensures that top-level properties on the payload object are always preserved. + // see https://github.com/dotnet/runtime/blob/f9246538e3d49b90b0e9128d7b1defef57cd6911/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs#L325 +#if NET6_0_OR_GREATER + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "The event source guarantees that top-level properties are preserved")] +#endif + static bool TryFetchRequest(object payload, out HttpRequestMessage request) + { + if (!StartRequestFetcher.TryFetch(payload, out request) || request == null) + { + return false; + } + + return true; + } } public void OnStopActivity(Activity activity, object payload) { if (activity.IsAllDataRequested) { - // https://github.com/dotnet/runtime/blob/master/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs - // requestTaskStatus is not null - _ = this.stopRequestStatusFetcher.TryFetch(payload, out var requestTaskStatus); + var requestTaskStatus = GetRequestStatus(payload); ActivityStatusCode currentStatusCode = activity.Status; if (requestTaskStatus != TaskStatus.RanToCompletion) @@ -241,7 +257,7 @@ public void OnStopActivity(Activity activity, object payload) } } - if (this.stopResponseFetcher.TryFetch(payload, out HttpResponseMessage response) && response != null) + if (TryFetchResponse(payload, out HttpResponseMessage response)) { if (this.emitOldAttributes) { @@ -267,6 +283,35 @@ public void OnStopActivity(Activity activity, object payload) HttpInstrumentationEventSource.Log.EnrichmentException(ex); } } + + // The AOT-annotation DynamicallyAccessedMembers in System.Net.Http library ensures that top-level properties on the payload object are always preserved. + // see https://github.com/dotnet/runtime/blob/f9246538e3d49b90b0e9128d7b1defef57cd6911/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs#L325 +#if NET6_0_OR_GREATER + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "The event source guarantees that top-level properties are preserved")] +#endif + static TaskStatus GetRequestStatus(object payload) + { + // requestTaskStatus (type is TaskStatus) is a non-nullable enum so we don't need to have a null check here. + // See: https://github.com/dotnet/runtime/blob/79c021d65c280020246d1035b0e87ae36f2d36a9/src/libraries/System.Net.Http/src/HttpDiagnosticsGuide.md?plain=1#L69 + _ = StopRequestStatusFetcher.TryFetch(payload, out var requestTaskStatus); + + return requestTaskStatus; + } + } + + // The AOT-annotation DynamicallyAccessedMembers in System.Net.Http library ensures that top-level properties on the payload object are always preserved. + // see https://github.com/dotnet/runtime/blob/f9246538e3d49b90b0e9128d7b1defef57cd6911/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs#L325 +#if NET6_0_OR_GREATER + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "The event source guarantees that top-level properties are preserved")] +#endif + static bool TryFetchResponse(object payload, out HttpResponseMessage response) + { + if (StopResponseFetcher.TryFetch(payload, out response) && response != null) + { + return true; + } + + return false; } } @@ -274,7 +319,7 @@ public void OnException(Activity activity, object payload) { if (activity.IsAllDataRequested) { - if (!this.stopExceptionFetcher.TryFetch(payload, out Exception exc) || exc == null) + if (!TryFetchException(payload, out Exception exc)) { HttpInstrumentationEventSource.Log.NullPayload(nameof(HttpHandlerDiagnosticListener), nameof(this.OnException)); return; @@ -299,5 +344,20 @@ public void OnException(Activity activity, object payload) HttpInstrumentationEventSource.Log.EnrichmentException(ex); } } + + // The AOT-annotation DynamicallyAccessedMembers in System.Net.Http library ensures that top-level properties on the payload object are always preserved. + // see https://github.com/dotnet/runtime/blob/f9246538e3d49b90b0e9128d7b1defef57cd6911/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs#L325 +#if NET6_0_OR_GREATER + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "The event source guarantees that top-level properties are preserved")] +#endif + static bool TryFetchException(object payload, out Exception exc) + { + if (!StopExceptionFetcher.TryFetch(payload, out exc) || exc == null) + { + return false; + } + + return true; + } } } diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerMetricsDiagnosticListener.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerMetricsDiagnosticListener.cs index fc0685cb331..3aac7b32160 100644 --- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerMetricsDiagnosticListener.cs +++ b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerMetricsDiagnosticListener.cs @@ -15,6 +15,9 @@ // using System.Diagnostics; +#if NET6_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; +#endif using System.Diagnostics.Metrics; #if NETFRAMEWORK using System.Net.Http; @@ -28,8 +31,8 @@ internal sealed class HttpHandlerMetricsDiagnosticListener : ListenerHandler { internal const string OnStopEvent = "System.Net.Http.HttpRequestOut.Stop"; - private readonly PropertyFetcher stopResponseFetcher = new("Response"); - private readonly PropertyFetcher stopRequestFetcher = new("Request"); + private static readonly PropertyFetcher StopRequestFetcher = new("Request"); + private static readonly PropertyFetcher StopResponseFetcher = new("Response"); private readonly Histogram httpClientDuration; private readonly HttpClientMetricInstrumentationOptions options; private readonly bool emitOldAttributes; @@ -56,7 +59,7 @@ public override void OnEventWritten(string name, object payload) } var activity = Activity.Current; - if (this.stopRequestFetcher.TryFetch(payload, out HttpRequestMessage request) && request != null) + if (TryFetchRequest(payload, out HttpRequestMessage request)) { TagList tags = default; @@ -73,7 +76,7 @@ public override void OnEventWritten(string name, object payload) tags.Add(new KeyValuePair(SemanticConventions.AttributeNetPeerPort, request.RequestUri.Port)); } - if (this.stopResponseFetcher.TryFetch(payload, out HttpResponseMessage response) && response != null) + if (TryFetchResponse(payload, out HttpResponseMessage response)) { tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode))); } @@ -91,7 +94,7 @@ public override void OnEventWritten(string name, object payload) tags.Add(new KeyValuePair(SemanticConventions.AttributeServerPort, request.RequestUri.Port)); } - if (this.stopResponseFetcher.TryFetch(payload, out HttpResponseMessage response) && response != null) + if (TryFetchResponse(payload, out HttpResponseMessage response)) { tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpResponseStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode))); } @@ -103,5 +106,21 @@ public override void OnEventWritten(string name, object payload) this.httpClientDuration.Record(activity.Duration.TotalMilliseconds, tags); } } + + // The AOT-annotation DynamicallyAccessedMembers in System.Net.Http library ensures that top-level properties on the payload object are always preserved. + // see https://github.com/dotnet/runtime/blob/f9246538e3d49b90b0e9128d7b1defef57cd6911/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs#L325 +#if NET6_0_OR_GREATER + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "The event source guarantees that top-level properties are preserved")] +#endif + static bool TryFetchRequest(object payload, out HttpRequestMessage request) => + StopRequestFetcher.TryFetch(payload, out request) && request != null; + + // The AOT-annotation DynamicallyAccessedMembers in System.Net.Http library ensures that top-level properties on the payload object are always preserved. + // see https://github.com/dotnet/runtime/blob/f9246538e3d49b90b0e9128d7b1defef57cd6911/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs#L325 +#if NET6_0_OR_GREATER + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "The event source guarantees that top-level properties are preserved")] +#endif + static bool TryFetchResponse(object payload, out HttpResponseMessage response) => + StopResponseFetcher.TryFetch(payload, out response) && response != null; } }