| | | 1 | | using System.Reflection; |
| | | 2 | | using OpenTelemetry; |
| | | 3 | | using OpenTelemetry.Exporter; |
| | | 4 | | using OpenTelemetry.Metrics; |
| | | 5 | | using OpenTelemetry.Resources; |
| | | 6 | | using OpenTelemetry.Trace; |
| | | 7 | | using ProjectTemplate.Web.Options; |
| | | 8 | | |
| | | 9 | | namespace ProjectTemplate.Web.Extensions; |
| | | 10 | | |
| | | 11 | | /// <summary> |
| | | 12 | | /// Provides extension methods to register OpenTelemetry services for the application. |
| | | 13 | | /// </summary> |
| | | 14 | | public static class OpenTelemetryServiceExtensions |
| | | 15 | | { |
| | | 16 | | /// <summary> |
| | | 17 | | /// Adds baseline OpenTelemetry tracing and metrics support. |
| | | 18 | | /// </summary> |
| | | 19 | | /// <param name="services">The service collection to add OpenTelemetry services to.</param> |
| | | 20 | | /// <param name="configuration">The application configuration source.</param> |
| | | 21 | | /// <param name="environment">The current hosting environment.</param> |
| | | 22 | | /// <returns>The same service collection instance so calls can be chained.</returns> |
| | | 23 | | public static IServiceCollection AddApplicationOpenTelemetry( |
| | | 24 | | this IServiceCollection services, |
| | | 25 | | IConfiguration configuration, |
| | | 26 | | IHostEnvironment environment) |
| | | 27 | | { |
| | 160 | 28 | | services |
| | 160 | 29 | | .AddOptions<ApplicationOpenTelemetryOptions>() |
| | 160 | 30 | | .Bind(configuration.GetSection(ApplicationOpenTelemetryOptions.SectionName)) |
| | 136 | 31 | | .Validate(options => !string.IsNullOrWhiteSpace(options.ServiceName), |
| | 160 | 32 | | "ProjectTemplate:OpenTelemetry:ServiceName must not be empty.") |
| | 136 | 33 | | .Validate(options => !options.Otlp.Enabled || Uri.TryCreate(options.Otlp.Endpoint, UriKind.Absolute, out _), |
| | 160 | 34 | | "ProjectTemplate:OpenTelemetry:Otlp:Endpoint must be a valid absolute URI when OTLP export is enabled.") |
| | 160 | 35 | | .ValidateOnStart(); |
| | | 36 | | |
| | 160 | 37 | | ApplicationOpenTelemetryOptions options = configuration |
| | 160 | 38 | | .GetSection(ApplicationOpenTelemetryOptions.SectionName) |
| | 160 | 39 | | .Get<ApplicationOpenTelemetryOptions>() ?? new ApplicationOpenTelemetryOptions(); |
| | | 40 | | |
| | 160 | 41 | | if (!options.Enabled) |
| | | 42 | | { |
| | 6 | 43 | | return services; |
| | | 44 | | } |
| | | 45 | | |
| | 154 | 46 | | string serviceVersion = ResolveServiceVersion(options); |
| | | 47 | | |
| | 154 | 48 | | OpenTelemetryBuilder openTelemetryBuilder = services |
| | 154 | 49 | | .AddOpenTelemetry() |
| | 408 | 50 | | .ConfigureResource(resource => resource |
| | 408 | 51 | | .AddService( |
| | 408 | 52 | | serviceName: options.ServiceName, |
| | 408 | 53 | | serviceVersion: serviceVersion) |
| | 408 | 54 | | .AddAttributes(new Dictionary<string, object> |
| | 408 | 55 | | { |
| | 408 | 56 | | ["deployment.environment.name"] = environment.EnvironmentName |
| | 408 | 57 | | })); |
| | | 58 | | |
| | 154 | 59 | | if (options.EnableTracing) |
| | | 60 | | { |
| | 152 | 61 | | openTelemetryBuilder.WithTracing(tracing => |
| | 152 | 62 | | { |
| | 152 | 63 | | if (options.EnableAspNetCoreInstrumentation) |
| | 152 | 64 | | { |
| | 150 | 65 | | tracing.AddAspNetCoreInstrumentation(); |
| | 152 | 66 | | } |
| | 152 | 67 | | |
| | 152 | 68 | | if (options.EnableHttpClientInstrumentation) |
| | 152 | 69 | | { |
| | 150 | 70 | | tracing.AddHttpClientInstrumentation(); |
| | 152 | 71 | | } |
| | 152 | 72 | | |
| | 152 | 73 | | if (options.Otlp.Enabled) |
| | 152 | 74 | | { |
| | 4 | 75 | | tracing.AddOtlpExporter(exporterOptions => ConfigureOtlpExporter(exporterOptions, options.Otlp)); |
| | 152 | 76 | | } |
| | 304 | 77 | | }); |
| | | 78 | | } |
| | | 79 | | |
| | 154 | 80 | | if (options.EnableMetrics) |
| | | 81 | | { |
| | 152 | 82 | | openTelemetryBuilder.WithMetrics(metrics => |
| | 152 | 83 | | { |
| | 152 | 84 | | if (options.EnableAspNetCoreInstrumentation) |
| | 152 | 85 | | { |
| | 150 | 86 | | metrics.AddAspNetCoreInstrumentation(); |
| | 152 | 87 | | } |
| | 152 | 88 | | |
| | 152 | 89 | | if (options.EnableHttpClientInstrumentation) |
| | 152 | 90 | | { |
| | 150 | 91 | | metrics.AddHttpClientInstrumentation(); |
| | 152 | 92 | | } |
| | 152 | 93 | | |
| | 152 | 94 | | if (options.Otlp.Enabled) |
| | 152 | 95 | | { |
| | 4 | 96 | | metrics.AddOtlpExporter(exporterOptions => ConfigureOtlpExporter(exporterOptions, options.Otlp)); |
| | 152 | 97 | | } |
| | 304 | 98 | | }); |
| | | 99 | | } |
| | | 100 | | |
| | 154 | 101 | | return services; |
| | | 102 | | } |
| | | 103 | | |
| | | 104 | | private static void ConfigureOtlpExporter( |
| | | 105 | | OtlpExporterOptions exporterOptions, |
| | | 106 | | ApplicationOtlpExporterOptions options) |
| | | 107 | | { |
| | 8 | 108 | | exporterOptions.Endpoint = new Uri(options.Endpoint); |
| | 8 | 109 | | exporterOptions.Protocol = string.Equals( |
| | 8 | 110 | | options.Protocol, |
| | 8 | 111 | | "HttpProtobuf", |
| | 8 | 112 | | StringComparison.OrdinalIgnoreCase) |
| | 8 | 113 | | ? OtlpExportProtocol.HttpProtobuf |
| | 8 | 114 | | : OtlpExportProtocol.Grpc; |
| | 8 | 115 | | } |
| | | 116 | | |
| | | 117 | | private static string ResolveServiceVersion(ApplicationOpenTelemetryOptions options) |
| | | 118 | | { |
| | 162 | 119 | | if (!string.IsNullOrWhiteSpace(options.ServiceVersion)) |
| | | 120 | | { |
| | 156 | 121 | | return options.ServiceVersion.Trim(); |
| | | 122 | | } |
| | | 123 | | |
| | 6 | 124 | | Assembly assembly = typeof(Program).Assembly; |
| | | 125 | | |
| | 6 | 126 | | string? informationalVersion = assembly |
| | 6 | 127 | | .GetCustomAttribute<AssemblyInformationalVersionAttribute>() |
| | 6 | 128 | | ?.InformationalVersion; |
| | | 129 | | |
| | 6 | 130 | | return !string.IsNullOrWhiteSpace(informationalVersion) ? informationalVersion : assembly.GetName().Version?.ToS |
| | | 131 | | } |
| | | 132 | | } |