From 243717fb25bbe34f224d0df9c08dd5f72f6f9c9c Mon Sep 17 00:00:00 2001 From: Amatsugu Date: Wed, 16 Apr 2025 21:28:53 -0400 Subject: [PATCH] add telemetry --- AobaCore/AobaCore.csproj | 7 +- AobaCore/Extensions.cs | 5 +- AobaServer/AobaServer.csproj | 5 ++ .../{ => Auth}/AobaAuthenticationHandler.cs | 2 +- AobaServer/Auth/MetricsTokenValidator.cs | 42 +++++++++++ AobaServer/Middleware/OpenTelemetry.cs | 73 +++++++++++++++++++ AobaServer/Program.cs | 5 ++ 7 files changed, 134 insertions(+), 5 deletions(-) rename AobaServer/{ => Auth}/AobaAuthenticationHandler.cs (97%) create mode 100644 AobaServer/Auth/MetricsTokenValidator.cs create mode 100644 AobaServer/Middleware/OpenTelemetry.cs diff --git a/AobaCore/AobaCore.csproj b/AobaCore/AobaCore.csproj index 5c456a7..02d6c07 100644 --- a/AobaCore/AobaCore.csproj +++ b/AobaCore/AobaCore.csproj @@ -7,10 +7,11 @@ - - + + - + + diff --git a/AobaCore/Extensions.cs b/AobaCore/Extensions.cs index 6964596..f04dd0c 100644 --- a/AobaCore/Extensions.cs +++ b/AobaCore/Extensions.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using MongoDB.Driver; +using MongoDB.Driver.Core.Extensions.DiagnosticSources; using System; using System.Collections.Generic; @@ -14,7 +15,9 @@ public static class Extensions { public static IServiceCollection AddAoba(this IServiceCollection services) { - var dbClient = new MongoClient("mongodb://NinoIna:27017"); + var settings = MongoClientSettings.FromConnectionString("mongodb://NinoIna:27017"); + settings.ClusterConfigurator = cb => cb.Subscribe(new DiagnosticsActivityEventSubscriber()); + var dbClient = new MongoClient(settings); var db = dbClient.GetDatabase("Aoba"); services.AddSingleton(dbClient); diff --git a/AobaServer/AobaServer.csproj b/AobaServer/AobaServer.csproj index 851fd5e..2e49415 100644 --- a/AobaServer/AobaServer.csproj +++ b/AobaServer/AobaServer.csproj @@ -15,6 +15,11 @@ + + + + + diff --git a/AobaServer/AobaAuthenticationHandler.cs b/AobaServer/Auth/AobaAuthenticationHandler.cs similarity index 97% rename from AobaServer/AobaAuthenticationHandler.cs rename to AobaServer/Auth/AobaAuthenticationHandler.cs index 3404200..d4467d2 100644 --- a/AobaServer/AobaAuthenticationHandler.cs +++ b/AobaServer/Auth/AobaAuthenticationHandler.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.Options; using System.Text.Encodings.Web; -namespace AobaServer; +namespace AobaServer.Auth; internal class AobaAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder) : AuthenticationHandler(options, logger, encoder) { diff --git a/AobaServer/Auth/MetricsTokenValidator.cs b/AobaServer/Auth/MetricsTokenValidator.cs new file mode 100644 index 0000000..a931e7d --- /dev/null +++ b/AobaServer/Auth/MetricsTokenValidator.cs @@ -0,0 +1,42 @@ +using Microsoft.IdentityModel.Tokens; + +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; + +namespace AobaServer.Auth; + +public class MetricsTokenValidator(AuthInfo authInfo) : JwtSecurityTokenHandler +{ + private readonly JwtSecurityTokenHandler _handler = new(); + public override Task ValidateTokenAsync(string token, TokenValidationParameters validationParameters) + { + try + { + var principal = _handler.ValidateToken(token, new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(authInfo.SecureKey), + ValidateIssuer = true, + ValidIssuer = authInfo.Issuer, + ValidateAudience = true, + ValidAudience = "metrics", + ValidateLifetime = false, + ClockSkew = TimeSpan.FromMinutes(1) + }, out var validatedToken); + return Task.FromResult(new TokenValidationResult + { + IsValid = true, + SecurityToken = validatedToken, + ClaimsIdentity = new ClaimsIdentity(principal.Identity), + }); + } + catch (Exception e) + { + return Task.FromResult(new TokenValidationResult + { + IsValid = false, + Exception = e + }); + } + } +} diff --git a/AobaServer/Middleware/OpenTelemetry.cs b/AobaServer/Middleware/OpenTelemetry.cs new file mode 100644 index 0000000..3ee05a3 --- /dev/null +++ b/AobaServer/Middleware/OpenTelemetry.cs @@ -0,0 +1,73 @@ +#nullable enable + +using AobaServer.Middleware; + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +using OpenTelemetry; +using OpenTelemetry.Metrics; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; + + +namespace AobaServer.Middleware; + +public static class OpenTelemetry +{ + public static void AddObersability(this IServiceCollection services, IConfiguration configuration) + { + var otel = services.AddOpenTelemetry(); + + otel.ConfigureResource(res => + { + res.AddService(serviceName: $"Breeze: {Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}"); + }); + + + // Add Metrics for ASP.NET Core and our custom metrics and export to Prometheus + otel.WithMetrics(metrics => metrics + // Metrics provider from OpenTelemetry + .AddAspNetCoreInstrumentation() + .AddCustomMetrics() + // Metrics provides by ASP.NET Core in .NET 8 + .AddMeter("Microsoft.AspNetCore.Hosting") + .AddMeter("Microsoft.AspNetCore.Server.Kestrel") + // Metrics provided by System.Net libraries + .AddMeter("System.Net.Http") + .AddMeter("System.Net.NameResolution") + .AddMeter("MongoDB.Driver.Core.Extensions.DiagnosticSources") + .AddPrometheusExporter()); + + // Add Tracing for ASP.NET Core and our custom ActivitySource and export to Jaeger + var tracingOtlpEndpoint = configuration["OTLP_ENDPOINT_URL"]; + otel.WithTracing(tracing => + { + tracing.AddAspNetCoreInstrumentation(); + tracing.AddHttpClientInstrumentation(); + if (!string.IsNullOrWhiteSpace(tracingOtlpEndpoint)) + { + tracing.AddOtlpExporter(otlpOptions => + { + otlpOptions.Endpoint = new Uri(tracingOtlpEndpoint); + }); + } + }); + } + + + public static MeterProviderBuilder AddCustomMetrics(this MeterProviderBuilder builder) + { + + + return builder; + } + + public static IEndpointRouteBuilder MapObserability(this IEndpointRouteBuilder endpoints) + { + endpoints.MapPrometheusScrapingEndpoint().RequireAuthorization(p => p.RequireRole("metrics")); + return endpoints; + } +} diff --git a/AobaServer/Program.cs b/AobaServer/Program.cs index 08b7593..24205d5 100644 --- a/AobaServer/Program.cs +++ b/AobaServer/Program.cs @@ -1,6 +1,8 @@ using AobaCore; using AobaServer; +using AobaServer.Auth; +using AobaServer.Middleware; using AobaServer.Models; using Microsoft.AspNetCore.Authentication; @@ -13,6 +15,7 @@ var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(opt => opt.ModelBinderProviders.Add(new BsonIdModelBinderProvider())); +builder.Services.AddObersability(builder.Configuration); var authInfo = AuthInfo.LoadOrCreate("Auth.json", "aobaV2", "aoba"); builder.Services.AddSingleton(authInfo); @@ -37,6 +40,7 @@ builder.Services.AddAuthentication(options => }).AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => //Bearer auth { options.TokenValidationParameters = validationParams; + options.TokenHandlers.Add(new MetricsTokenValidator(authInfo)); options.Events = new JwtBearerEvents { OnMessageReceived = ctx => //Retreive token from cookie if not found in headers @@ -87,6 +91,7 @@ app.UseAuthorization(); app.MapControllers(); +app.MapObserability(); app.Run();