Skip to content

Commit 6b6bad8

Browse files
[AzureMonitorExporter] Part 1 - Customer sdkstats - Add / Update support class (Azure#52650)
* Add / Update support class * copilot feedback
1 parent 82a1407 commit 6b6bad8

File tree

11 files changed

+658
-1
lines changed

11 files changed

+658
-1
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System.Diagnostics;
5+
using Azure.Monitor.OpenTelemetry.Exporter.Internals.Platform;
6+
7+
namespace Azure.Monitor.OpenTelemetry.Exporter.Internals.CustomerSdkStats
8+
{
9+
/// <summary>
10+
/// Provides dimension values for customer SDK statistics metrics.
11+
/// </summary>
12+
internal static class CustomerSdkStatsDimensions
13+
{
14+
private static string? s_computeType;
15+
16+
/// <summary>
17+
/// Gets base tags common to all customer SDK stats metrics.
18+
/// </summary>
19+
/// <param name="telemetryType">The type of telemetry (REQUEST, DEPENDENCY, etc.)</param>
20+
/// <returns>TagList with common dimensions</returns>
21+
public static TagList GetBaseTags(string telemetryType)
22+
{
23+
return new TagList
24+
{
25+
{ "language", SdkVersionUtils.IsDistro ? "dotnet-distro" : "dotnet-exp" },
26+
{ "version", SdkVersionUtils.ExtensionsVersion.Truncate(SchemaConstants.Tags_AiInternalSdkVersion_MaxLength) },
27+
{ "computeType", GetComputeType() },
28+
{ "telemetry_type", telemetryType }
29+
};
30+
}
31+
32+
/// <summary>
33+
/// Gets tags for dropped telemetry items.
34+
/// </summary>
35+
/// <param name="telemetryType">The type of telemetry</param>
36+
/// <param name="dropCode">The drop code (status code or error category)</param>
37+
/// <param name="dropReason">Optional drop reason description</param>
38+
/// <returns>TagList with dropped item dimensions</returns>
39+
public static TagList GetDroppedTags(string telemetryType, string dropCode, string? dropReason = null)
40+
{
41+
var tags = GetBaseTags(telemetryType);
42+
tags.Add("drop.code", dropCode);
43+
if (!string.IsNullOrEmpty(dropReason))
44+
{
45+
tags.Add("drop.reason", dropReason);
46+
}
47+
return tags;
48+
}
49+
50+
/// <summary>
51+
/// Gets tags for retried telemetry items.
52+
/// </summary>
53+
/// <param name="telemetryType">The type of telemetry</param>
54+
/// <param name="retryCode">The retry code (status code or error category)</param>
55+
/// <param name="retryReason">Optional retry reason description</param>
56+
/// <returns>TagList with retry item dimensions</returns>
57+
public static TagList GetRetryTags(string telemetryType, string retryCode, string? retryReason = null)
58+
{
59+
var tags = GetBaseTags(telemetryType);
60+
tags.Add("retry.code", retryCode);
61+
if (!string.IsNullOrEmpty(retryReason))
62+
{
63+
tags.Add("retry.reason", retryReason);
64+
}
65+
return tags;
66+
}
67+
68+
private static string GetComputeType()
69+
{
70+
if (s_computeType == null)
71+
{
72+
var platform = DefaultPlatform.Instance;
73+
s_computeType = ResourceProviderHelper.DetermineResourceProvider(platform);
74+
}
75+
return s_computeType;
76+
}
77+
}
78+
}
Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.IO;
7+
using System.Net.Http;
8+
using System.Net.Sockets;
9+
using System.Threading.Tasks;
10+
using Azure.Monitor.OpenTelemetry.Exporter.Internals.Diagnostics;
11+
using Azure.Monitor.OpenTelemetry.Exporter.Internals.Platform;
12+
13+
namespace Azure.Monitor.OpenTelemetry.Exporter.Internals.CustomerSdkStats
14+
{
15+
/// <summary>
16+
/// Helper class for tracking customer SDK statistics.
17+
/// </summary>
18+
internal static class CustomerSdkStatsHelper
19+
{
20+
private static bool? s_isEnabled;
21+
22+
private static readonly IReadOnlyDictionary<int, string> StatusCodeDropReasons = new Dictionary<int, string>
23+
{
24+
[400] = "Bad request",
25+
[401] = "Unauthorized",
26+
[402] = "Daily quota exceeded",
27+
[403] = "Forbidden",
28+
[404] = "Not found",
29+
[408] = "Request timeout",
30+
[413] = "Payload too large",
31+
[429] = "Too many requests",
32+
[500] = "Internal server error",
33+
[502] = "Bad gateway",
34+
[503] = "Service unavailable",
35+
[504] = "Gateway timeout"
36+
};
37+
38+
/// <summary>
39+
/// Checks if customer SDK stats are enabled.
40+
/// </summary>
41+
/// <returns>True if enabled, false otherwise</returns>
42+
public static bool IsEnabled()
43+
{
44+
if (s_isEnabled == null)
45+
{
46+
var enabledValue = DefaultPlatform.Instance.GetEnvironmentVariable(EnvironmentVariableConstants.APPLICATIONINSIGHTS_SDKSTATS_ENABLED_PREVIEW);
47+
s_isEnabled = string.Equals(enabledValue, "true", StringComparison.OrdinalIgnoreCase);
48+
}
49+
return s_isEnabled.Value;
50+
}
51+
52+
/// <summary>
53+
/// Gets the configured export interval for customer SDK stats in milliseconds.
54+
/// </summary>
55+
/// <returns>Export interval in milliseconds (default: 900000 = 15 minutes)</returns>
56+
public static int GetExportIntervalMilliseconds()
57+
{
58+
var intervalValue = DefaultPlatform.Instance.GetEnvironmentVariable(EnvironmentVariableConstants.APPLICATIONINSIGHTS_SDKSTATS_EXPORT_INTERVAL);
59+
60+
if (int.TryParse(intervalValue, out int intervalSeconds) && intervalSeconds > 0)
61+
{
62+
var intervalMs = Math.Max(60_000, intervalSeconds * 1_000); // Minimum 1 minute
63+
return Math.Min(intervalMs, 24 * 60 * 60 * 1_000); // Maximum 24 hours
64+
}
65+
66+
return 900_000;
67+
}
68+
69+
public static string? GetDropReason(Exception exception)
70+
{
71+
return exception switch
72+
{
73+
TimeoutException => "Timeout exception",
74+
TaskCanceledException when exception.InnerException is TimeoutException => "Timeout exception",
75+
OperationCanceledException => "Timeout exception",
76+
HttpRequestException => "Network exception",
77+
SocketException => "Network exception",
78+
DriveNotFoundException => "Storage exception",
79+
FileNotFoundException => "Storage exception",
80+
UnauthorizedAccessException => "Storage exception",
81+
PathTooLongException => "Storage exception",
82+
IOException => "Storage exception",
83+
_ => null
84+
};
85+
}
86+
87+
/// <summary>
88+
/// Categorizes an HTTP status code into a human-readable drop reason.
89+
/// Follows the Python categorize_status_code function logic.
90+
/// </summary>
91+
/// <param name="statusCode">HTTP status code to categorize</param>
92+
/// <returns>Human-readable drop reason</returns>
93+
public static string CategorizeStatusCode(int statusCode)
94+
{
95+
if (StatusCodeDropReasons.TryGetValue(statusCode, out var reason))
96+
{
97+
return reason;
98+
}
99+
100+
if (statusCode >= 400 && statusCode < 500)
101+
{
102+
return "Client error 4xx";
103+
}
104+
105+
if (statusCode >= 500 && statusCode < 600)
106+
{
107+
return "Server error 5xx";
108+
}
109+
110+
return $"status_{statusCode}";
111+
}
112+
113+
/// <summary>
114+
/// Tracks successful telemetry transmission using pre-computed counts.
115+
/// </summary>
116+
/// <param name="telemetryCounter">Telemetry counter for tracking metrics</param>
117+
public static void TrackSuccess(TelemetryCounter? telemetryCounter)
118+
{
119+
if (!IsEnabled() || telemetryCounter == null)
120+
return;
121+
122+
try
123+
{
124+
if (telemetryCounter._requestCount != 0)
125+
{
126+
var tags = CustomerSdkStatsDimensions.GetBaseTags("REQUEST");
127+
CustomerSdkStatsMeters.ItemSuccessCount.Add(telemetryCounter._requestCount, tags);
128+
}
129+
130+
if (telemetryCounter._dependencyCount != 0)
131+
{
132+
var tags = CustomerSdkStatsDimensions.GetBaseTags("DEPENDENCY");
133+
CustomerSdkStatsMeters.ItemSuccessCount.Add(telemetryCounter._dependencyCount, tags);
134+
}
135+
136+
if (telemetryCounter._exceptionCount != 0)
137+
{
138+
var tags = CustomerSdkStatsDimensions.GetBaseTags("EXCEPTION");
139+
CustomerSdkStatsMeters.ItemSuccessCount.Add(telemetryCounter._exceptionCount, tags);
140+
}
141+
142+
if (telemetryCounter._eventCount != 0)
143+
{
144+
var tags = CustomerSdkStatsDimensions.GetBaseTags("EVENT");
145+
CustomerSdkStatsMeters.ItemSuccessCount.Add(telemetryCounter._eventCount, tags);
146+
}
147+
148+
if (telemetryCounter._metricCount != 0)
149+
{
150+
var tags = CustomerSdkStatsDimensions.GetBaseTags("METRIC");
151+
CustomerSdkStatsMeters.ItemSuccessCount.Add(telemetryCounter._metricCount, tags);
152+
}
153+
154+
if (telemetryCounter._traceCount != 0)
155+
{
156+
var tags = CustomerSdkStatsDimensions.GetBaseTags("TRACE");
157+
CustomerSdkStatsMeters.ItemSuccessCount.Add(telemetryCounter._traceCount, tags);
158+
}
159+
}
160+
catch (Exception ex)
161+
{
162+
AzureMonitorExporterEventSource.Log.CustomerSdkStatsTrackingFailed("success", ex);
163+
}
164+
}
165+
166+
/// <summary>
167+
/// Tracks dropped telemetry using pre-computed counts.
168+
/// According to the specification:
169+
/// - For non-retryable status codes: use the actual HTTP status code (401, 403, etc.) as dropCode
170+
/// - For other scenarios: use DropCode enum values (CLIENT_EXCEPTION, CLIENT_READONLY, etc.)
171+
/// </summary>
172+
/// <param name="persistentBlobProviderExists">Indicates if persistent blob storage is configured</param>
173+
/// <param name="telemetryCounter">Telemetry counter for tracking metrics</param>
174+
public static void TrackDropped(TelemetryCounter? telemetryCounter, bool persistentBlobProviderExists)
175+
{
176+
TrackDropped(telemetryCounter, (int)(persistentBlobProviderExists == false ? DropCode.ClientStorageDisabled : DropCode.ClientPersistenceIssue), null);
177+
}
178+
179+
/// <summary>
180+
/// Tracks dropped telemetry using pre-computed counts.
181+
/// </summary>
182+
/// <param name="telemetryCounter">Telemetry counter for tracking metrics</param>
183+
/// <param name="dropCode">Drop code - either HTTP status code or DropCode enum value cast to int</param>
184+
/// <param name="dropReason">Optional detailed reason for the dropped telemetry</param>
185+
public static void TrackDropped(TelemetryCounter? telemetryCounter, int dropCode, string? dropReason = null)
186+
{
187+
if (!IsEnabled() || telemetryCounter == null)
188+
return;
189+
190+
try
191+
{
192+
string? dropCodeString = null;
193+
194+
if (dropCode > 0 && dropCode < 10)
195+
{
196+
DropCode enumValue = (DropCode)dropCode;
197+
dropCodeString = enumValue.ToString();
198+
}
199+
200+
dropCodeString ??= dropCode.ToString();
201+
202+
if (telemetryCounter._requestCount != 0)
203+
{
204+
var tags = CustomerSdkStatsDimensions.GetDroppedTags("REQUEST", dropCodeString, dropReason);
205+
CustomerSdkStatsMeters.ItemDroppedCount.Add(telemetryCounter._requestCount, tags);
206+
}
207+
208+
if (telemetryCounter._dependencyCount != 0)
209+
{
210+
var tags = CustomerSdkStatsDimensions.GetDroppedTags("DEPENDENCY", dropCodeString, dropReason);
211+
CustomerSdkStatsMeters.ItemDroppedCount.Add(telemetryCounter._dependencyCount, tags);
212+
}
213+
214+
if (telemetryCounter._exceptionCount != 0)
215+
{
216+
var tags = CustomerSdkStatsDimensions.GetDroppedTags("EXCEPTION", dropCodeString, dropReason);
217+
CustomerSdkStatsMeters.ItemDroppedCount.Add(telemetryCounter._exceptionCount, tags);
218+
}
219+
220+
if (telemetryCounter._eventCount != 0)
221+
{
222+
var tags = CustomerSdkStatsDimensions.GetDroppedTags("EVENT", dropCodeString, dropReason);
223+
CustomerSdkStatsMeters.ItemDroppedCount.Add(telemetryCounter._eventCount, tags);
224+
}
225+
226+
if (telemetryCounter._metricCount != 0)
227+
{
228+
var tags = CustomerSdkStatsDimensions.GetDroppedTags("METRIC", dropCodeString, dropReason);
229+
CustomerSdkStatsMeters.ItemDroppedCount.Add(telemetryCounter._metricCount, tags);
230+
}
231+
232+
if (telemetryCounter._traceCount != 0)
233+
{
234+
var tags = CustomerSdkStatsDimensions.GetDroppedTags("TRACE", dropCodeString, dropReason);
235+
CustomerSdkStatsMeters.ItemDroppedCount.Add(telemetryCounter._traceCount, tags);
236+
}
237+
}
238+
catch (Exception ex)
239+
{
240+
AzureMonitorExporterEventSource.Log.CustomerSdkStatsTrackingFailed("dropped", ex);
241+
}
242+
}
243+
244+
/// <summary>
245+
/// Tracks retry telemetry using pre-computed counts.
246+
/// </summary>
247+
/// <param name="telemetryCounter">Telemetry counter for tracking metrics</param>
248+
/// <param name="retryCode">retry code - either HTTP status code or DropCode enum value cast to int</param>
249+
/// <param name="retryReason">Optional detailed reason for the retry telemetry</param>
250+
public static void TrackRetry(TelemetryCounter? telemetryCounter, int retryCode, string? retryReason = null)
251+
{
252+
if (!IsEnabled() || telemetryCounter == null)
253+
return;
254+
255+
try
256+
{
257+
string? retryCodeString = null;
258+
259+
if (retryCode > 0 && retryCode < 10)
260+
{
261+
DropCode enumValue = (DropCode)retryCode;
262+
retryCodeString = enumValue.ToString();
263+
}
264+
265+
retryCodeString ??= retryCode.ToString();
266+
267+
if (telemetryCounter._requestCount != 0)
268+
{
269+
var tags = CustomerSdkStatsDimensions.GetRetryTags("REQUEST", retryCodeString, retryReason);
270+
CustomerSdkStatsMeters.ItemRetryCount.Add(telemetryCounter._requestCount, tags);
271+
}
272+
273+
if (telemetryCounter._dependencyCount != 0)
274+
{
275+
var tags = CustomerSdkStatsDimensions.GetRetryTags("DEPENDENCY", retryCodeString, retryReason);
276+
CustomerSdkStatsMeters.ItemRetryCount.Add(telemetryCounter._dependencyCount, tags);
277+
}
278+
279+
if (telemetryCounter._exceptionCount != 0)
280+
{
281+
var tags = CustomerSdkStatsDimensions.GetRetryTags("EXCEPTION", retryCodeString, retryReason);
282+
CustomerSdkStatsMeters.ItemRetryCount.Add(telemetryCounter._exceptionCount, tags);
283+
}
284+
285+
if (telemetryCounter._eventCount != 0)
286+
{
287+
var tags = CustomerSdkStatsDimensions.GetRetryTags("EVENT", retryCodeString, retryReason);
288+
CustomerSdkStatsMeters.ItemRetryCount.Add(telemetryCounter._eventCount, tags);
289+
}
290+
291+
if (telemetryCounter._metricCount != 0)
292+
{
293+
var tags = CustomerSdkStatsDimensions.GetRetryTags("METRIC", retryCodeString, retryReason);
294+
CustomerSdkStatsMeters.ItemRetryCount.Add(telemetryCounter._metricCount, tags);
295+
}
296+
297+
if (telemetryCounter._traceCount != 0)
298+
{
299+
var tags = CustomerSdkStatsDimensions.GetRetryTags("TRACE", retryCodeString, retryReason);
300+
CustomerSdkStatsMeters.ItemRetryCount.Add(telemetryCounter._traceCount, tags);
301+
}
302+
}
303+
catch (Exception ex)
304+
{
305+
AzureMonitorExporterEventSource.Log.CustomerSdkStatsTrackingFailed("retry", ex);
306+
}
307+
}
308+
}
309+
}

0 commit comments

Comments
 (0)