using System; using System.Collections.Generic; using System.Reflection; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using OpenTelemetry.Metrics; using OpenTelemetry.Resources; using OpenTelemetry.Trace; using StellaOps.Excititor.WebService.Options; using StellaOps.Excititor.WebService.Telemetry; using StellaOps.Ingestion.Telemetry; namespace StellaOps.Excititor.WebService.Extensions; internal static class TelemetryExtensions { public static void ConfigureExcititorTelemetry(this WebApplicationBuilder builder) { var telemetryOptions = new ExcititorTelemetryOptions(); builder.Configuration.GetSection("Excititor:Telemetry").Bind(telemetryOptions); if (!telemetryOptions.Enabled || (!telemetryOptions.EnableTracing && !telemetryOptions.EnableMetrics)) { return; } var openTelemetry = builder.Services.AddOpenTelemetry(); openTelemetry.ConfigureResource(resource => { var serviceName = telemetryOptions.ServiceName ?? builder.Environment.ApplicationName ?? "StellaOps.Excititor.WebService"; var version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "unknown"; resource.AddService(serviceName, serviceVersion: version, serviceInstanceId: Environment.MachineName); foreach (var attribute in telemetryOptions.ResourceAttributes) { if (string.IsNullOrWhiteSpace(attribute.Key) || string.IsNullOrWhiteSpace(attribute.Value)) { continue; } resource.AddAttributes(new[] { new KeyValuePair(attribute.Key, attribute.Value) }); } }); if (telemetryOptions.EnableTracing) { openTelemetry.WithTracing(tracing => { tracing .AddSource(IngestionTelemetry.ActivitySourceName) .AddAspNetCoreInstrumentation() .AddHttpClientInstrumentation(); ConfigureExporters(telemetryOptions, tracing); }); } if (telemetryOptions.EnableMetrics) { openTelemetry.WithMetrics(metrics => { metrics .AddMeter(IngestionTelemetry.MeterName) .AddMeter(EvidenceTelemetry.MeterName) .AddAspNetCoreInstrumentation() .AddHttpClientInstrumentation() .AddRuntimeInstrumentation(); ConfigureExporters(telemetryOptions, metrics); }); } } private static void ConfigureExporters(ExcititorTelemetryOptions options, TracerProviderBuilder tracing) { if (!string.IsNullOrWhiteSpace(options.OtlpEndpoint)) { tracing.AddOtlpExporter(otlp => { otlp.Endpoint = new Uri(options.OtlpEndpoint, UriKind.Absolute); var headers = BuildHeaders(options.OtlpHeaders); if (!string.IsNullOrEmpty(headers)) { otlp.Headers = headers; } }); return; } if (options.ExportConsole) { tracing.AddConsoleExporter(); } } private static void ConfigureExporters(ExcititorTelemetryOptions options, MeterProviderBuilder metrics) { if (!string.IsNullOrWhiteSpace(options.OtlpEndpoint)) { metrics.AddOtlpExporter(otlp => { otlp.Endpoint = new Uri(options.OtlpEndpoint, UriKind.Absolute); var headers = BuildHeaders(options.OtlpHeaders); if (!string.IsNullOrEmpty(headers)) { otlp.Headers = headers; } }); return; } if (options.ExportConsole) { metrics.AddConsoleExporter(); } } private static string? BuildHeaders(IReadOnlyDictionary headers) { if (headers.Count == 0) { return null; } var parts = new List(headers.Count); foreach (var header in headers) { if (string.IsNullOrWhiteSpace(header.Key) || string.IsNullOrWhiteSpace(header.Value)) { continue; } parts.Add($"{header.Key}={header.Value}"); } return parts.Count == 0 ? null : string.Join(',', parts); } }