Skip to content

Commit dd0510e

Browse files
author
Timothy Mothra
authored
[AzureMonitorExporter] resolve AOT warnings (Azure#38459)
* initial commit * build fix * workaround for StackFrame.GetMethod() * cleanup * fix test * remove TrimmingAttribute.cs * temp disable ApiCompat * cleanup * test fix for ApiCompat * update comment * add aotcompat test app * add readme * readme * recommended fix for ApiCompat * comment * test fix for validation errors re: aotcompat directory * isolate StackFrame.GetMethod * cleanup * isolate StackFrame.GetMethod (2) * add comment. * fix * pr feedback * update comment * fix script * refactor as extension method * cleanup
1 parent 9c11169 commit dd0510e

File tree

14 files changed

+204
-16
lines changed

14 files changed

+204
-16
lines changed

sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Azure.Monitor.OpenTelemetry.Exporter.csproj

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<Description>An OpenTelemetry .NET exporter that exports to Azure Monitor</Description>
44
<AssemblyTitle>AzureMonitor OpenTelemetry Exporter</AssemblyTitle>
55
<Version>1.1.0-beta.1</Version>
66
<!--The ApiCompatVersion is managed automatically and should not generally be modified manually.-->
7-
<ApiCompatVersion>1.0.0</ApiCompatVersion>
7+
<ApiCompatVersion Condition="'$(TargetFramework)' == 'netstandard2.0'">1.0.0</ApiCompatVersion>
88
<PackageTags>Azure Monitor OpenTelemetry Exporter ApplicationInsights</PackageTags>
9-
<TargetFrameworks>$(RequiredTargetFrameworks)</TargetFrameworks>
9+
<!--NET6 is added here for trimming compatibility. This includes necessary annotations and System.Text.Json's "Source Generation" feature.-->
10+
<TargetFrameworks>net6.0;$(RequiredTargetFrameworks)</TargetFrameworks>
1011
<IncludeOperationsSharedSource>true</IncludeOperationsSharedSource>
1112
</PropertyGroup>
1213

sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Customizations/Models/StackFrame.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@ public StackFrame(System.Diagnostics.StackFrame stackFrame, int frameId)
1414
{
1515
string fullName, assemblyName;
1616

17-
var methodInfo = stackFrame.GetMethod();
17+
var methodInfo = stackFrame.GetMethodWithoutWarning();
1818
if (methodInfo == null)
1919
{
20-
fullName = "unknown";
20+
// In an AOT scenario GetMethod() will return null. Note this can happen even in non AOT scenarios.
21+
// Instead, call ToString() which gives a string like this:
22+
// "MethodName + 0x00 at offset 000 in file:line:column <filename unknown>:0:0"
23+
fullName = stackFrame.ToString();
2124
assemblyName = "unknown";
2225
}
2326
else

sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/Diagnostics/AzureMonitorExporterEventSource.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
using System;
5+
using System.Diagnostics.CodeAnalysis;
56
using System.Diagnostics.Tracing;
67
using System.Runtime.CompilerServices;
78
using Azure.Monitor.OpenTelemetry.Exporter.Internals.ConnectionString;
@@ -87,7 +88,7 @@ public void TransmissionFailed(int statusCode, TelemetryItemOrigin origin, bool
8788
isAadEnabled: isAadEnabled,
8889
instrumentationKey: connectionVars.InstrumentationKey,
8990
configuredEndpoint: connectionVars.IngestionEndpoint,
90-
actualEndpoint: requestEndpoint);
91+
actualEndpoint: requestEndpoint ?? "null");
9192
}
9293
else
9394
{
@@ -101,7 +102,7 @@ public void TransmissionFailed(int statusCode, TelemetryItemOrigin origin, bool
101102
isAadEnabled: isAadEnabled,
102103
instrumentationKey: connectionVars.InstrumentationKey,
103104
configuredEndpoint: connectionVars.IngestionEndpoint,
104-
actualEndpoint: requestEndpoint);
105+
actualEndpoint: requestEndpoint ?? "null");
105106
}
106107
}
107108
}
@@ -115,12 +116,13 @@ public void TransmissionFailed(int statusCode, TelemetryItemOrigin origin, bool
115116
isAadEnabled: isAadEnabled,
116117
instrumentationKey: connectionVars.InstrumentationKey,
117118
configuredEndpoint: connectionVars.IngestionEndpoint,
118-
actualEndpoint: requestEndpoint);
119+
actualEndpoint: requestEndpoint ?? "null");
119120
}
120121
}
121122

123+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Parameters to this method are primitive and are trimmer safe.")]
122124
[Event(8, Message = "Transmission failed. StatusCode: {0}. Error from Ingestion: {1}. Action: {2}. Origin: {3}. AAD Enabled: {4}. Instrumentation Key: {5}. Configured Endpoint: {6}. Actual Endpoint: {7}", Level = EventLevel.Critical)]
123-
public void TransmissionFailed(int statusCode, string errorMessage, string action, string origin, bool isAadEnabled, string instrumentationKey, string configuredEndpoint, string? actualEndpoint)
125+
public void TransmissionFailed(int statusCode, string errorMessage, string action, string origin, bool isAadEnabled, string instrumentationKey, string configuredEndpoint, string actualEndpoint)
124126
=> WriteEvent(8, statusCode, errorMessage, action, origin, isAadEnabled, instrumentationKey, configuredEndpoint, actualEndpoint);
125127

126128
[Event(9, Message = "{0} has been disposed.", Level = EventLevel.Informational)]
@@ -213,6 +215,7 @@ public void FailedToExtractActivityEvent(string activitySourceName, string activ
213215
[Event(17, Message = "Failed to extract Activity Event due to an exception. This telemetry item will be lost. ActivitySource: {0}. Activity: {1}. {2}", Level = EventLevel.Error)]
214216
public void FailedToExtractActivityEvent(string activitySourceName, string activityDisplayName, string exceptionMessage) => WriteEvent(17, activitySourceName, activityDisplayName, exceptionMessage);
215217

218+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Parameters to this method are primitive and are trimmer safe.")]
216219
[Event(18, Message = "Maximum count of {0} Activity Links reached. Excess Links are dropped. ActivitySource: {1}. Activity: {2}.", Level = EventLevel.Warning)]
217220
public void ActivityLinksIgnored(int maxLinksAllowed, string activitySourceName, string activityDisplayName) => WriteEvent(18, maxLinksAllowed, activitySourceName, activityDisplayName);
218221

@@ -279,9 +282,11 @@ public void FailedToTransmitFromStorage(bool isAadEnabled, string instrumentatio
279282
}
280283
}
281284

285+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Parameters to this method are primitive and are trimmer safe.")]
282286
[Event(25, Message = "Failed to transmit from storage due to an exception. AAD Enabled: {0}. Instrumentation Key: {1}. {2}", Level = EventLevel.Error)]
283287
public void FailedToTransmitFromStorage(bool isAadEnabled, string instrumentationKey, string exceptionMessage) => WriteEvent(25, isAadEnabled, instrumentationKey, exceptionMessage);
284288

289+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Parameters to this method are primitive and are trimmer safe.")]
285290
[Event(26, Message = "Successfully transmitted a blob from storage. AAD Enabled: {0}. Instrumentation Key: {1}", Level = EventLevel.Verbose)]
286291
public void TransmitFromStorageSuccess(bool isAadEnabled, string instrumentationKey) => WriteEvent(26, isAadEnabled, instrumentationKey);
287292

@@ -327,6 +332,7 @@ public void TransmissionSuccess(TelemetryItemOrigin origin, bool isAadEnabled, s
327332
}
328333
}
329334

335+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Parameters to this method are primitive and are trimmer safe.")]
330336
[Event(32, Message = "Successfully transmitted a batch of telemetry Items. Origin: {0}. AAD: {1}. Instrumentation Key: {2}", Level = EventLevel.Verbose)]
331337
public void TransmissionSuccess(string origin, bool isAadEnabled, string instrumentationKey) => WriteEvent(32, origin, isAadEnabled, instrumentationKey);
332338

@@ -339,9 +345,11 @@ public void TransmitterFailed(TelemetryItemOrigin origin, bool isAadEnabled, str
339345
}
340346
}
341347

348+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Parameters to this method are primitive and are trimmer safe.")]
342349
[Event(33, Message = "Transmitter failed due to an exception. Origin: {0}. AAD: {1}. Instrumentation Key: {2}. {3}", Level = EventLevel.Error)]
343350
public void TransmitterFailed(string origin, bool isAadEnabled, string instrumentationKey, string exceptionMessage) => WriteEvent(33, origin, isAadEnabled, instrumentationKey, exceptionMessage);
344351

352+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Parameters to this method are primitive and are trimmer safe.")]
345353
[Event(34, Message = "Exporter encountered a transmission failure and will wait {0} milliseconds before transmitting again.", Level = EventLevel.Warning)]
346354
public void BackoffEnabled(double milliseconds) => WriteEvent(34, milliseconds);
347355

@@ -372,6 +380,7 @@ public void PartialContentResponseInvalid(int totalTelemetryItems, TelemetryErro
372380
}
373381
}
374382

383+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Parameters to this method are primitive and are trimmer safe.")]
375384
[Event(38, Message = "Received a partial success from ingestion that does not match telemetry that was sent. Total telemetry items sent: {0}. Error Index: {1}. Error StatusCode: {2}. Error Message: {3}", Level = EventLevel.Warning)]
376385
public void PartialContentResponseInvalid(int totalTelemetryItems, string errorIndex, string errorStatusCode, string errorMessage) => WriteEvent(38, totalTelemetryItems, errorIndex, errorStatusCode, errorMessage);
377386

sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/IngestionResponseHelper.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,13 @@ internal static class IngestionResponseHelper
2323

2424
var responseContent = response.Content.ToString();
2525

26-
var responseObj = JsonSerializer.Deserialize<ResponseObject>(responseContent);
26+
ResponseObject? responseObj;
27+
28+
#if NET6_0_OR_GREATER
29+
responseObj = JsonSerializer.Deserialize<ResponseObject>(responseContent, SourceGenerationContext.Default.ResponseObject);
30+
#else
31+
responseObj = JsonSerializer.Deserialize<ResponseObject>(responseContent);
32+
#endif
2733

2834
if (responseObj == null || responseObj.Errors == null)
2935
{
@@ -39,7 +45,8 @@ internal static class IngestionResponseHelper
3945
}
4046
}
4147

42-
private class ResponseObject
48+
// This class needs to be internal rather than private so that it can be used by the System.Text.Json source generator
49+
internal class ResponseObject
4350
{
4451
[JsonPropertyName("itemsReceived")]
4552
public int ItemsReceived { get; set; }
@@ -51,7 +58,8 @@ private class ResponseObject
5158
public List<ErrorObject>? Errors { get; set; }
5259
}
5360

54-
private class ErrorObject
61+
// This class needs to be internal rather than private so that it can be used by the System.Text.Json source generator
62+
internal class ErrorObject
5563
{
5664
[JsonPropertyName("index")]
5765
public int Index { get; set; }

sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/LogsHelper.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,17 @@ internal static string GetProblemId(Exception exception)
140140

141141
if (exceptionStackFrame != null)
142142
{
143-
MethodBase? methodBase = exceptionStackFrame.GetMethod();
143+
MethodBase? methodBase = exceptionStackFrame.GetMethodWithoutWarning();
144144

145-
if (methodBase != null)
145+
if (methodBase == null)
146+
{
147+
// In an AOT scenario GetMethod() will return null.
148+
// Instead, call ToString() which gives a string like this:
149+
// "MethodName + 0x00 at offset 000 in file:line:column <filename unknown>:0:0"
150+
methodName = exceptionStackFrame.ToString();
151+
methodOffset = System.Diagnostics.StackFrame.OFFSET_UNKNOWN;
152+
}
153+
else
146154
{
147155
methodName = (methodBase.DeclaringType?.FullName ?? "Global") + "." + methodBase.Name;
148156
methodOffset = exceptionStackFrame.GetILOffset();
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using Azure.Monitor.OpenTelemetry.Exporter.Internals.Statsbeat;
5+
using System.Text.Json.Serialization;
6+
7+
namespace Azure.Monitor.OpenTelemetry.Exporter.Internals;
8+
9+
#if NET6_0_OR_GREATER
10+
// "Source Generation" is feature added to System.Text.Json in .NET 6.0.
11+
// This is a performance optimization that avoids runtime reflection when performing serialization.
12+
// Serialization metadata will be computed at compile-time and included in the assembly.
13+
// https://learn.microsoft.com/dotnet/standard/serialization/system-text-json/source-generation-modes
14+
// https://learn.microsoft.com/dotnet/standard/serialization/system-text-json/source-generation
15+
// https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-source-generator/
16+
[JsonSerializable(typeof(VmMetadataResponse))]
17+
[JsonSerializable(typeof(string))]
18+
[JsonSerializable(typeof(IngestionResponseHelper.ResponseObject))]
19+
[JsonSerializable(typeof(IngestionResponseHelper.ErrorObject))]
20+
[JsonSerializable(typeof(int))]
21+
internal partial class SourceGenerationContext : JsonSerializerContext
22+
{
23+
}
24+
#endif
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System.Diagnostics.CodeAnalysis;
5+
using System.Reflection;
6+
using System.Runtime.CompilerServices;
7+
8+
namespace Azure.Monitor.OpenTelemetry.Exporter.Internals
9+
{
10+
internal static class StackFrameExtensions
11+
{
12+
/// <summary>
13+
/// Wrapper for <see cref="System.Diagnostics.StackFrame.GetMethod"/>.
14+
/// This method disables the Trimmer warning "IL2026:RequiresUnreferencedCode".
15+
/// Callers MUST handle the null condition.
16+
/// </summary>
17+
/// <remarks>
18+
/// In an AOT scenario GetMethod() will return null. Note this can happen even in non AOT scenarios.
19+
/// Instead, call ToString() which gives a string like this:
20+
/// "MethodName + 0x00 at offset 000 in file:line:column filename unknown:0:0".
21+
/// </remarks>
22+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
23+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "We will handle the null condition and call ToString() instead.")]
24+
public static MethodBase? GetMethodWithoutWarning(this System.Diagnostics.StackFrame stackFrame) => stackFrame.GetMethod();
25+
}
26+
}

sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/Statsbeat/AzureMonitorStatsbeat.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,12 @@ private Measurement<int> GetAttachStatsbeat()
133133
{
134134
httpClient.DefaultRequestHeaders.Add("Metadata", "True");
135135
var responseString = httpClient.GetStringAsync(StatsbeatConstants.AMS_Url);
136-
var vmMetadata = JsonSerializer.Deserialize<VmMetadataResponse>(responseString.Result);
136+
VmMetadataResponse? vmMetadata;
137+
#if NET6_0_OR_GREATER
138+
vmMetadata = JsonSerializer.Deserialize<VmMetadataResponse>(responseString.Result, SourceGenerationContext.Default.VmMetadataResponse);
139+
#else
140+
vmMetadata = JsonSerializer.Deserialize<VmMetadataResponse>(responseString.Result);
141+
#endif
137142

138143
return vmMetadata;
139144
}

sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/Statsbeat/VmMetadataResponse.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
namespace Azure.Monitor.OpenTelemetry.Exporter.Internals.Statsbeat
55
{
6+
// This class needs to be internal rather than private so that it can be used by the System.Text.Json source generator
67
internal class VmMetadataResponse
78
{
89
public string? osType { get; set; }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<OutputType>Exe</OutputType>
4+
<TargetFrameworks>net7.0;net6.0;net462</TargetFrameworks>
5+
<IsTestSupportProject>true</IsTestSupportProject>
6+
</PropertyGroup>
7+
8+
<PropertyGroup Condition="'$(TargetFramework)' == 'net7.0'">
9+
<PublishAot>true</PublishAot>
10+
<TrimmerSingleWarn>false</TrimmerSingleWarn>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<ProjectReference Include="..\..\..\..\core\Azure.Core\src\Azure.Core.csproj" />
15+
<ProjectReference Include="..\..\src\Azure.Monitor.OpenTelemetry.Exporter.csproj" />
16+
17+
<!-- Update this dependency to its latest, which has all the annotations -->
18+
<PackageReference Include="Microsoft.Extensions.Logging.Configuration" VersionOverride="8.0.0-rc.1.23419.4" />
19+
</ItemGroup>
20+
21+
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
22+
<TrimmerRootAssembly Include="Azure.Monitor.OpenTelemetry.Exporter" />
23+
</ItemGroup>
24+
25+
<!--Temp fix for "error : No files matching ;NU5105;CA1812;"-->
26+
<!--Real fix coming in .NET 8 RC2: https://github.com/dotnet/runtime/issues/91965-->
27+
<ItemGroup>
28+
<_NoWarn Include="$(NoWarn)" />
29+
</ItemGroup>
30+
<PropertyGroup>
31+
<NoWarn>@(_NoWarn)</NoWarn>
32+
</PropertyGroup>
33+
34+
</Project>

0 commit comments

Comments
 (0)