From 2f494932f8d82a99e76c2cc3cc6e8062f0c0cd6b Mon Sep 17 00:00:00 2001 From: Alexander Konietzko Date: Mon, 14 Apr 2025 15:17:06 +0200 Subject: [PATCH] feat: Improve Aspire telemetry --- CleanArchitecture.AppHost/Program.cs | 3 +- .../Properties/launchSettings.json | 5 +- .../appsettings.Development.json | 3 +- .../Extensions.cs | 42 ++++++++-- .../HttpEnricher.cs | 80 +++++++++++++++++++ 5 files changed, 122 insertions(+), 11 deletions(-) create mode 100644 CleanArchitecture.ServiceDefaults/HttpEnricher.cs diff --git a/CleanArchitecture.AppHost/Program.cs b/CleanArchitecture.AppHost/Program.cs index 81aba7e..a05ade9 100644 --- a/CleanArchitecture.AppHost/Program.cs +++ b/CleanArchitecture.AppHost/Program.cs @@ -7,13 +7,14 @@ var rabbitPasswordParameter = builder.AddParameter("username", rabbitPasswordRessource.Value); var rabbitMq = builder - .AddRabbitMQ("RabbitMq", null, rabbitPasswordParameter, 5672) + .AddRabbitMQ("RabbitMQ", null, rabbitPasswordParameter, 5672) .WithManagementPlugin(); var sqlServer = builder.AddSqlServer("SqlServer"); var db = sqlServer.AddDatabase("Database", "clean-architecture"); builder.AddProject("CleanArchitecture-Api") + .WithEnvironment("ASPIRE_ENABLED", "true") .WithOtlpExporter() .WithHttpHealthCheck("/health") .WithReference(redis) diff --git a/CleanArchitecture.AppHost/Properties/launchSettings.json b/CleanArchitecture.AppHost/Properties/launchSettings.json index 030b6d4..32d8db9 100644 --- a/CleanArchitecture.AppHost/Properties/launchSettings.json +++ b/CleanArchitecture.AppHost/Properties/launchSettings.json @@ -4,14 +4,13 @@ "Aspire": { "commandName": "Project", "dotnetRunMessages": true, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:17270;http://localhost:15188", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "DOTNET_ENVIRONMENT": "Development", "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21200", - "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22111", - "ASPIRE_ENABLED": "true" + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22111" } } } diff --git a/CleanArchitecture.AppHost/appsettings.Development.json b/CleanArchitecture.AppHost/appsettings.Development.json index 0c208ae..289f257 100644 --- a/CleanArchitecture.AppHost/appsettings.Development.json +++ b/CleanArchitecture.AppHost/appsettings.Development.json @@ -4,5 +4,6 @@ "Default": "Information", "Microsoft.AspNetCore": "Warning" } - } + }, + "EnableHttpTraces": false } diff --git a/CleanArchitecture.ServiceDefaults/Extensions.cs b/CleanArchitecture.ServiceDefaults/Extensions.cs index 9d1b110..1a5a3cd 100644 --- a/CleanArchitecture.ServiceDefaults/Extensions.cs +++ b/CleanArchitecture.ServiceDefaults/Extensions.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Hosting; @@ -39,6 +40,8 @@ public static class Extensions private static void ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder { + var enableHttpTraces = builder.Configuration.GetValue("APP_ENABLE_HTTP_TRACES") ?? false; + builder.Logging.AddOpenTelemetry(logging => { logging.IncludeFormattedMessage = true; @@ -48,17 +51,44 @@ public static class Extensions builder.Services.AddOpenTelemetry() .WithMetrics(metrics => { - metrics.AddAspNetCoreInstrumentation() + metrics + .AddAspNetCoreInstrumentation() .AddHttpClientInstrumentation() .AddRuntimeInstrumentation(); }) .WithTracing(tracing => { - tracing.AddSource(builder.Environment.ApplicationName) - .AddAspNetCoreInstrumentation() - .AddGrpcClientInstrumentation() - .AddEntityFrameworkCoreInstrumentation() - .AddHttpClientInstrumentation(); + tracing + .AddSource(builder.Environment.ApplicationName) + .AddSource("MassTransit") + .AddAspNetCoreInstrumentation(options => + { + options.EnableAspNetCoreSignalRSupport = true; + options.EnrichWithHttpResponse = HttpEnricher.HttpRouteEnricher; + }) + .AddGrpcClientInstrumentation(options => + { + if (enableHttpTraces) + { + options.EnrichWithHttpRequestMessage = HttpEnricher.RequestEnricher; + options.EnrichWithHttpResponseMessage = HttpEnricher.ResponseEnricher; + } + }) + .AddEntityFrameworkCoreInstrumentation(options => + { + options.EnrichWithIDbCommand = (activity, dbCommand) => + { + activity.SetTag("sql.statement", dbCommand.CommandText); + }; + }) + .AddHttpClientInstrumentation(options => + { + if (enableHttpTraces) + { + options.EnrichWithHttpRequestMessage = HttpEnricher.RequestEnricher; + options.EnrichWithHttpResponseMessage = HttpEnricher.ResponseEnricher; + } + }); }); builder.AddOpenTelemetryExporters(); diff --git a/CleanArchitecture.ServiceDefaults/HttpEnricher.cs b/CleanArchitecture.ServiceDefaults/HttpEnricher.cs new file mode 100644 index 0000000..a426817 --- /dev/null +++ b/CleanArchitecture.ServiceDefaults/HttpEnricher.cs @@ -0,0 +1,80 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Routing; + +namespace CleanArchitecture.ServiceDefaults; + +public static class HttpEnricher +{ + public static Action? RequestEnricher = static (activity, request) => + { + if (request.Content is not null) + { + try + { + var requestContent = request.Content.ReadAsStringAsync().Result; + activity.SetTag("http.request.body", requestContent); + } + catch (HttpRequestException) + { + + } + } + else + { + activity.SetTag("http.response.body", null); + } + }; + + public static Action? ResponseEnricher = static (activity, response) => + { + if (response.Content is not null) + { + try + { + var responseContent = response.Content.ReadAsStringAsync().Result; + activity.SetTag("http.response.body", responseContent); + } + catch (HttpRequestException) + { + + } + catch (ObjectDisposedException) + { + + } + } + else + { + activity.SetTag("http.response.body", null); + } + }; + + public static Action? HttpRouteEnricher = static (activity, request) => + { + var endpoint = request.HttpContext.GetEndpoint(); + + if (endpoint is RouteEndpoint routeEndpoint) + { + var descriptor = routeEndpoint.Metadata.GetMetadata(); + + if (descriptor is null) + { + return; + } + + var controller = descriptor.ControllerName; + var action = descriptor.ActionName; + + var pathParameters = descriptor.Parameters + .Where(p => p.BindingInfo is null || p.BindingInfo.BindingSource?.Id == "Path") + .Select(p => $"{{{p.Name}}}"); + + var route = string.Join("/", [controller, action, .. pathParameters]); + + activity.DisplayName = route; + activity.SetTag("http.route", route); + activity.SetTag("Name", route); + } + }; +}