From ebcb2b861f73ab40a33a603054dfda67bb7012e6 Mon Sep 17 00:00:00 2001 From: MattB Date: Sat, 20 Jun 2026 16:01:38 -0700 Subject: [PATCH 1/6] Refactor OpenTelemetry configuration in AgentOtelExtension --- samples/dotnet/otel/AgentOtelExtension.cs | 131 +++++++++++++++------- 1 file changed, 91 insertions(+), 40 deletions(-) diff --git a/samples/dotnet/otel/AgentOtelExtension.cs b/samples/dotnet/otel/AgentOtelExtension.cs index 65c13c98..10b08fb1 100644 --- a/samples/dotnet/otel/AgentOtelExtension.cs +++ b/samples/dotnet/otel/AgentOtelExtension.cs @@ -20,26 +20,19 @@ namespace Otel // To learn more about using the local aspire desktop, see https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/dashboard/standalone?tabs=bash public static class AgentOtelExtension { - public static TBuilder ConfigureOtelProviders(this TBuilder builder) where TBuilder : IHostApplicationBuilder { - + builder.Services.AddOpenTelemetry() .ConfigureResource(resource => resource.AddService( - serviceName: AgentTelemetry.ServiceName, - serviceVersion: AgentTelemetry.ServiceVersion - ) - .AddAttributes(new[] - { - new KeyValuePair("service.instance.id", Environment.MachineName ?? "unknown"), - new KeyValuePair("telemetry.sdk.language", "dotnet") - })) + serviceName: AgentsTelemetry.SourceName, + serviceVersion: AgentsTelemetry.SourceVersion + )) .WithTracing(tracing => tracing .AddSource( "Microsoft.AspNetCore", "System.Net.Http", - AgentsTelemetry.SourceName, - AgentTelemetry.ServiceName + AgentsTelemetry.SourceName ) .SetSampler(new AlwaysOnSampler()) .AddAspNetCoreInstrumentation(tracing => @@ -50,10 +43,13 @@ public static TBuilder ConfigureOtelProviders(this TBuilder builder) w { activity.SetTag("http.request.body.size", request.ContentLength); activity.SetTag("user_agent", request.Headers.UserAgent); + ExtractHeadersForOTEL(activity, request.Headers, "http.request.headers"); + }; tracing.EnrichWithHttpResponse = (activity, response) => { activity.SetTag("http.response.body.size", response.ContentLength); + ExtractHeadersForOTEL(activity, response.Headers, "http.response.headers"); }; }) .AddHttpClientInstrumentation(o => @@ -65,28 +61,12 @@ public static TBuilder ConfigureOtelProviders(this TBuilder builder) w activity.SetTag("http.request.method", request.Method); activity.SetTag("http.request.host", request.RequestUri?.Host); activity.SetTag("http.request.useragent", request.Headers?.UserAgent); + ExtractHeadersForOTEL(activity, request.Headers, "http.request.headers"); }; o.EnrichWithHttpResponseMessage = (activity, response) => { activity.SetTag("http.response.status_code", (int)response.StatusCode); - // Convert response.Content.Headers to a string array: "HeaderName=val1,val2" - var headerList = response.Content?.Headers? - .Where(h => h.Key != "Authorization") - .Select(h => $"{h.Key}={string.Join(",", h.Value)}") - .ToArray(); - - if (headerList is { Length: > 0 }) - { - // Set as an array tag (preferred for OTEL exporters supporting array-of-primitive attributes) - activity.SetTag("http.response.headers", headerList); - - // (Optional) Also emit individual header tags (comment out if too high-cardinality) - // foreach (var h in response.Content.Headers) - // { - // activity.SetTag($"http.response.header.{h.Key.ToLowerInvariant()}", string.Join(",", h.Value)); - // } - } - + ExtractHeadersForOTEL(activity, response.Headers, "http.response.headers"); }; }) .AddOtlpExporter()) @@ -94,24 +74,95 @@ public static TBuilder ConfigureOtelProviders(this TBuilder builder) w .AddAspNetCoreInstrumentation() .AddHttpClientInstrumentation() .AddRuntimeInstrumentation() - .AddMeter(AgentsTelemetry.SourceName, AgentTelemetry.ServiceName) + .AddMeter(AgentsTelemetry.SourceName) .AddOtlpExporter()); - + builder.Logging.AddOpenTelemetry(logging => { logging.IncludeFormattedMessage = true; logging.IncludeScopes = true; logging.AddOtlpExporter(); }); - - // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) - //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) - //{ - // builder.Services.AddOpenTelemetry() - // .UseAzureMonitor(); - //} - + + // Enable Azure Monitor exporter if Application Insights connection string is configured + if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) + { + builder.Services.AddOpenTelemetry() + .UseAzureMonitor(); + } + return builder; } + + /// + /// Extracts HTTP headers from the request or response and adds them as tags to the OpenTelemetry activity. + /// + /// The OpenTelemetry activity to which the header tags will be added. + /// The HTTP content headers to extract. If null, no tags will be added. + /// The name of the tag to use when adding headers to the activity (e.g., "http.request.headers" or "http.response.headers"). + /// + /// This method filters out the "Authorization" header for security reasons and formats the remaining headers + /// as "HeaderName=value1,value2" strings. The headers are then added to the activity as an array tag, + /// which is compatible with OpenTelemetry exporters that support array-of-primitive attributes. + /// + private static void ExtractHeadersForOTEL(System.Diagnostics.Activity activity, HttpHeaders? request, string tagName) + { + + if (request == null) + { + return; + } + + var headerList = request//.Where(h => h.Key != "Authorization") + .Select(h => $"{h.Key}={string.Join(",", h.Value)}") + .ToArray(); + + if (headerList is { Length: > 0 }) + { + // Set as an array tag (preferred for OTEL exporters supporting array-of-primitive attributes) + activity.SetTag(tagName, headerList); + + // (Optional) Also emit individual header tags (comment out if too high-cardinality) + // foreach (var h in response.Content.Headers) + // { + // activity.SetTag($"http.response.header.{h.Key.ToLowerInvariant()}", string.Join(",", h.Value)); + // } + } + } + + /// + /// Extracts HTTP headers from the request or response and adds them as tags to the OpenTelemetry activity. + /// + /// The OpenTelemetry activity to which the header tags will be added. + /// The HTTP content headers to extract. If null, no tags will be added. + /// The name of the tag to use when adding headers to the activity (e.g., "http.request.headers" or "http.response.headers"). + /// + /// This method filters out the "Authorization" header for security reasons and formats the remaining headers + /// as "HeaderName=value1,value2" strings. The headers are then added to the activity as an array tag, + /// which is compatible with OpenTelemetry exporters that support array-of-primitive attributes. + /// + private static void ExtractHeadersForOTEL(System.Diagnostics.Activity activity, IHeaderDictionary? request, string tagName) + { + + if (request == null) + { + return; + } + var headerList = request//.Where(h => h.Key != "Authorization") + .Select(h => $"{h.Key}={string.Join(",", h.Value)}") + .ToArray(); + + if (headerList is { Length: > 0 }) + { + // Set as an array tag (preferred for OTEL exporters supporting array-of-primitive attributes) + activity.SetTag(tagName, headerList); + + // (Optional) Also emit individual header tags (comment out if too high-cardinality) + // foreach (var h in response.Content.Headers) + // { + // activity.SetTag($"http.response.header.{h.Key.ToLowerInvariant()}", string.Join(",", h.Value)); + // } + } + } } } From 597f7d099cc3212903d0939bf3a303fe7702afc8 Mon Sep 17 00:00:00 2001 From: MattB Date: Sat, 20 Jun 2026 16:02:35 -0700 Subject: [PATCH 2/6] Uncomment header filtering for Authorization key --- samples/dotnet/otel/AgentOtelExtension.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/dotnet/otel/AgentOtelExtension.cs b/samples/dotnet/otel/AgentOtelExtension.cs index 10b08fb1..3b599057 100644 --- a/samples/dotnet/otel/AgentOtelExtension.cs +++ b/samples/dotnet/otel/AgentOtelExtension.cs @@ -113,7 +113,7 @@ private static void ExtractHeadersForOTEL(System.Diagnostics.Activity activity, return; } - var headerList = request//.Where(h => h.Key != "Authorization") + var headerList = request.Where(h => h.Key != "Authorization") .Select(h => $"{h.Key}={string.Join(",", h.Value)}") .ToArray(); @@ -148,7 +148,7 @@ private static void ExtractHeadersForOTEL(System.Diagnostics.Activity activity, { return; } - var headerList = request//.Where(h => h.Key != "Authorization") + var headerList = request.Where(h => h.Key != "Authorization") .Select(h => $"{h.Key}={string.Join(",", h.Value)}") .ToArray(); From 537344c3bf83350fa937ced0e05871d2d1e512be Mon Sep 17 00:00:00 2001 From: MattB Date: Sat, 20 Jun 2026 16:10:34 -0700 Subject: [PATCH 3/6] Update OpenTelemetry configuration in AgentOtelExtension Removed Azure Monitor exporter configuration and added attributes for service instance ID and telemetry SDK language. --- samples/dotnet/otel/AgentOtelExtension.cs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/samples/dotnet/otel/AgentOtelExtension.cs b/samples/dotnet/otel/AgentOtelExtension.cs index 3b599057..a7c708d0 100644 --- a/samples/dotnet/otel/AgentOtelExtension.cs +++ b/samples/dotnet/otel/AgentOtelExtension.cs @@ -27,7 +27,12 @@ public static TBuilder ConfigureOtelProviders(this TBuilder builder) w .ConfigureResource(resource => resource.AddService( serviceName: AgentsTelemetry.SourceName, serviceVersion: AgentsTelemetry.SourceVersion - )) + ) + .AddAttributes(new[] + { + new KeyValuePair("service.instance.id", Environment.MachineName ?? "unknown"), + new KeyValuePair("telemetry.sdk.language", "dotnet") + })) .WithTracing(tracing => tracing .AddSource( "Microsoft.AspNetCore", @@ -83,14 +88,13 @@ public static TBuilder ConfigureOtelProviders(this TBuilder builder) w logging.IncludeScopes = true; logging.AddOtlpExporter(); }); - - // Enable Azure Monitor exporter if Application Insights connection string is configured - if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) - { - builder.Services.AddOpenTelemetry() - .UseAzureMonitor(); - } - + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) + //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) + //{ + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + //} return builder; } From b43f94dd216523beda22902ee116d061d3db5ee0 Mon Sep 17 00:00:00 2001 From: MattB Date: Sat, 20 Jun 2026 16:42:54 -0700 Subject: [PATCH 4/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- samples/dotnet/otel/AgentOtelExtension.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/dotnet/otel/AgentOtelExtension.cs b/samples/dotnet/otel/AgentOtelExtension.cs index a7c708d0..49748e37 100644 --- a/samples/dotnet/otel/AgentOtelExtension.cs +++ b/samples/dotnet/otel/AgentOtelExtension.cs @@ -117,7 +117,7 @@ private static void ExtractHeadersForOTEL(System.Diagnostics.Activity activity, return; } - var headerList = request.Where(h => h.Key != "Authorization") + var headerList = request.Where(h => !string.Equals(h.Key, "Authorization", StringComparison.OrdinalIgnoreCase)) .Select(h => $"{h.Key}={string.Join(",", h.Value)}") .ToArray(); From 79ae18f231466d8c188c6189d7fadd6377b41cff Mon Sep 17 00:00:00 2001 From: MattB Date: Sat, 20 Jun 2026 16:44:25 -0700 Subject: [PATCH 5/6] Fix header filtering to ignore case for Authorization --- samples/dotnet/otel/AgentOtelExtension.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/samples/dotnet/otel/AgentOtelExtension.cs b/samples/dotnet/otel/AgentOtelExtension.cs index 49748e37..b29c297c 100644 --- a/samples/dotnet/otel/AgentOtelExtension.cs +++ b/samples/dotnet/otel/AgentOtelExtension.cs @@ -12,6 +12,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Agents.Core.Telemetry; +using System.Net.Http.Headers; namespace Otel { @@ -152,7 +153,7 @@ private static void ExtractHeadersForOTEL(System.Diagnostics.Activity activity, { return; } - var headerList = request.Where(h => h.Key != "Authorization") + var headerList = request.Where(h => !string.Equals(h.Key, "Authorization", StringComparison.OrdinalIgnoreCase)) .Select(h => $"{h.Key}={string.Join(",", h.Value)}") .ToArray(); From 821df326b2db0cbf7e94d3c8dcea2403d8dab665 Mon Sep 17 00:00:00 2001 From: MattB Date: Fri, 26 Jun 2026 10:15:56 -0700 Subject: [PATCH 6/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- samples/dotnet/otel/AgentOtelExtension.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/dotnet/otel/AgentOtelExtension.cs b/samples/dotnet/otel/AgentOtelExtension.cs index b29c297c..bad698e5 100644 --- a/samples/dotnet/otel/AgentOtelExtension.cs +++ b/samples/dotnet/otel/AgentOtelExtension.cs @@ -110,7 +110,7 @@ public static TBuilder ConfigureOtelProviders(this TBuilder builder) w /// as "HeaderName=value1,value2" strings. The headers are then added to the activity as an array tag, /// which is compatible with OpenTelemetry exporters that support array-of-primitive attributes. /// - private static void ExtractHeadersForOTEL(System.Diagnostics.Activity activity, HttpHeaders? request, string tagName) + private static void ExtractHeadersForOTEL(System.Diagnostics.Activity activity, System.Net.Http.Headers.HttpHeaders? request, string tagName) { if (request == null)