diff --git a/samples/dotnet/otel/AgentOtelExtension.cs b/samples/dotnet/otel/AgentOtelExtension.cs index 65c13c98..bad698e5 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 { @@ -20,14 +21,13 @@ 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 + serviceName: AgentsTelemetry.SourceName, + serviceVersion: AgentsTelemetry.SourceVersion ) .AddAttributes(new[] { @@ -38,8 +38,7 @@ public static TBuilder ConfigureOtelProviders(this TBuilder builder) w .AddSource( "Microsoft.AspNetCore", "System.Net.Http", - AgentsTelemetry.SourceName, - AgentTelemetry.ServiceName + AgentsTelemetry.SourceName ) .SetSampler(new AlwaysOnSampler()) .AddAspNetCoreInstrumentation(tracing => @@ -50,10 +49,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 +67,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,9 +80,9 @@ 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; @@ -110,8 +96,78 @@ public static TBuilder ConfigureOtelProviders(this TBuilder builder) w // 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, System.Net.Http.Headers.HttpHeaders? request, string tagName) + { + + if (request == null) + { + return; + } + + var headerList = request.Where(h => !string.Equals(h.Key, "Authorization", StringComparison.OrdinalIgnoreCase)) + .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 => !string.Equals(h.Key, "Authorization", StringComparison.OrdinalIgnoreCase)) + .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)); + // } + } + } } }