diff --git a/examples/Example.AspNetCore.Mvc/Controllers/HomeController.cs b/examples/Example.AspNetCore.Mvc/Controllers/HomeController.cs index 52db681..4f1d48e 100644 --- a/examples/Example.AspNetCore.Mvc/Controllers/HomeController.cs +++ b/examples/Example.AspNetCore.Mvc/Controllers/HomeController.cs @@ -14,9 +14,11 @@ public class HomeController(IHttpClientFactory httpClientFactory) : Controller public const string ActivitySourceName = "CustomActivitySource"; private static readonly ActivitySource ActivitySource = new(ActivitySourceName, "1.0.0"); + private readonly IHttpClientFactory _httpClientFactory = httpClientFactory; + public async Task Index() { - using var client = httpClientFactory.CreateClient(); + using var client = _httpClientFactory.CreateClient(); // ReSharper disable once ExplicitCallerInfoArgument using var activity = ActivitySource.StartActivity("DoingStuff", ActivityKind.Internal); diff --git a/examples/Example.AspNetCore.Mvc/Program.cs b/examples/Example.AspNetCore.Mvc/Program.cs index 48dc787..f421b61 100644 --- a/examples/Example.AspNetCore.Mvc/Program.cs +++ b/examples/Example.AspNetCore.Mvc/Program.cs @@ -16,13 +16,12 @@ // Add services to the container. builder.Services .AddHttpClient() - .AddOpenTelemetry() - .ConfigureResource(r => r.AddService("MyNewService1")) - .WithElasticDefaults(builder.Configuration); + .AddElasticOpenTelemetry(builder.Configuration, logger) + .ConfigureResource(r => r.AddService("MyNewService1")); -builder.Services.AddOpenTelemetry() - .ConfigureResource(r => r.AddService("MyNewService2")) - .WithElasticDefaults(builder.Configuration); +//builder.Services.AddOpenTelemetry() +// .ConfigureResource(r => r.AddService("MyNewService2")) +// .WithElasticDefaults(builder.Configuration); //OpenTelemetrySdk.Create(b => b.WithElasticDefaults(builder.Configuration)); diff --git a/examples/ServiceDefaults/ServiceDefaults.csproj b/examples/ServiceDefaults/ServiceDefaults.csproj index b1c2cad..97251ca 100644 --- a/examples/ServiceDefaults/ServiceDefaults.csproj +++ b/examples/ServiceDefaults/ServiceDefaults.csproj @@ -1,7 +1,7 @@ - + - net8.0 + net9.0 enable enable true @@ -16,7 +16,6 @@ - diff --git a/src/Elastic.OpenTelemetry/Core/SignalBuilder.cs b/src/Elastic.OpenTelemetry/Core/SignalBuilder.cs index 96df4fc..7d5854c 100644 --- a/src/Elastic.OpenTelemetry/Core/SignalBuilder.cs +++ b/src/Elastic.OpenTelemetry/Core/SignalBuilder.cs @@ -37,7 +37,6 @@ public static bool ConfigureBuilder( // code in this particular case by shortcutting and returning early. if (options is not null && components is not null) { - ValidateGlobalCallCount(methodName, builderName, options, components, callCount); configure(builder, components); return true; } diff --git a/src/Elastic.OpenTelemetry/Diagnostics/LoggerMessages.cs b/src/Elastic.OpenTelemetry/Diagnostics/LoggerMessages.cs index d10c4a5..9492ceb 100644 --- a/src/Elastic.OpenTelemetry/Diagnostics/LoggerMessages.cs +++ b/src/Elastic.OpenTelemetry/Diagnostics/LoggerMessages.cs @@ -39,6 +39,10 @@ internal static partial class LoggerMessages [LoggerMessage(EventId = 9, EventName = "AddedInstrumentation", Level = LogLevel.Information, Message = "Added {InstrumentationName} to {Provider}.")] public static partial void LogAddedInstrumentation(this ILogger logger, string instrumentationName, string provider); + [LoggerMessage(EventId = 10, EventName = "HttpInstrumentationFound", Level = LogLevel.Information, Message = "The HTTP instrumentation library was located at '{AssemblyPath}'. " + + "Skipping adding native {InstrumentationType} instrumentation from the 'System.Net.Http' ActivitySource.")] + public static partial void LogHttpInstrumentationFound(this ILogger logger, string assemblyPath, string instrumentationType); + // We explictly reuse the same event ID and this is the same log message, but with different types for the structured data [LoggerMessage(EventId = 9, Level = LogLevel.Debug, Message = "{ProcessorName} found `{AttributeName}` attribute with value '{AttributeValue}' on the span.")] internal static partial void FoundTag(this ILogger logger, string processorName, string attributeName, string attributeValue); diff --git a/src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj b/src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj index 9d2728f..5519386 100644 --- a/src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj +++ b/src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj @@ -1,4 +1,4 @@ - + Library @@ -24,7 +24,6 @@ - @@ -32,15 +31,16 @@ - - + + + diff --git a/src/Elastic.OpenTelemetry/Extensions/MeterProviderBuilderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/MeterProviderBuilderExtensions.cs index 1ea5656..320ea9b 100644 --- a/src/Elastic.OpenTelemetry/Extensions/MeterProviderBuilderExtensions.cs +++ b/src/Elastic.OpenTelemetry/Extensions/MeterProviderBuilderExtensions.cs @@ -40,7 +40,21 @@ private static InstrumentationAssemblyInfo[] GetReflectionInstrumentationAssembl Filename = "OpenTelemetry.Instrumentation.AspNetCore.dll", FullyQualifiedType = "OpenTelemetry.Metrics.AspNetCoreInstrumentationMeterProviderBuilderExtensions", InstrumentationMethod = "AddAspNetCoreInstrumentation" - } + }, +#if NET9_0_OR_GREATER + // On .NET 9, we add the `System.Net.Http` source for native instrumentation, rather than referencing + // the contrib instrumentation. However, if the consuming application has their own reference to + // `OpenTelemetry.Instrumentation.Http`, then we use that since it signals the consumer prefers the + // contrib instrumentation. Therefore, on .NET 9+ targets, we attempt to dynamically load the contrib + // instrumentation, when available. + new() + { + Name = "Http", + Filename = "OpenTelemetry.Instrumentation.Http.dll", + FullyQualifiedType = "OpenTelemetry.Metrics.HttpClientInstrumentationMeterProviderBuilderExtensions", + InstrumentationMethod = "AddHttpClientInstrumentation" + }, +#endif ]; /// @@ -156,8 +170,41 @@ static void ConfigureBuilder(MeterProviderBuilder builder, ElasticOpenTelemetryC { builder.ConfigureResource(r => r.AddElasticDistroAttributes()); - AddWithLogging(builder, components.Logger, "HttpClient", b => b.AddHttpClientInstrumentation()); +#if NET9_0_OR_GREATER + try + { + // This first check determines whether OpenTelemetry.Instrumentation.Http.dll is present, in which case, + // it will be registered on the builder via reflection. If it's not present, we can safely add the native + // source which is OTel compliant since .NET 9. + var assemblyLocation = Path.GetDirectoryName(typeof(ElasticOpenTelemetry).Assembly.Location); + if (assemblyLocation is not null) + { + var assemblyPath = Path.Combine(assemblyLocation, "OpenTelemetry.Instrumentation.Http.dll"); + + if (!File.Exists(assemblyPath)) + { + AddWithLogging(builder, components.Logger, "Http (via native instrumentation)", b => b.AddMeter("System.Net.Http")); + } + else + { + components.Logger.LogHttpInstrumentationFound(assemblyPath, "metric"); + } + } + } + catch (Exception ex) + { + components.Logger.LogError(ex, "An exception occurred while checking for the presence of `OpenTelemetry.Instrumentation.Http.dll`."); + } +#else + AddWithLogging(builder, components.Logger, "Http (via contrib instrumentation)", b => b.AddHttpClientInstrumentation()); +#endif + AddWithLogging(builder, components.Logger, "Process", b => b.AddProcessInstrumentation()); +#if NET9_0_OR_GREATER + AddWithLogging(builder, components.Logger, "Runtime", b => b.AddMeter("System.Runtime")); +#else + AddWithLogging(builder, components.Logger, "Runtime", b => b.AddRuntimeInstrumentation()); +#endif // TODO - Guard this behind runtime checks e.g. RuntimeFeature.IsDynamicCodeSupported to support AoT users. // see https://github.com/elastic/elastic-otel-dotnet/issues/198 diff --git a/src/Elastic.OpenTelemetry/Extensions/TracerProviderBuilderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/TracerProviderBuilderExtensions.cs index 6bfe4c1..9b4c619 100644 --- a/src/Elastic.OpenTelemetry/Extensions/TracerProviderBuilderExtensions.cs +++ b/src/Elastic.OpenTelemetry/Extensions/TracerProviderBuilderExtensions.cs @@ -43,7 +43,21 @@ private static InstrumentationAssemblyInfo[] GetReflectionInstrumentationAssembl Filename = "OpenTelemetry.Instrumentation.AspNetCore.dll", FullyQualifiedType = "OpenTelemetry.Trace.AspNetCoreInstrumentationTracerProviderBuilderExtensions", InstrumentationMethod = "AddAspNetCoreInstrumentation" - } + }, +#if NET9_0_OR_GREATER + // On .NET 9, we add the `System.Net.Http` source for native instrumentation, rather than referencing + // the contrib instrumentation. However, if the consuming application has their own reference to + // `OpenTelemetry.Instrumentation.Http`, then we use that since it signals the consumer prefers the + // contrib instrumentation. Therefore, on .NET 9+ targets, we attempt to dynamically load the contrib + // instrumentation, when available. + new() + { + Name = "Http", + Filename = "OpenTelemetry.Instrumentation.Http.dll", + FullyQualifiedType = "OpenTelemetry.Trace.HttpClientInstrumentationTracerProviderBuilderExtensions", + InstrumentationMethod = "AddHttpClientInstrumentation" + }, +#endif ]; /// @@ -147,7 +161,35 @@ static void ConfigureBuilder(TracerProviderBuilder builder, ElasticOpenTelemetry { builder.ConfigureResource(r => r.AddElasticDistroAttributes()); - AddWithLogging(builder, components.Logger, "HttpClient", b => b.AddHttpClientInstrumentation()); +#if NET9_0_OR_GREATER + try + { + // This first check determines whether OpenTelemetry.Instrumentation.Http.dll is present, in which case, + // it will be registered on the builder via reflection. If it's not present, we can safely add the native + // source which is OTel compliant since .NET 9. + var assemblyLocation = Path.GetDirectoryName(typeof(ElasticOpenTelemetry).Assembly.Location); + if (assemblyLocation is not null) + { + var assemblyPath = Path.Combine(assemblyLocation, "OpenTelemetry.Instrumentation.Http.dll"); + + if (!File.Exists(assemblyPath)) + { + AddWithLogging(builder, components.Logger, "Http (via native instrumentation)", b => b.AddSource("System.Net.Http")); + } + else + { + components.Logger.LogHttpInstrumentationFound(assemblyPath, "trace"); + } + } + } + catch (Exception ex) + { + components.Logger.LogError(ex, "An exception occurred while checking for the presence of `OpenTelemetry.Instrumentation.Http.dll`."); + } +#else + AddWithLogging(builder, components.Logger, "Http (via contrib instrumentation)", b => b.AddHttpClientInstrumentation()); +#endif + AddWithLogging(builder, components.Logger, "GrpcClient", b => b.AddGrpcClientInstrumentation()); AddWithLogging(builder, components.Logger, "EntityFrameworkCore", b => b.AddEntityFrameworkCoreInstrumentation()); AddWithLogging(builder, components.Logger, "NEST", b => b.AddElasticsearchClientInstrumentation()); @@ -192,9 +234,9 @@ static void AddInstrumentationViaReflection(TracerProviderBuilder builder, ILogg AddInstrumentationLibraryViaReflection(builder, logger, assemblyLocation, assembly); } } - catch + catch (Exception ex) { - // TODO - Logging + logger.LogError(ex, "An exception occurred while adding instrumentation via reflection."); } } @@ -207,7 +249,7 @@ static void AddInstrumentationLibraryViaReflection( try { var assemblyPath = Path.Combine(assemblyLocation, info.Filename); - if (File.Exists(Path.Combine(assemblyLocation, info.Filename))) + if (File.Exists(assemblyPath)) { logger.LogLocatedInstrumentationAssembly(info.Filename, assemblyLocation);