using System.Diagnostics; using Nexus.Api.Observability; using Nexus.Api.Http; using Npgsql; using OpenTelemetry; using OpenTelemetry.Exporter; using OpenTelemetry.Metrics; using OpenTelemetry.Resources; using OpenTelemetry.Trace; namespace Nexus.Api.Extensions; public static class PlatformServiceCollectionExtensions { /// /// Registers the canonical OpenAPI document and privacy-safe telemetry. /// OTLP export is opt-in; without an endpoint Nexus keeps only in-process /// instrumentation and does not add a production telemetry service. /// public static IServiceCollection AddNexusPlatform( this IServiceCollection services, IConfiguration configuration) { services.AddOpenApi("v1", options => options.AddSchemaTransformer()); services.AddProblemDetails(options => { options.CustomizeProblemDetails = context => NexusProblemDetailsDefaults.Apply( context.ProblemDetails, context.HttpContext); }); var telemetry = services.AddOpenTelemetry() .ConfigureResource(resource => resource.AddService( serviceName: "nexus-api", serviceVersion: typeof(Program).Assembly.GetName().Version?.ToString())) .WithMetrics(metrics => metrics .AddAspNetCoreInstrumentation() .AddHttpClientInstrumentation() .AddRuntimeInstrumentation() .AddMeter(NexusTelemetry.SourceName)) .WithTracing(tracing => tracing .AddAspNetCoreInstrumentation(options => { // Exception messages and stack traces may contain prompts, // paths or other operator content. options.RecordException = false; options.Filter = context => !context.Request.Path.StartsWithSegments("/health"); }) .AddHttpClientInstrumentation(options => { options.RecordException = false; }) .AddNpgsql() .AddSource(NexusTelemetry.SourceName) .AddProcessor(new NexusTelemetryRedactionProcessor())); var endpointValue = configuration["OpenTelemetry:OtlpEndpoint"] ?? Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_ENDPOINT"); if (Uri.TryCreate(endpointValue, UriKind.Absolute, out var endpoint)) { telemetry.UseOtlpExporter(OtlpExportProtocol.Grpc, endpoint); } return services; } }