From 15cc2311f522d4bd91454de7fd4b9b4f24348a09 Mon Sep 17 00:00:00 2001 From: Steve Gordon Date: Fri, 21 Feb 2025 13:46:17 +0000 Subject: [PATCH 01/12] Cleanup MVC sample program file --- examples/Example.AspNetCore.Mvc/Program.cs | 25 ++++------------------ 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/examples/Example.AspNetCore.Mvc/Program.cs b/examples/Example.AspNetCore.Mvc/Program.cs index f421b61..7328170 100644 --- a/examples/Example.AspNetCore.Mvc/Program.cs +++ b/examples/Example.AspNetCore.Mvc/Program.cs @@ -3,48 +3,31 @@ // See the LICENSE file in the project root for more information using OpenTelemetry; -using OpenTelemetry.Resources; +using OpenTelemetry.Trace; var builder = WebApplication.CreateBuilder(args); -using var loggerFactory = LoggerFactory.Create(loggingBuilder => loggingBuilder - .SetMinimumLevel(LogLevel.Trace) - .AddConsole()); - -var logger = loggerFactory.CreateLogger("OpenTelemetry"); - -// Add services to the container. builder.Services .AddHttpClient() - .AddElasticOpenTelemetry(builder.Configuration, logger) - .ConfigureResource(r => r.AddService("MyNewService1")); - -//builder.Services.AddOpenTelemetry() -// .ConfigureResource(r => r.AddService("MyNewService2")) -// .WithElasticDefaults(builder.Configuration); - -//OpenTelemetrySdk.Create(b => b.WithElasticDefaults(builder.Configuration)); + .AddElasticOpenTelemetry() + .WithTracing(t => t.AddAspNetCoreInstrumentation()); // This is redundant but used in manual testing for now builder.Services .AddControllersWithViews(); var app = builder.Build(); -app.Logger.LogInformation("Process Id {ProcesId}", Environment.ProcessId); +app.Logger.LogInformation("Process Id {ProcessId}", Environment.ProcessId); -// Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); - app.UseRouting(); - app.UseAuthorization(); app.MapControllerRoute( From c6d761122b72c6fd756c7cabad2b7072c4820395 Mon Sep 17 00:00:00 2001 From: Steve Gordon Date: Fri, 21 Feb 2025 13:51:05 +0000 Subject: [PATCH 02/12] Bump auto instrumentation to 1.10 --- .../Elastic.OpenTelemetry.AutoInstrumentation.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Elastic.OpenTelemetry.AutoInstrumentation/Elastic.OpenTelemetry.AutoInstrumentation.csproj b/src/Elastic.OpenTelemetry.AutoInstrumentation/Elastic.OpenTelemetry.AutoInstrumentation.csproj index fe9f4ad..c12b1b8 100644 --- a/src/Elastic.OpenTelemetry.AutoInstrumentation/Elastic.OpenTelemetry.AutoInstrumentation.csproj +++ b/src/Elastic.OpenTelemetry.AutoInstrumentation/Elastic.OpenTelemetry.AutoInstrumentation.csproj @@ -8,7 +8,7 @@ - + From b99675d82564986371830d5c5c013d53ed7272f7 Mon Sep 17 00:00:00 2001 From: Steve Gordon Date: Tue, 25 Feb 2025 11:10:51 +0000 Subject: [PATCH 03/12] Native AoT support and assembly scanning refactoring --- Elastic.OpenTelemetry.sln | 24 ++ .../.config/dotnet-tools.json | 13 + .../Example.AspNetCore.WebApiAot.csproj | 21 ++ .../Example.AspNetCore.WebApiAot.http | 11 + .../Example.AspNetCore.WebApiAot/Program.cs | 38 +++ .../Properties/launchSettings.json | 15 + .../appsettings.Development.json | 8 + .../appsettings.json | 15 + .../CompositeElasticOpenTelemetryOptions.cs | 10 + .../ElasticOpenTelemetryOptions.cs | 12 +- .../Configuration/EnvironmentVariables.cs | 1 + .../Parsers/ConfigurationParser.cs | 4 + .../Core/AutoInstrumentationPlugin.cs | 2 +- .../Core/SignalBuilder.cs | 112 ++++++- .../Diagnostics/FileLogger.cs | 4 +- .../Diagnostics/LoggerMessages.cs | 40 ++- .../Elastic.OpenTelemetry.csproj | 14 +- .../HostApplicationBuilderExtensions.cs | 19 +- .../LoggingProviderBuilderExtensions.cs | 19 +- .../MeterProviderBuilderExtensions.cs | 206 ++++++------- .../OpenTelemetryBuilderExtensions.cs | 47 ++- .../TracerProviderBuilderExtensions.cs | 215 ++++++-------- .../ContribMetricsInstrumentation.cs | 95 ++++++ .../ContribTraceInstrumentation.cs | 151 ++++++++++ test-applications/Directory.Build.props | 8 + test-applications/WebApi/Program.cs | 20 ++ .../WebApi/Properties/launchSettings.json | 23 ++ test-applications/WebApi/README.md | 4 + test-applications/WebApi/WebApi.csproj | 18 ++ test-applications/WebApi/appsettings.json | 9 + .../WebApiDotNet8/WebApiDotNet8/Program.cs | 26 ++ .../Properties/launchSettings.json | 41 +++ .../WebApiDotNet8/WebApiDotNet8.csproj | 18 ++ .../WebApiDotNet8/WebApiDotNet8.http | 6 + .../WebApiDotNet8/appsettings.json | 9 + .../Aot/NativeAotCompatibilityTests.cs | 63 ++++ ...mpositeElasticOpenTelemetryOptionsTests.cs | 279 +++++++++++++++++- .../ElasticOpenTelemetryOptionsTests.cs | 279 ------------------ .../Elastic.OpenTelemetry.Tests.csproj | 10 +- .../InstrumentationScanningTests.cs | 113 +++++++ .../Properties/launchSettings.json | 12 + .../ServiceCollectionTests.cs | 11 +- 42 files changed, 1470 insertions(+), 575 deletions(-) create mode 100644 examples/Example.AspNetCore.WebApiAot/.config/dotnet-tools.json create mode 100644 examples/Example.AspNetCore.WebApiAot/Example.AspNetCore.WebApiAot.csproj create mode 100644 examples/Example.AspNetCore.WebApiAot/Example.AspNetCore.WebApiAot.http create mode 100644 examples/Example.AspNetCore.WebApiAot/Program.cs create mode 100644 examples/Example.AspNetCore.WebApiAot/Properties/launchSettings.json create mode 100644 examples/Example.AspNetCore.WebApiAot/appsettings.Development.json create mode 100644 examples/Example.AspNetCore.WebApiAot/appsettings.json create mode 100644 src/Elastic.OpenTelemetry/Instrumentation/ContribMetricsInstrumentation.cs create mode 100644 src/Elastic.OpenTelemetry/Instrumentation/ContribTraceInstrumentation.cs create mode 100644 test-applications/Directory.Build.props create mode 100644 test-applications/WebApi/Program.cs create mode 100644 test-applications/WebApi/Properties/launchSettings.json create mode 100644 test-applications/WebApi/README.md create mode 100644 test-applications/WebApi/WebApi.csproj create mode 100644 test-applications/WebApi/appsettings.json create mode 100644 test-applications/WebApiDotNet8/WebApiDotNet8/Program.cs create mode 100644 test-applications/WebApiDotNet8/WebApiDotNet8/Properties/launchSettings.json create mode 100644 test-applications/WebApiDotNet8/WebApiDotNet8/WebApiDotNet8.csproj create mode 100644 test-applications/WebApiDotNet8/WebApiDotNet8/WebApiDotNet8.http create mode 100644 test-applications/WebApiDotNet8/WebApiDotNet8/appsettings.json create mode 100644 tests/Elastic.OpenTelemetry.Tests/Aot/NativeAotCompatibilityTests.cs delete mode 100644 tests/Elastic.OpenTelemetry.Tests/Configuration/ElasticOpenTelemetryOptionsTests.cs create mode 100644 tests/Elastic.OpenTelemetry.Tests/InstrumentationScanningTests.cs create mode 100644 tests/Elastic.OpenTelemetry.Tests/Properties/launchSettings.json diff --git a/Elastic.OpenTelemetry.sln b/Elastic.OpenTelemetry.sln index a94c588..d10cafe 100644 --- a/Elastic.OpenTelemetry.sln +++ b/Elastic.OpenTelemetry.sln @@ -54,6 +54,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "k8s", "k8s", "{21E61166-264 examples\k8s\README.md = examples\k8s\README.md EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example.AspNetCore.WebApiAot", "examples\Example.AspNetCore.WebApiAot\Example.AspNetCore.WebApiAot.csproj", "{D985352F-6741-4BB2-8BBC-38A69F624D26}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "applications", "applications", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi", "test-applications\WebApi\WebApi.csproj", "{2A1F61D6-EE54-4ECE-9814-A2BF34957415}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiDotNet8", "test-applications\WebApiDotNet8\WebApiDotNet8\WebApiDotNet8.csproj", "{C8C543F8-5C7A-4748-A3E8-658D79E3FDC1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -120,6 +128,18 @@ Global {B1CA9165-89D9-4D6E-AFEF-5434A8D8A672}.Debug|Any CPU.Build.0 = Debug|Any CPU {B1CA9165-89D9-4D6E-AFEF-5434A8D8A672}.Release|Any CPU.ActiveCfg = Release|Any CPU {B1CA9165-89D9-4D6E-AFEF-5434A8D8A672}.Release|Any CPU.Build.0 = Release|Any CPU + {D985352F-6741-4BB2-8BBC-38A69F624D26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D985352F-6741-4BB2-8BBC-38A69F624D26}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D985352F-6741-4BB2-8BBC-38A69F624D26}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D985352F-6741-4BB2-8BBC-38A69F624D26}.Release|Any CPU.Build.0 = Release|Any CPU + {2A1F61D6-EE54-4ECE-9814-A2BF34957415}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2A1F61D6-EE54-4ECE-9814-A2BF34957415}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2A1F61D6-EE54-4ECE-9814-A2BF34957415}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2A1F61D6-EE54-4ECE-9814-A2BF34957415}.Release|Any CPU.Build.0 = Release|Any CPU + {C8C543F8-5C7A-4748-A3E8-658D79E3FDC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C8C543F8-5C7A-4748-A3E8-658D79E3FDC1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C8C543F8-5C7A-4748-A3E8-658D79E3FDC1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C8C543F8-5C7A-4748-A3E8-658D79E3FDC1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -138,6 +158,10 @@ Global {782E4DC1-8186-4BAC-B2F4-89E6DF22A4DD} = {AAD39891-0B70-47FA-A212-43E1AAE5DF56} {B1CA9165-89D9-4D6E-AFEF-5434A8D8A672} = {E622CFF2-C6C4-40FB-BE42-7C4F2B38B75A} {21E61166-2640-4E38-8108-1BA510C07110} = {4E95C87B-655B-4BC3-8F2A-DF06B7AAB7E9} + {D985352F-6741-4BB2-8BBC-38A69F624D26} = {4E95C87B-655B-4BC3-8F2A-DF06B7AAB7E9} + {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {AAD39891-0B70-47FA-A212-43E1AAE5DF56} + {2A1F61D6-EE54-4ECE-9814-A2BF34957415} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {C8C543F8-5C7A-4748-A3E8-658D79E3FDC1} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {573B2B5F-8CBB-4D52-A55A-4E65E282AAFB} diff --git a/examples/Example.AspNetCore.WebApiAot/.config/dotnet-tools.json b/examples/Example.AspNetCore.WebApiAot/.config/dotnet-tools.json new file mode 100644 index 0000000..13049e1 --- /dev/null +++ b/examples/Example.AspNetCore.WebApiAot/.config/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-ef": { + "version": "9.0.2", + "commands": [ + "dotnet-ef" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/examples/Example.AspNetCore.WebApiAot/Example.AspNetCore.WebApiAot.csproj b/examples/Example.AspNetCore.WebApiAot/Example.AspNetCore.WebApiAot.csproj new file mode 100644 index 0000000..9bdc0a4 --- /dev/null +++ b/examples/Example.AspNetCore.WebApiAot/Example.AspNetCore.WebApiAot.csproj @@ -0,0 +1,21 @@ + + + + net9.0 + enable + enable + true + true + 205c18a1-356e-4e59-b15f-2a297cb8d4b8 + + + + + + + + + + + + diff --git a/examples/Example.AspNetCore.WebApiAot/Example.AspNetCore.WebApiAot.http b/examples/Example.AspNetCore.WebApiAot/Example.AspNetCore.WebApiAot.http new file mode 100644 index 0000000..46a36cd --- /dev/null +++ b/examples/Example.AspNetCore.WebApiAot/Example.AspNetCore.WebApiAot.http @@ -0,0 +1,11 @@ +@Example.AspNetCore.WebApiAot_HostAddress = http://localhost:5283 + +GET {{Example.AspNetCore.WebApiAot_HostAddress}}/todos/ +Accept: application/json + +### + +GET {{Example.AspNetCore.WebApiAot_HostAddress}}/todos/1 +Accept: application/json + +### diff --git a/examples/Example.AspNetCore.WebApiAot/Program.cs b/examples/Example.AspNetCore.WebApiAot/Program.cs new file mode 100644 index 0000000..32f0fbe --- /dev/null +++ b/examples/Example.AspNetCore.WebApiAot/Program.cs @@ -0,0 +1,38 @@ +using System.Text.Json.Serialization; +using OpenTelemetry; +using OpenTelemetry.Trace; + +var builder = WebApplication.CreateSlimBuilder(args); + +builder.AddElasticOpenTelemetry(b => b.WithTracing(t => t.AddAspNetCoreInstrumentation())); + +builder.Services.ConfigureHttpJsonOptions(options => +{ + options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); +}); + +var app = builder.Build(); + +var sampleTodos = new Todo[] { + new(1, "Walk the dog"), + new(2, "Do the dishes", DateOnly.FromDateTime(DateTime.Now)), + new(3, "Do the laundry", DateOnly.FromDateTime(DateTime.Now.AddDays(1))), + new(4, "Clean the bathroom"), + new(5, "Clean the car", DateOnly.FromDateTime(DateTime.Now.AddDays(2))) +}; + +var todosApi = app.MapGroup("/todos"); +todosApi.MapGet("/", () => sampleTodos); +todosApi.MapGet("/{id}", (int id) => + sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo + ? Results.Ok(todo) + : Results.NotFound()); + +app.Run(); + +public record Todo(int Id, string? Title, DateOnly? DueBy = null, bool IsComplete = false); + +[JsonSerializable(typeof(Todo[]))] +internal partial class AppJsonSerializerContext : JsonSerializerContext +{ +} diff --git a/examples/Example.AspNetCore.WebApiAot/Properties/launchSettings.json b/examples/Example.AspNetCore.WebApiAot/Properties/launchSettings.json new file mode 100644 index 0000000..a88d9ce --- /dev/null +++ b/examples/Example.AspNetCore.WebApiAot/Properties/launchSettings.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "todos", + "applicationUrl": "http://localhost:5283", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/examples/Example.AspNetCore.WebApiAot/appsettings.Development.json b/examples/Example.AspNetCore.WebApiAot/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/examples/Example.AspNetCore.WebApiAot/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/examples/Example.AspNetCore.WebApiAot/appsettings.json b/examples/Example.AspNetCore.WebApiAot/appsettings.json new file mode 100644 index 0000000..351068d --- /dev/null +++ b/examples/Example.AspNetCore.WebApiAot/appsettings.json @@ -0,0 +1,15 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "Elastic": { + "OpenTelemetry": { + "LogLevel": "Trace", + "LogDirectory": "C:\\Logs\\AotLogs" + } + } +} diff --git a/src/Elastic.OpenTelemetry/Configuration/CompositeElasticOpenTelemetryOptions.cs b/src/Elastic.OpenTelemetry/Configuration/CompositeElasticOpenTelemetryOptions.cs index 29f67d3..560cd8f 100644 --- a/src/Elastic.OpenTelemetry/Configuration/CompositeElasticOpenTelemetryOptions.cs +++ b/src/Elastic.OpenTelemetry/Configuration/CompositeElasticOpenTelemetryOptions.cs @@ -39,6 +39,7 @@ internal sealed class CompositeElasticOpenTelemetryOptions private readonly ConfigCell _logLevel = new(nameof(LogLevel), LogLevel.Warning); private readonly ConfigCell _skipOtlpExporter = new(nameof(SkipOtlpExporter), false); + private readonly ConfigCell _skipInstrumentationAssemblyScanning = new(nameof(SkipInstrumentationAssemblyScanning), false); private readonly ConfigCell _runningInContainer = new(nameof(_runningInContainer), false); private readonly ConfigCell _signals = new(nameof(Signals), Signals.All); @@ -69,6 +70,7 @@ internal CompositeElasticOpenTelemetryOptions(IDictionary? environmentVariables) SetFromEnvironment(OTEL_LOG_LEVEL, _logLevel, LogLevelParser); SetFromEnvironment(ELASTIC_OTEL_LOG_TARGETS, _logTargets, LogTargetsParser); SetFromEnvironment(ELASTIC_OTEL_SKIP_OTLP_EXPORTER, _skipOtlpExporter, BoolParser); + SetFromEnvironment(ELASTIC_OTEL_SKIP_ASSEMBLY_SCANNING, _skipInstrumentationAssemblyScanning, BoolParser); var parser = new EnvironmentParser(_environmentVariables); parser.ParseInstrumentationVariables(_signals, _tracing, _metrics, _logging); @@ -86,6 +88,7 @@ internal CompositeElasticOpenTelemetryOptions(IConfiguration? configuration, IDi parser.ParseLogTargets(_logTargets); parser.ParseLogLevel(_logLevel, ref _eventLevel); parser.ParseSkipOtlpExporter(_skipOtlpExporter); + parser.ParseSkipInstrumentationAssemblyScanning(_skipInstrumentationAssemblyScanning); } internal CompositeElasticOpenTelemetryOptions(ElasticOpenTelemetryOptions options) @@ -192,6 +195,13 @@ public bool SkipOtlpExporter init => _skipOtlpExporter.Assign(value, ConfigSource.Property); } + /// + public bool SkipInstrumentationAssemblyScanning + { + get => _skipInstrumentationAssemblyScanning.Value ?? false; + init => _skipInstrumentationAssemblyScanning.Assign(value, ConfigSource.Property); + } + public ILogger? AdditionalLogger { get; internal set; } /// diff --git a/src/Elastic.OpenTelemetry/Configuration/ElasticOpenTelemetryOptions.cs b/src/Elastic.OpenTelemetry/Configuration/ElasticOpenTelemetryOptions.cs index 8706d85..e179f09 100644 --- a/src/Elastic.OpenTelemetry/Configuration/ElasticOpenTelemetryOptions.cs +++ b/src/Elastic.OpenTelemetry/Configuration/ElasticOpenTelemetryOptions.cs @@ -39,6 +39,9 @@ public class ElasticOpenTelemetryOptions /// DebugRich debugging and development. /// TraceContain the most detailed messages. /// + /// + /// When unset, this defaults to Warning. + /// /// public LogLevel? LogLevel { get; init; } @@ -48,8 +51,9 @@ public class ElasticOpenTelemetryOptions public LogTargets? LogTargets { get; init; } /// - /// Stops Elastic Distribution of OpenTelemetry .NET from registering OLTP exporters, useful for testing scenarios. + /// Skips registration of OLTP exporters by the Elastic Distribution of OpenTelemetry .NET. /// + /// When unset, this defaults to false. public bool? SkipOtlpExporter { get; init; } /// @@ -62,4 +66,10 @@ public class ElasticOpenTelemetryOptions /// to which logs will be written. /// public ILoggerFactory? AdditionalLoggerFactory { get; init; } + + /// + /// Skips automatic registration of instrumentation libraries via assembly scanning by the Elastic Distribution of OpenTelemetry .NET. + /// + /// When unset, this defaults to false. + public bool? SkipInstrumentationAssemblyScanning { get; init; } } diff --git a/src/Elastic.OpenTelemetry/Configuration/EnvironmentVariables.cs b/src/Elastic.OpenTelemetry/Configuration/EnvironmentVariables.cs index 7efeeb3..01991dd 100644 --- a/src/Elastic.OpenTelemetry/Configuration/EnvironmentVariables.cs +++ b/src/Elastic.OpenTelemetry/Configuration/EnvironmentVariables.cs @@ -9,6 +9,7 @@ internal static class EnvironmentVariables // ReSharper disable InconsistentNaming // ReSharper disable IdentifierTypo public const string ELASTIC_OTEL_SKIP_OTLP_EXPORTER = nameof(ELASTIC_OTEL_SKIP_OTLP_EXPORTER); + public const string ELASTIC_OTEL_SKIP_ASSEMBLY_SCANNING = nameof(ELASTIC_OTEL_SKIP_ASSEMBLY_SCANNING); public const string ELASTIC_OTEL_LOG_TARGETS = nameof(ELASTIC_OTEL_LOG_TARGETS); public const string OTEL_DOTNET_AUTO_LOG_DIRECTORY = nameof(OTEL_DOTNET_AUTO_LOG_DIRECTORY); diff --git a/src/Elastic.OpenTelemetry/Configuration/Parsers/ConfigurationParser.cs b/src/Elastic.OpenTelemetry/Configuration/Parsers/ConfigurationParser.cs index ea95fd8..034c217 100644 --- a/src/Elastic.OpenTelemetry/Configuration/Parsers/ConfigurationParser.cs +++ b/src/Elastic.OpenTelemetry/Configuration/Parsers/ConfigurationParser.cs @@ -39,6 +39,7 @@ private static void SetFromConfiguration(IConfiguration configuration, Config var lookup = configuration.GetValue($"{ConfigurationSection}:{cell.Key}"); if (lookup is null) return; + var parsed = parser(lookup); if (parsed is null) return; @@ -87,4 +88,7 @@ public void ParseLogLevel(ConfigCell logLevel, ref EventLevel eventLe public void ParseSkipOtlpExporter(ConfigCell skipOtlpExporter) => SetFromConfiguration(_configuration, skipOtlpExporter, BoolParser); + + public void ParseSkipInstrumentationAssemblyScanning(ConfigCell skipInstrumentationAssemblyScanning) => + SetFromConfiguration(_configuration, skipInstrumentationAssemblyScanning, BoolParser); } diff --git a/src/Elastic.OpenTelemetry/Core/AutoInstrumentationPlugin.cs b/src/Elastic.OpenTelemetry/Core/AutoInstrumentationPlugin.cs index c706a7c..6f892a1 100644 --- a/src/Elastic.OpenTelemetry/Core/AutoInstrumentationPlugin.cs +++ b/src/Elastic.OpenTelemetry/Core/AutoInstrumentationPlugin.cs @@ -87,7 +87,7 @@ public TracerProviderBuilder BeforeConfigureTracerProvider(TracerProviderBuilder public MeterProviderBuilder BeforeConfigureMeterProvider(MeterProviderBuilder builder) => !_bootstrapInfo.Succeeded || _components is null ? builder - : builder.UseElasticDefaults(_components); + : builder.UseAutoInstrumentationElasticDefaults(_components); /// /// To configure logs SDK (the method name is the same as for other logs options). diff --git a/src/Elastic.OpenTelemetry/Core/SignalBuilder.cs b/src/Elastic.OpenTelemetry/Core/SignalBuilder.cs index 7d5854c..e689e58 100644 --- a/src/Elastic.OpenTelemetry/Core/SignalBuilder.cs +++ b/src/Elastic.OpenTelemetry/Core/SignalBuilder.cs @@ -4,14 +4,44 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Reflection; using Elastic.OpenTelemetry.Configuration; +using Elastic.OpenTelemetry.Diagnostics; +using Elastic.OpenTelemetry.Instrumentation; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; namespace Elastic.OpenTelemetry.Core; +/// +/// Provides static helper methods which centralise provider builder logic used when registering EDOT +/// defaults on the various builders. +/// internal static class SignalBuilder { + /// + /// Returns the most relevant for builder extension methods to use. + /// + /// + /// + /// + public static ILogger GetLogger(ElasticOpenTelemetryComponents? components, CompositeElasticOpenTelemetryOptions? options) => + components?.Logger ?? options?.AdditionalLogger ?? NullLogger.Instance; + + public static bool IsSignalEnabled(ElasticOpenTelemetryComponents? components, CompositeElasticOpenTelemetryOptions? options, + Signals signalToCheck, string providerBuilderName, ILogger logger) + { + var configuredSignals = components?.Options.Signals ?? options?.Signals ?? Signals.All; + if (!configuredSignals.HasFlagFast(signalToCheck)) + { + logger.LogSignalDisabled(signalToCheck.ToString().ToLower(), providerBuilderName); + return false; + } + + return true; + } + /// /// Hold common logic for configuring a builder, either a TracerProviderBuilder, /// MeterProviderBuilder or LoggingProviderBuilder. @@ -32,7 +62,7 @@ public static bool ConfigureBuilder( // This scenario occurs if for example `AddElasticOpenTelemetry` is called multipled times // on the same `IServiceCollection`. In this case, a new `OpenTelemetryBuilder` would be // created (inside the SDK) for each call to `AddOpenTelemetry`, so the `BuilderState`, is - // not be useful. `TryBootstrap` would be eventually reuse cached components registered + // not useful. `TryBootstrap` would be eventually reuse cached components registered // against the `IServiceCollection`, but we can still be more efficient to avoid calling that // code in this particular case by shortcutting and returning early. if (options is not null && components is not null) @@ -44,14 +74,17 @@ public static bool ConfigureBuilder( // This will later be set to false if `CreateState`, is invoked. var existingStateFound = true; - // Note: This incurs a closure, but should only be called a few times at most during application + // Note: This incurs a closure, but it should only be called a few times at most during application // startup, so we are not too concerned with the performance impact. var state = ElasticOpenTelemetry.BuilderStateTable.GetValue(builder, _ => CreateState(builder, builderName, services, ref options, ref existingStateFound)); - components = state.Components; + // At this point, we either have cached components for the builder or a new instance + // created by the `CreateState` method. + + Debug.Assert(state.Components is not null); - Debug.Assert(components is not null); + components = state.Components; ValidateGlobalCallCount(methodName, builderName, options, components, callCount); @@ -130,4 +163,75 @@ static void ValidateGlobalCallCount(string methodName, string builderName, Compo } } } + + /// + /// Identifies whether a specific instrumentation assembly is present alongside the executing application. + /// + public static bool InstrumentationAssemblyExists(string assemblyName) + { + var assemblyLocation = Path.GetDirectoryName(AppContext.BaseDirectory); + + if (string.IsNullOrEmpty(assemblyLocation)) + return false; + + return File.Exists(Path.Combine(assemblyLocation, assemblyName)); + } + + [RequiresUnreferencedCode("Accesses assemblies and methods dynamically using refelction. This is by design and cannot be made trim compatible.")] + public static void AddInstrumentationViaReflection(T builder, ElasticOpenTelemetryComponents components, ReadOnlySpan assemblyInfos) + where T : class + { + if (components.Options.SkipInstrumentationAssemblyScanning) + return; + + var logger = components.Logger; + var builderTypeName = builder.GetType().Name; + var assemblyLocation = AppContext.BaseDirectory; + + if (!string.IsNullOrEmpty(assemblyLocation)) + { + foreach (var assemblyInfo in assemblyInfos) + { + try + { + var assemblyPath = Path.Combine(assemblyLocation, assemblyInfo.Filename); + if (File.Exists(assemblyPath)) + { + logger.LogLocatedInstrumentationAssembly(assemblyInfo.Filename, assemblyLocation); + + var assembly = Assembly.LoadFrom(assemblyPath); + var type = assembly.GetType(assemblyInfo.FullyQualifiedType); + + if (type is null) + { + logger.LogWarning("Unable to find {FullyQualifiedTypeName} in {AssemblyFullName}.", assemblyInfo.FullyQualifiedType, assembly.FullName); + continue; + } + + var methodInfo = type.GetMethod(assemblyInfo.InstrumentationMethod, BindingFlags.Static | BindingFlags.Public, + Type.DefaultBinder, [typeof(T)], null); + + if (methodInfo is null) + { + logger.LogWarning("Unable to find the {TypeName}.{Method} extension method in {AssemblyFullName}.", + assemblyInfo.FullyQualifiedType, assemblyInfo.InstrumentationMethod, assembly.FullName); + continue; + } + + methodInfo.Invoke(null, [builder]); // Invoke the extension method to register the instrumentation with the builder. + + logger.LogAddedInstrumentation(assemblyInfo.Name, builderTypeName); + } + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to dynamically enable {InstrumentationName} on {Provider}.", assemblyInfo.Name, builderTypeName); + } + } + } + else + { + logger.LogWarning("The result of `AppContext.BaseDirectory` was null or empty. Unable to perform instrumentation assembly scanning."); + } + } } diff --git a/src/Elastic.OpenTelemetry/Diagnostics/FileLogger.cs b/src/Elastic.OpenTelemetry/Diagnostics/FileLogger.cs index 420401b..0d8c914 100644 --- a/src/Elastic.OpenTelemetry/Diagnostics/FileLogger.cs +++ b/src/Elastic.OpenTelemetry/Diagnostics/FileLogger.cs @@ -69,9 +69,9 @@ public FileLogger(CompositeElasticOpenTelemetryOptions options) _streamWriter.AutoFlush = true; // Ensure we don't lose logs by not flushing to the file. if (options?.AdditionalLogger is not null) - options?.AdditionalLogger.LogInformation("File logging for EDOT .NET enabled. Logs are being written to '{LogFilePath}'", LogFilePath); + options?.AdditionalLogger.LogInformation("File logging for EDOT .NET enabled. Logs are being written to '{LogFilePath}'.", LogFilePath); else - Console.Out.WriteLine($"File logging for EDOT .NET enabled. Logs are being written to '{LogFilePath}'"); + Console.Out.WriteLine($"File logging for EDOT .NET enabled. Logs are being written to '{LogFilePath}'."); return; } diff --git a/src/Elastic.OpenTelemetry/Diagnostics/LoggerMessages.cs b/src/Elastic.OpenTelemetry/Diagnostics/LoggerMessages.cs index 9492ceb..d43af45 100644 --- a/src/Elastic.OpenTelemetry/Diagnostics/LoggerMessages.cs +++ b/src/Elastic.OpenTelemetry/Diagnostics/LoggerMessages.cs @@ -21,27 +21,47 @@ internal static partial class LoggerMessages [LoggerMessage(EventId = 3, EventName = "SharedComponentsReused", Level = LogLevel.Debug, Message = "Reusing existing shared components. {newline}{StackTrace}", SkipEnabledCheck = true)] public static partial void LogSharedComponentsReused(this ILogger logger, string newline, StackTrace stackTrace); - [LoggerMessage(EventId = 4, EventName = "SharedComponentsNotReused", Level = LogLevel.Debug, Message = "Unable to reuse existing shared components as the provided `CompositeElasticOpenTelemetryOptions` differ. {newline}{StackTrace}", SkipEnabledCheck = true)] + [LoggerMessage(EventId = 4, EventName = "SharedComponentsNotReused", Level = LogLevel.Debug, Message = "Unable to reuse existing shared components as the provided " + + "`CompositeElasticOpenTelemetryOptions` differ. {newline}{StackTrace}", SkipEnabledCheck = true)] public static partial void LogSharedComponentsNotReused(this ILogger logger, string newline, StackTrace stackTrace); [LoggerMessage(EventId = 5, EventName = "ServiceCollectionComponentsReused", Level = LogLevel.Debug, Message = "Reusing existing components on IServiceCollection. {newline}{StackTrace}")] public static partial void LogComponentsReused(this ILogger logger, string newline, StackTrace stackTrace); - [LoggerMessage(EventId = 6, EventName = "ConfiguredSignalProvider", Level = LogLevel.Debug, Message = "Configured EDOT defaults for {Signal} via the {Provider}.")] - public static partial void LogConfiguredSignalProvider(this ILogger logger, string signal, string provider); + [LoggerMessage(EventId = 6, EventName = "ConfiguredSignalProvider", Level = LogLevel.Debug, Message = "Configured EDOT defaults for {Signal} via the {ProviderBuilderType}.")] + public static partial void LogConfiguredSignalProvider(this ILogger logger, string signal, string providerBuilderType); - [LoggerMessage(EventId = 7, EventName = "SkippingOtlpExporter", Level = LogLevel.Information, Message = "Skipping OTLP exporter for {Signal} based on the provided `ElasticOpenTelemetryOptions` via the {Provider}.")] - public static partial void LogSkippingOtlpExporter(this ILogger logger, string signal, string provider); + [LoggerMessage(EventId = 7, EventName = "SkippingOtlpExporter", Level = LogLevel.Information, Message = "Skipping OTLP exporter for {Signal} based on the provided `ElasticOpenTelemetryOptions` " + + "via the {ProviderBuilderType}.")] + public static partial void LogSkippingOtlpExporter(this ILogger logger, string signal, string providerBuilderType); - [LoggerMessage(EventId = 8, EventName = "LocatedInstrumentationAssembly", Level = LogLevel.Information, Message = "Located {AssemblyFilename} in {Path}.")] + [LoggerMessage(EventId = 8, EventName = "LocatedInstrumentationAssembly", Level = LogLevel.Trace, Message = "Located {AssemblyFilename} in {Path}.")] public static partial void LogLocatedInstrumentationAssembly(this ILogger logger, string assemblyFilename, string path); - [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 = 9, EventName = "AddedInstrumentation", Level = LogLevel.Debug, Message = "Added {InstrumentationName} to {ProviderBuilderType}.")] + public static partial void LogAddedInstrumentation(this ILogger logger, string instrumentationName, string providerBuilderType); - [LoggerMessage(EventId = 10, EventName = "HttpInstrumentationFound", Level = LogLevel.Information, Message = "The HTTP instrumentation library was located at '{AssemblyPath}'. " + + [LoggerMessage(EventId = 10, EventName = "AddedInstrumentationViaReflection", Level = LogLevel.Debug, Message = "Added {InstrumentationName} to {ProviderBuilderType} via reflection assembly scanning.")] + public static partial void LogAddedInstrumentationViaReflection(this ILogger logger, string instrumentationName, string providerBuilderType); + + [LoggerMessage(EventId = 11, EventName = "HttpInstrumentationFound", Level = LogLevel.Debug, Message = "The contrib HTTP instrumentation library was located alongside the executing assembly. " + "Skipping adding native {InstrumentationType} instrumentation from the 'System.Net.Http' ActivitySource.")] - public static partial void LogHttpInstrumentationFound(this ILogger logger, string assemblyPath, string instrumentationType); + public static partial void LogHttpInstrumentationFound(this ILogger logger, string instrumentationType); + + [LoggerMessage(EventId = 12, EventName = "RuntimeInstrumentationFound", Level = LogLevel.Debug, Message = "The contrib runtime instrumentation library was located alongside the executing assembly. " + + "Skipping adding native metric instrumentation from the 'System.Runtime' ActivitySource.")] + public static partial void LogRuntimeInstrumentationFound(this ILogger logger); + + [LoggerMessage(EventId = 13, EventName = "SignalDisabled", Level = LogLevel.Information, Message = "Skipping configuring and setting EDOT defaults for {Signal}, as these have been disabled via configuration.")] + public static partial void LogSignalDisabled(this ILogger logger, string signal); + + [LoggerMessage(EventId = 14, EventName = "ProviderBuilderSignalDisabled", Level = LogLevel.Information, Message = "Skipping configuring and setting EDOT defaults for {Signal} on {ProviderBuilderType}, " + + "as these have been disabled via configuration.")] + public static partial void LogSignalDisabled(this ILogger logger, string signal, string providerBuilderType); + + [LoggerMessage(EventId = 15, EventName = "SkippingBootstrap", Level = LogLevel.Warning, Message = "Skipping EDOT bootstrap and provider configuration because the `Signals` configuration is set to `None`. " + + "This likely represents a misconfiguration. If you do not want to use the EDOT for any signals, avoid calling `WithElasticDefaults` on the builder.")] + public static partial void LogSkippingBootstrapWarning(this ILogger logger); // 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.")] diff --git a/src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj b/src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj index 5519386..8daa29b 100644 --- a/src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj +++ b/src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj @@ -12,7 +12,11 @@ false $(NoWarn);OTEL1000 latest - + + + + true + true @@ -23,9 +27,7 @@ - - @@ -42,7 +44,11 @@ - + + + + + diff --git a/src/Elastic.OpenTelemetry/Extensions/HostApplicationBuilderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/HostApplicationBuilderExtensions.cs index 5683111..0af5eb0 100644 --- a/src/Elastic.OpenTelemetry/Extensions/HostApplicationBuilderExtensions.cs +++ b/src/Elastic.OpenTelemetry/Extensions/HostApplicationBuilderExtensions.cs @@ -43,27 +43,28 @@ public static IHostApplicationBuilder AddElasticOpenTelemetry(this IHostApplicat throw new ArgumentNullException(nameof(serviceName)); #endif - return AddElasticOpenTelemetry(builder, r => r.AddService(serviceName)); + return AddElasticOpenTelemetry(builder, r => r.ConfigureResource(r => r.AddService(serviceName))); } /// /// /// /// - /// configuration action. + /// configuration action. /// - public static IHostApplicationBuilder AddElasticOpenTelemetry(this IHostApplicationBuilder builder, Action configureResource) + public static IHostApplicationBuilder AddElasticOpenTelemetry(this IHostApplicationBuilder builder, Action configure) { #if NET - ArgumentNullException.ThrowIfNull(configureResource); + ArgumentNullException.ThrowIfNull(configure); #else - if (configureResource is null) - throw new ArgumentNullException(nameof(configureResource)); + if (configure is null) + throw new ArgumentNullException(nameof(configure)); #endif - builder.Services - .AddElasticOpenTelemetry(builder.Configuration) - .ConfigureResource(configureResource); + var otelBuilder = builder.Services + .AddElasticOpenTelemetry(builder.Configuration); + + configure.Invoke(otelBuilder); return builder; } diff --git a/src/Elastic.OpenTelemetry/Extensions/LoggingProviderBuilderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/LoggingProviderBuilderExtensions.cs index 0d6a046..0fb2ad5 100644 --- a/src/Elastic.OpenTelemetry/Extensions/LoggingProviderBuilderExtensions.cs +++ b/src/Elastic.OpenTelemetry/Extensions/LoggingProviderBuilderExtensions.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; // Matching namespace with LoggerProviderBuilder #pragma warning disable IDE0130 // Namespace does not match folder structure @@ -98,22 +99,28 @@ internal static LoggerProviderBuilder UseElasticDefaultsCore( ElasticOpenTelemetryComponents? components, IServiceCollection? services = null) { - var logger = components?.Logger ?? options?.AdditionalLogger; + const string providerBuilderName = nameof(LoggerProviderBuilder); + + var logger = SignalBuilder.GetLogger(components, options); + + // If the signal is disabled via configuration we skip any potential bootstrapping. + if (!SignalBuilder.IsSignalEnabled(components, options, Signals.Logs, providerBuilderName, logger)) + return builder; try { - if (!SignalBuilder.ConfigureBuilder(nameof(UseElasticDefaults), nameof(LoggerProviderBuilder), builder, + if (!SignalBuilder.ConfigureBuilder(nameof(UseElasticDefaults), providerBuilderName, builder, GlobalLoggerProviderBuilderState, options, services, ConfigureBuilder, ref components)) { - logger = components?.Logger ?? options?.AdditionalLogger; // Update with ref-returned components - logger?.UnableToConfigureLoggingDefaultsError(nameof(LoggerProviderBuilder)); + logger = components?.Logger ?? options?.AdditionalLogger ?? NullLogger.Instance; // Update the logger we should use from the ref-returned components. + logger.UnableToConfigureLoggingDefaultsError(providerBuilderName); return builder; } } catch (Exception ex) { // NOTE: Not using LoggerMessage as we want to pass the exception. As this should be rare, performance isn't critical here. - logger?.LogError(ex, "Failed to fully register EDOT .NET logging defaults for {Provider}.", nameof(LoggerProviderBuilder)); + logger.LogError(ex, "Failed to fully register EDOT .NET logging defaults for {ProviderBuilderType}.", providerBuilderName); } return builder; @@ -122,7 +129,7 @@ static void ConfigureBuilder(LoggerProviderBuilder builder, ElasticOpenTelemetry { builder.ConfigureResource(r => r.AddElasticDistroAttributes()); - if (components.Options.SkipOtlpExporter || components.Options.SkipOtlpExporter) + if (components.Options.SkipOtlpExporter) { components.Logger.LogSkippingOtlpExporter(nameof(Signals.Logs), nameof(LoggerProviderBuilder)); } diff --git a/src/Elastic.OpenTelemetry/Extensions/MeterProviderBuilderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/MeterProviderBuilderExtensions.cs index 320ea9b..be3344f 100644 --- a/src/Elastic.OpenTelemetry/Extensions/MeterProviderBuilderExtensions.cs +++ b/src/Elastic.OpenTelemetry/Extensions/MeterProviderBuilderExtensions.cs @@ -2,8 +2,8 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Reflection; using System.Runtime.CompilerServices; using Elastic.OpenTelemetry; using Elastic.OpenTelemetry.Configuration; @@ -13,6 +13,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using OpenTelemetry.Trace; // Matching namespace with MeterProviderBuilder #pragma warning disable IDE0130 // Namespace does not match folder structure @@ -27,42 +29,12 @@ public static class MeterProviderBuilderExtensions { private static readonly GlobalProviderBuilderState GlobalMeterProviderBuilderState = new(); - // Note: This is defined as a static method and allocates the array each time. - // This is intentional, as we expect this to be invoked once (or worst case, few times). - // After initialisation, the array is no longer required and can be reclaimed by the GC. - // This is likley to be overall more efficient for the common scenario as we don't keep - // an object alive for the lifetime of the application. - private static InstrumentationAssemblyInfo[] GetReflectionInstrumentationAssemblies() => - [ - new() - { - Name = "AspNetCore", - 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 - ]; - /// /// Use Elastic Distribution of OpenTelemetry .NET defaults for . /// /// - /// This is not neccesary if - /// has been called previously as that automatically adds the . + /// Calling this method is not neccesary if + /// has been called previously as that automatically adds the defaults for all signals. /// /// The to configure. /// The for chaining configuration. @@ -72,6 +44,7 @@ public static MeterProviderBuilder UseElasticDefaults(this MeterProviderBuilder /// /// /// + /// /// /// When registering Elastic defaults, skip automatic registration of the OTLP exporter for metrics. /// @@ -140,28 +113,35 @@ internal static MeterProviderBuilder UseElasticDefaults( IServiceCollection serviceCollection) => UseElasticDefaultsCore(builder, null, null, serviceCollection); - [RequiresDynamicCode("Requires reflection for dynamic assembly loading and instrumentation activation.")] - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026", Justification = "The calls to `AddSqlClientInstrumentation` and `AssemblyScanning.AddInstrumentationViaReflection` " + + "are guarded by a RuntimeFeature.IsDynamicCodeSupported` check and therefore this method is safe to call in AoT scenarios.")] internal static MeterProviderBuilder UseElasticDefaultsCore( this MeterProviderBuilder builder, CompositeElasticOpenTelemetryOptions? options, ElasticOpenTelemetryComponents? components, IServiceCollection? services = null) { + const string providerBuilderName = nameof(MeterProviderBuilder); + + var logger = SignalBuilder.GetLogger(components, options); + + // If the signal is disabled via configuration we skip any potential bootstrapping. + if (!SignalBuilder.IsSignalEnabled(components, options, Signals.Metrics, providerBuilderName, logger)) + return builder; + try { - if (!SignalBuilder.ConfigureBuilder(nameof(UseElasticDefaults), nameof(MeterProviderBuilder), builder, + if (!SignalBuilder.ConfigureBuilder(nameof(UseElasticDefaults), providerBuilderName, builder, GlobalMeterProviderBuilderState, options, services, ConfigureBuilder, ref components)) { - var logger = components?.Logger ?? options?.AdditionalLogger; - logger?.LogError("Unable to configure {Builder} with Elastic defaults.", nameof(MeterProviderBuilder)); + logger = components?.Logger ?? options?.AdditionalLogger ?? NullLogger.Instance; // Update the logger we should use from the ref-returned components. + logger.UnableToConfigureLoggingDefaultsError(providerBuilderName); return builder; } } catch (Exception ex) { - var exceptionLogger = components is not null ? components.Logger : options?.AdditionalLogger; - exceptionLogger?.LogError(ex, "Failed to fully register EDOT .NET meter defaults for {Provider}.", nameof(MeterProviderBuilder)); + logger.LogError(ex, "Failed to fully register EDOT .NET meter defaults for {ProviderBuilderType}.", providerBuilderName); } return builder; @@ -171,46 +151,60 @@ static void ConfigureBuilder(MeterProviderBuilder builder, ElasticOpenTelemetryC builder.ConfigureResource(r => r.AddElasticDistroAttributes()); #if NET9_0_OR_GREATER - try + // On .NET 9, the contrib HTTP instrumentation is no longer required. If the dependency exists, + // it will be registered via the reflection-based assembly scanning. + if (SignalBuilder.InstrumentationAssemblyExists("OpenTelemetry.Instrumentation.Http.dll")) { - // 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"); - } - } + components.Logger.LogHttpInstrumentationFound("metric"); + + // For native AOT scenarios, the reflection-based assembly scanning will not run. + // Therefore, we log a warning since no HTTP instrumentation will be automatically registered. + // In this scenario, the consumer must register the contrib instrumentation manually, or + // remove the dependency so that the native .NET 9 HTTP instrumentation source will be added + // instead. + if (!RuntimeFeature.IsDynamicCodeSupported) + components.Logger.LogWarning("The OpenTelemetry.Instrumentation.Http.dll was found alongside the executing assembly. " + + "When using Native AOT publishing on .NET, the metric instrumentation is not registered automatically. Either register it manually, " + + "or remove the dependency so that the native `System.Net.Http` instrumentation (available in .NET 9) is observed instead."); } - catch (Exception ex) + else { - components.Logger.LogError(ex, "An exception occurred while checking for the presence of `OpenTelemetry.Instrumentation.Http.dll`."); + AddWithLogging(builder, components.Logger, "HTTP (via native instrumentation)", b => b.AddMeter("System.Net.Http")); } -#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")); + // On .NET 9, the contrib runtime instrumentation is no longer required. If the dependency exists, + // it will be registered via the reflection-based assembly scanning. + if (SignalBuilder.InstrumentationAssemblyExists("OpenTelemetry.Instrumentation.Runtime.dll")) + { + components.Logger.LogRuntimeInstrumentationFound(); + + // For native AOT scenarios, the reflection-based assembly scanning will not run. + // Therefore, we log a warning since no runtime metric instrumentation will be automatically registered. + // In this scenario, the consumer must register the contrib instrumentation manually, or + // remove the dependency so that the native .NET 9 runtime metric instrumentation source will be added + // instead. + if (!RuntimeFeature.IsDynamicCodeSupported) + components.Logger.LogWarning("The OpenTelemetry.Instrumentation.Runtime.dll was found alongside the executing assembly. " + + "When using Native AOT publishing on .NET, the metric instrumentation is not registered automatically. Either register it manually, " + + "or remove the dependency so that the native `System.Runtime` instrumentation (available in .NET 9) is observed instead."); + } + else + { + AddWithLogging(builder, components.Logger, "Runtime", b => b.AddMeter("System.Runtime")); + } #else + AddWithLogging(builder, components.Logger, "HTTP (via contrib instrumentation)", b => b.AddHttpClientInstrumentation()); 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 - AddInstrumentationViaReflection(builder, components.Logger); +#if NET + if (RuntimeFeature.IsDynamicCodeSupported) +#endif + { + SignalBuilder.AddInstrumentationViaReflection(builder, components, ContribMetricsInstrumentation.GetMetricsInstrumentationAssembliesInfo()); + } - if (components.Options.SkipOtlpExporter || components.Options.SkipOtlpExporter) + if (components.Options.SkipOtlpExporter) { components.Logger.LogSkippingOtlpExporter(nameof(Signals.Traces), nameof(MeterProviderBuilder)); } @@ -222,66 +216,44 @@ static void ConfigureBuilder(MeterProviderBuilder builder, ElasticOpenTelemetryC components.Logger.LogConfiguredSignalProvider(nameof(Signals.Logs), nameof(MeterProviderBuilder)); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] static void AddWithLogging(MeterProviderBuilder builder, ILogger logger, string name, Action add) { add.Invoke(builder); logger.LogAddedInstrumentation(name, nameof(MeterProviderBuilder)); } + } + + // We use a different method here to ensure we don't cause a crash depending on instrumentation libraries which are not present. + // We can't assume that any DLLs are available besides OpenTelemetry.dll, which auto-instrumentation includes. + // The auto instrumentation enables a set of default instrumentation of it's own, so we rely on that. + // In the future, we can assess if we should copy instrumentation DLLs into the autoinstrumentation zip file and enable them. + internal static MeterProviderBuilder UseAutoInstrumentationElasticDefaults(this MeterProviderBuilder builder, ElasticOpenTelemetryComponents components) + { + Debug.Assert(components is not null, "Components should not be null when invoked from the auto instrumentation."); - static void AddInstrumentationViaReflection(MeterProviderBuilder builder, ILogger logger) + try { - try + builder.ConfigureResource(r => r.AddElasticDistroAttributes()); + + if (components.Options.SkipOtlpExporter) { - // This section is in its own try/catch because we don't want failures in the reflection-based - // registration to prevent completion of registering the more general defaults we apply. - - var assemblyLocation = Path.GetDirectoryName(typeof(ElasticOpenTelemetry).Assembly.Location); - if (assemblyLocation is not null) - { - foreach (var assembly in GetReflectionInstrumentationAssemblies()) - AddInstrumentationLibraryViaReflection(builder, logger, assemblyLocation, assembly); - } + components.Logger.LogSkippingOtlpExporter(nameof(Signals.Traces), nameof(TracerProviderBuilder)); } - catch + else { - // TODO - Logging + builder.AddOtlpExporter(); } - } - static void AddInstrumentationLibraryViaReflection( - MeterProviderBuilder builder, - ILogger logger, - string assemblyLocation, - in InstrumentationAssemblyInfo info) + components.Logger.LogConfiguredSignalProvider("Traces", nameof(TracerProviderBuilder)); + + return builder; + } + catch (Exception ex) { - try - { - var assemblyPath = Path.Combine(assemblyLocation, info.Filename); - - if (File.Exists(Path.Combine(assemblyLocation, info.Filename))) - { - logger.LogLocatedInstrumentationAssembly(info.Filename, assemblyLocation); - - var assembly = Assembly.LoadFrom(assemblyPath); - var type = assembly?.GetType(info.FullyQualifiedType); - var method = type?.GetMethod(info.InstrumentationMethod, BindingFlags.Static | BindingFlags.Public, - Type.DefaultBinder, [typeof(MeterProviderBuilder)], null); - - if (method is not null) - { - logger.LogAddedInstrumentation(info.Name, nameof(MeterProviderBuilder)); - method.Invoke(null, [builder]); - } - else - { - logger.LogWarning("Unable to invoke {TypeName}.{Method} on {AssemblyPath}.", info.FullyQualifiedType, info.InstrumentationMethod, assemblyPath); - } - } - } - catch (Exception ex) - { - logger.LogError(ex, "Failed to dynamically enable {InstrumentationName} on {Provider}.", info.Name, nameof(MeterProviderBuilder)); - } + components?.Logger?.LogError(ex, "Failed to register EDOT defaults for meter auto-instrumentation to the {Provider}.", nameof(TracerProviderBuilder)); } + + return builder; } } diff --git a/src/Elastic.OpenTelemetry/Extensions/OpenTelemetryBuilderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/OpenTelemetryBuilderExtensions.cs index f74633a..2b13b2b 100644 --- a/src/Elastic.OpenTelemetry/Extensions/OpenTelemetryBuilderExtensions.cs +++ b/src/Elastic.OpenTelemetry/Extensions/OpenTelemetryBuilderExtensions.cs @@ -6,6 +6,7 @@ using Elastic.OpenTelemetry; using Elastic.OpenTelemetry.Configuration; using Elastic.OpenTelemetry.Core; +using Elastic.OpenTelemetry.Diagnostics; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.Metrics; @@ -86,7 +87,16 @@ internal static IOpenTelemetryBuilder WithElasticDefaultsCore( this IOpenTelemetryBuilder builder, CompositeElasticOpenTelemetryOptions options) { - var usingExistingState = true; // Will be set to false, if we later create state for this builder. + // If for some reason `WithElasticDefaults` is invoked with the `Signals` option set to + // none, we skip bootstrapping entirely. We log this as a warning since it's best to + // simply not call `WithElasticDefaults` in this scenario and may indicate a misconfiguration. + if (options.Signals == Signals.None) + { + options.AdditionalLogger?.LogSkippingBootstrapWarning(); + return builder; + } + + var usingExistingState = true; // Will be set to false if we later create state for this builder. // Attempt to load existing state if any Elastic extension methods on this builder have been called // previously. This allows reuse of existing components, and ensures we bootstrap once per builder. @@ -111,16 +121,16 @@ internal static IOpenTelemetryBuilder WithElasticDefaultsCore( if (builderState.UseElasticDefaultsCounter > 1) { - // TODO - Log warning + // TODO - Log warning - https://github.com/elastic/elastic-otel-dotnet/issues/216 } else if (callCount > 1) { - // TODO - Log warning + // TODO - Log warning - https://github.com/elastic/elastic-otel-dotnet/issues/216 } if (!usingExistingState) { - // TODO - Log + // TODO - Log - https://github.com/elastic/elastic-otel-dotnet/issues/216 } var bootstrapInfo = builderState.BootstrapInfo; @@ -136,9 +146,32 @@ internal static IOpenTelemetryBuilder WithElasticDefaultsCore( return builder; } - builder.WithLogging(b => b.UseElasticDefaults(components, builder.Services)); - builder.WithMetrics(b => b.UseElasticDefaults(components, builder.Services)); - builder.WithTracing(b => b.UseElasticDefaults(components, builder.Services)); + if (options.Signals.HasFlagFast(Signals.Traces)) + { + builder.WithTracing(b => b.UseElasticDefaults(components, builder.Services)); + } + else + { + components.Logger.LogSignalDisabled(Signals.Traces.ToString().ToLower()); + } + + if (options.Signals.HasFlagFast(Signals.Metrics)) + { + builder.WithMetrics(b => b.UseElasticDefaults(components, builder.Services)); + } + else + { + components.Logger.LogSignalDisabled(Signals.Metrics.ToString().ToLower()); + } + + if (options.Signals.HasFlagFast(Signals.Logs)) + { + builder.WithLogging(b => b.UseElasticDefaults(components, builder.Services)); + } + else + { + components.Logger.LogSignalDisabled(Signals.Logs.ToString().ToLower()); + } return builder; } diff --git a/src/Elastic.OpenTelemetry/Extensions/TracerProviderBuilderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/TracerProviderBuilderExtensions.cs index 9b4c619..c49e77a 100644 --- a/src/Elastic.OpenTelemetry/Extensions/TracerProviderBuilderExtensions.cs +++ b/src/Elastic.OpenTelemetry/Extensions/TracerProviderBuilderExtensions.cs @@ -4,7 +4,6 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Reflection; using System.Runtime.CompilerServices; using Elastic.OpenTelemetry; using Elastic.OpenTelemetry.Configuration; @@ -30,36 +29,6 @@ public static class TracerProviderBuilderExtensions { private static readonly GlobalProviderBuilderState GlobalTracerProviderBuilderState = new(); - // Note: This is defined as a static method and allocates the array each time. - // This is intentional, as we expect this to be invoked once (or worst case, few times). - // After initialisation, the array is no longer required and can be reclaimed by the GC. - // This is likley to be overall more efficient for the common scenario as we don't keep - // an object alive for the lifetime of the application. - private static InstrumentationAssemblyInfo[] GetReflectionInstrumentationAssemblies() => - [ - new() - { - Name = "AspNetCore", - 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 - ]; - /// /// Use Elastic Distribution of OpenTelemetry .NET defaults for . /// @@ -68,30 +37,56 @@ private static InstrumentationAssemblyInfo[] GetReflectionInstrumentationAssembl /// has been called previously as that automatically adds the . /// /// The to configure. + /// Thrown when the is null. /// The for chaining configuration. - public static TracerProviderBuilder UseElasticDefaults(this TracerProviderBuilder builder) => - UseElasticDefaultsCore(builder, null, null); + public static TracerProviderBuilder UseElasticDefaults(this TracerProviderBuilder builder) + { +#if NET + ArgumentNullException.ThrowIfNull(builder); +#else + if (builder is null) + throw new ArgumentNullException(nameof(builder)); +#endif + + return UseElasticDefaultsCore(builder, null, null); + } /// /// /// /// /// When registering Elastic defaults, skip automatic registration of the OTLP exporter for traces. + /// Thrown when the is null. /// - public static TracerProviderBuilder UseElasticDefaults(this TracerProviderBuilder builder, bool skipOtlpExporter) => - UseElasticDefaultsCore(builder, skipOtlpExporter ? CompositeElasticOpenTelemetryOptions.SkipOtlpOptions : null, null); + public static TracerProviderBuilder UseElasticDefaults(this TracerProviderBuilder builder, bool skipOtlpExporter) + { +#if NET + ArgumentNullException.ThrowIfNull(builder); +#else + if (builder is null) + throw new ArgumentNullException(nameof(builder)); +#endif + + return UseElasticDefaultsCore(builder, skipOtlpExporter ? CompositeElasticOpenTelemetryOptions.SkipOtlpOptions : null, null); + } /// /// /// /// /// used to configure the Elastic Distribution of OpenTelemetry (EDOT) for .NET. + /// Thrown when the is null. + /// Thrown when the is null. /// public static TracerProviderBuilder UseElasticDefaults(this TracerProviderBuilder builder, ElasticOpenTelemetryOptions options) { #if NET + ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(options); #else + if (builder is null) + throw new ArgumentNullException(nameof(builder)); + if (options is null) throw new ArgumentNullException(nameof(options)); #endif @@ -104,16 +99,22 @@ public static TracerProviderBuilder UseElasticDefaults(this TracerProviderBuilde /// /// /// An instance from which to load the OpenTelemetry SDK options. + /// Thrown when the is null. + /// Thrown when the is null. /// public static TracerProviderBuilder UseElasticDefaults(this TracerProviderBuilder builder, IConfiguration configuration) { #if NET + ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(configuration); #else + if (builder is null) + throw new ArgumentNullException(nameof(builder)); + if (configuration is null) throw new ArgumentNullException(nameof(configuration)); #endif - return UseElasticDefaultsCore(builder, new(configuration), null); + return UseElasticDefaultsCore(builder, new(configuration)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -131,78 +132,80 @@ internal static TracerProviderBuilder UseElasticDefaults( internal static TracerProviderBuilder UseElasticDefaults(this TracerProviderBuilder builder, ElasticOpenTelemetryComponents components, IServiceCollection? services) => UseElasticDefaultsCore(builder, components.Options, components, services); - [RequiresDynamicCode("Requires reflection for dynamic assembly loading and instrumentation activation.")] - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static TracerProviderBuilder UseElasticDefaultsCore( TracerProviderBuilder builder, CompositeElasticOpenTelemetryOptions? options, - ElasticOpenTelemetryComponents? components, + ElasticOpenTelemetryComponents? components = null, IServiceCollection? services = null) { + const string providerBuilderName = nameof(MeterProviderBuilder); + + var logger = SignalBuilder.GetLogger(components, options); + + // If the signal is disabled via configuration we skip any potential bootstrapping. + if (!SignalBuilder.IsSignalEnabled(components, options, Signals.Traces, providerBuilderName, logger)) + return builder; + try { - if (!SignalBuilder.ConfigureBuilder(nameof(UseElasticDefaults), nameof(TracerProviderBuilder), builder, + if (!SignalBuilder.ConfigureBuilder(nameof(UseElasticDefaults), providerBuilderName, builder, GlobalTracerProviderBuilderState, options, services, ConfigureBuilder, ref components)) { - var logger = components?.Logger ?? options?.AdditionalLogger; - logger?.LogError("Unable to configure {Builder} with Elastic defaults.", nameof(TracerProviderBuilder)); + logger = SignalBuilder.GetLogger(components, options); // Update the logger we should use from the ref-returned components. + logger.UnableToConfigureLoggingDefaultsError(providerBuilderName); return builder; } } catch (Exception ex) { - var exceptionLogger = components is not null ? components.Logger : options?.AdditionalLogger; - exceptionLogger?.LogError(ex, "Failed to fully register EDOT .NET tracer defaults for {Provider}.", nameof(TracerProviderBuilder)); + logger?.LogError(ex, "Failed to fully register EDOT .NET tracer defaults for {ProviderBuilderType}.", providerBuilderName); } return builder; + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026", Justification = "The calls to `AddSqlClientInstrumentation` and `AssemblyScanning.AddInstrumentationViaReflection` " + + "are guarded by a RuntimeFeature.IsDynamicCodeSupported` check and therefore this method is safe to call in AoT scenarios.")] static void ConfigureBuilder(TracerProviderBuilder builder, ElasticOpenTelemetryComponents components) { builder.ConfigureResource(r => r.AddElasticDistroAttributes()); #if NET9_0_OR_GREATER - try + if (SignalBuilder.InstrumentationAssemblyExists("OpenTelemetry.Instrumentation.Http.dll")) { - // 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"); - } - } + components.Logger.LogHttpInstrumentationFound("trace"); + + if (!RuntimeFeature.IsDynamicCodeSupported) + components.Logger.LogWarning("The OpenTelemetry.Instrumentation.Http.dll was found alongside the executing assembly. " + + "When using Native AOT publishing on .NET, the trace instrumentation is not registered automatically. Either register it manually, " + + "or remove the dependency so that the native `System.Net.Http` instrumentation (available in .NET 9) is observed instead."); } - catch (Exception ex) + else { - components.Logger.LogError(ex, "An exception occurred while checking for the presence of `OpenTelemetry.Instrumentation.Http.dll`."); + AddWithLogging(builder, components.Logger, "HTTP (via native instrumentation)", b => b.AddSource("System.Net.Http")); } #else - AddWithLogging(builder, components.Logger, "Http (via contrib instrumentation)", b => b.AddHttpClientInstrumentation()); + 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()); - AddWithLogging(builder, components.Logger, "SqlClient", b => b.AddSqlClientInstrumentation()); AddWithLogging(builder, components.Logger, "ElasticTransport", b => b.AddSource("Elastic.Transport")); - // TODO - Guard this behind runtime checks e.g. RuntimeFeature.IsDynamicCodeSupported to support AoT users. - // see https://github.com/elastic/elastic-otel-dotnet/issues/198 - AddInstrumentationViaReflection(builder, components.Logger); + // NOTE: Despite them having no dependencies. We cannot add the OpenTelemetry.Instrumentation.ElasticsearchClient or + // OpenTelemetry.Instrumentation.EntityFrameworkCore instrumentations here, as including the package references causes + // trimming warnings. We can still add them via reflection. + +#if NET + if (RuntimeFeature.IsDynamicCodeSupported) +#endif + { + // This instrumentation is not currently compatible for AoT scenarios. + AddWithLogging(builder, components.Logger, "SqlClient", b => b.AddSqlClientInstrumentation()); + SignalBuilder.AddInstrumentationViaReflection(builder, components, ContribTraceInstrumentation.GetReflectionInstrumentationAssemblies()); + } AddElasticProcessorsCore(builder, components); - if (components.Options.SkipOtlpExporter || components.Options.SkipOtlpExporter) + if (components.Options.SkipOtlpExporter) { components.Logger.LogSkippingOtlpExporter(nameof(Signals.Traces), nameof(TracerProviderBuilder)); } @@ -214,62 +217,12 @@ static void ConfigureBuilder(TracerProviderBuilder builder, ElasticOpenTelemetry components.Logger.LogConfiguredSignalProvider(nameof(Signals.Traces), nameof(TracerProviderBuilder)); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] static void AddWithLogging(TracerProviderBuilder builder, ILogger logger, string name, Action add) { add.Invoke(builder); logger.LogAddedInstrumentation(name, nameof(TracerProviderBuilder)); } - - static void AddInstrumentationViaReflection(TracerProviderBuilder builder, ILogger logger) - { - try - { - // This section is in its own try/catch because we don't want failures in the reflection-based - // registration to prevent completion of registering the more general defaults we apply. - - var assemblyLocation = Path.GetDirectoryName(typeof(ElasticOpenTelemetry).Assembly.Location); - if (assemblyLocation is not null) - { - foreach (var assembly in GetReflectionInstrumentationAssemblies()) - AddInstrumentationLibraryViaReflection(builder, logger, assemblyLocation, assembly); - } - } - catch (Exception ex) - { - logger.LogError(ex, "An exception occurred while adding instrumentation via reflection."); - } - } - - static void AddInstrumentationLibraryViaReflection( - TracerProviderBuilder builder, - ILogger logger, - string assemblyLocation, - in InstrumentationAssemblyInfo info) - { - try - { - var assemblyPath = Path.Combine(assemblyLocation, info.Filename); - if (File.Exists(assemblyPath)) - { - logger.LogLocatedInstrumentationAssembly(info.Filename, assemblyLocation); - - var assembly = Assembly.LoadFrom(assemblyPath); - var type = assembly?.GetType(info.FullyQualifiedType); - var method = type?.GetMethod(info.InstrumentationMethod, BindingFlags.Static | BindingFlags.Public, - Type.DefaultBinder, [typeof(TracerProviderBuilder)], null); - - if (method is not null) - { - logger.LogAddedInstrumentation(info.Name, nameof(TracerProviderBuilder)); - method.Invoke(null, [builder]); - } - } - } - catch (Exception ex) - { - logger.LogError(ex, "Failed to dynamically enable {InstrumentationName} on {Provider}.", info.Name, nameof(TracerProviderBuilder)); - } - } } // We use a different method here to ensure we don't cause a crash depending on instrumentation libraries which are not present. @@ -316,9 +269,19 @@ internal static TracerProviderBuilder UseAutoInstrumentationElasticDefaults(this /// /// The where the Elastic trace /// processors should be added. + /// Thrown when the is null. /// The for chaining. - public static TracerProviderBuilder AddElasticProcessors(this TracerProviderBuilder builder) => - AddElasticProcessorsCore(builder, null); + public static TracerProviderBuilder AddElasticProcessors(this TracerProviderBuilder builder) + { +#if NET + ArgumentNullException.ThrowIfNull(builder); +#else + if (builder is null) + throw new ArgumentNullException(nameof(builder)); +#endif + + return AddElasticProcessorsCore(builder, null); + } private static TracerProviderBuilder AddElasticProcessorsCore( this TracerProviderBuilder builder, @@ -344,12 +307,14 @@ private static TracerProviderBuilder AddElasticProcessorsCore( return builder; + [MethodImpl(MethodImplOptions.AggressiveInlining)] static void ConfigureBuilder(TracerProviderBuilder builder, ElasticOpenTelemetryComponents components) { builder.LogAndAddProcessor(new ElasticCompatibilityProcessor(components.Logger), components.Logger); } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static TracerProviderBuilder LogAndAddProcessor(this TracerProviderBuilder builder, BaseProcessor processor, ILogger logger) { builder.AddProcessor(processor); diff --git a/src/Elastic.OpenTelemetry/Instrumentation/ContribMetricsInstrumentation.cs b/src/Elastic.OpenTelemetry/Instrumentation/ContribMetricsInstrumentation.cs new file mode 100644 index 0000000..d0efe3c --- /dev/null +++ b/src/Elastic.OpenTelemetry/Instrumentation/ContribMetricsInstrumentation.cs @@ -0,0 +1,95 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +namespace Elastic.OpenTelemetry.Instrumentation; + +internal static class ContribMetricsInstrumentation +{ + // Note: This is defined as a static method and allocates the array each time. + // This is intentional, as we expect this to be invoked once (or worst case, few times). + // After initialisation, the array is no longer required and can be reclaimed by the GC. + // This is likley to be overall more efficient for the common scenario as we don't keep + // an object alive for the lifetime of the application. + public static InstrumentationAssemblyInfo[] GetMetricsInstrumentationAssembliesInfo() => + [ + new() + { + Name = "AspNet", + Filename = "OpenTelemetry.Instrumentation.AspNet.dll", + FullyQualifiedType = "OpenTelemetry.Metrics.MeterProviderBuilderExtensions", + InstrumentationMethod = "AddAspNetInstrumentation" + }, + + new() + { + Name = "AspNetCore", + Filename = "OpenTelemetry.Instrumentation.AspNetCore.dll", + FullyQualifiedType = "OpenTelemetry.Metrics.AspNetCoreInstrumentationMeterProviderBuilderExtensions", + InstrumentationMethod = "AddAspNetCoreInstrumentation" + }, + + new() + { + Name = "AWS", + Filename = "OpenTelemetry.Instrumentation.AWS.dll", + FullyQualifiedType = "OpenTelemetry.Metrics.MeterProviderBuilderExtensions", + InstrumentationMethod = "AddAWSInstrumentation" + }, + + new() + { + Name = "Cassandra", + Filename = "OpenTelemetry.Instrumentation.Cassandra.dll", + FullyQualifiedType = "OpenTelemetry.Metrics.MeterProviderBuilderExtensions", + InstrumentationMethod = "AddCassandraInstrumentation" + }, + + new() + { + Name = "Kafka (Producer)", + Filename = "OpenTelemetry.Instrumentation.ConfluentKafka.dll", + FullyQualifiedType = "OpenTelemetry.Metrics.MeterProviderBuilderExtensions", + InstrumentationMethod = "AddKafkaProducerInstrumentation" + }, + + new() + { + Name = "Kafka (Consumer)", + Filename = "OpenTelemetry.Instrumentation.ConfluentKafka.dll", + FullyQualifiedType = "OpenTelemetry.Metrics.MeterProviderBuilderExtensions", + InstrumentationMethod = "AddKafkaConsumerInstrumentation" + }, + + new() + { + Name = "EventCounters", + Filename = "OpenTelemetry.Instrumentation.EventCounters.dll", + FullyQualifiedType = "OpenTelemetry.Metrics.MeterProviderBuilderExtensions", + InstrumentationMethod = "AddEventCountersInstrumentation" + }, + +#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 + + new() + { + Name = "Process", + Filename = "OpenTelemetry.Instrumentation.Process.dll", + FullyQualifiedType = "OpenTelemetry.Metrics.MeterProviderBuilderExtensions", + InstrumentationMethod = "AddProcessInstrumentation" + }, + ]; +} diff --git a/src/Elastic.OpenTelemetry/Instrumentation/ContribTraceInstrumentation.cs b/src/Elastic.OpenTelemetry/Instrumentation/ContribTraceInstrumentation.cs new file mode 100644 index 0000000..b6e9dae --- /dev/null +++ b/src/Elastic.OpenTelemetry/Instrumentation/ContribTraceInstrumentation.cs @@ -0,0 +1,151 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +namespace Elastic.OpenTelemetry.Instrumentation; + +internal static class ContribTraceInstrumentation +{ + // Note: This is defined as a static method and allocates the array each time. + // This is intentional, as we expect this to be invoked once (or worst case, few times). + // After initialisation, the array is no longer required and can be reclaimed by the GC. + // This is likley to be overall more efficient for the common scenario as we don't keep + // an object alive for the lifetime of the application. + public static InstrumentationAssemblyInfo[] GetReflectionInstrumentationAssemblies() => + [ + new() + { + Name = "AspNet", + Filename = "OpenTelemetry.Instrumentation.AspNet.dll", + FullyQualifiedType = "OpenTelemetry.Trace.TracerProviderBuilderExtensions", + InstrumentationMethod = "AddAspNetInstrumentation" + }, + + new() + { + Name = "AspNetCore", + Filename = "OpenTelemetry.Instrumentation.AspNetCore.dll", + FullyQualifiedType = "OpenTelemetry.Trace.AspNetCoreInstrumentationTracerProviderBuilderExtensions", + InstrumentationMethod = "AddAspNetCoreInstrumentation" + }, + + new() + { + Name = "AWS", + Filename = "OpenTelemetry.Instrumentation.AWS.dll", + FullyQualifiedType = "OpenTelemetry.Trace.TracerProviderBuilderExtensions", + InstrumentationMethod = "AddAWSInstrumentation" + }, + + new() + { + Name = "Kafka (Producer)", + Filename = "OpenTelemetry.Instrumentation.ConfluentKafka.dll", + FullyQualifiedType = "OpenTelemetry.Trace.TracerProviderBuilderExtensions", + InstrumentationMethod = "AddKafkaProducerInstrumentation" + }, + + new() + { + Name = "Kafka (Consumer)", + Filename = "OpenTelemetry.Instrumentation.ConfluentKafka.dll", + FullyQualifiedType = "OpenTelemetry.Trace.TracerProviderBuilderExtensions", + InstrumentationMethod = "AddKafkaConsumerInstrumentation" + }, + + new() + { + Name = "EntityFrameworkCore", + Filename = "OpenTelemetry.Instrumentation.EntityFrameworkCore.dll", + FullyQualifiedType = "OpenTelemetry.Trace.TracerProviderBuilderExtensions", + InstrumentationMethod = "AddEntityFrameworkCoreInstrumentation" + }, + + new() + { + Name = "GrpcCore", + Filename = "OpenTelemetry.Instrumentation.GrpcCore.dll", + FullyQualifiedType = "OpenTelemetry.Trace.TracerProviderBuilderExtensions", + InstrumentationMethod = "AddGrpcCoreInstrumentation" + }, + + new() + { + Name = "Hangfire", + Filename = "OpenTelemetry.Instrumentation.Hangfire.dll", + FullyQualifiedType = "OpenTelemetry.Trace.TracerProviderBuilderExtensions", + InstrumentationMethod = "AddHangfireInstrumentation" + }, + +#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, because we no longer take this dependency for .NET 9 targets. + new() + { + Name = "HTTP", + Filename = "OpenTelemetry.Instrumentation.Http.dll", + FullyQualifiedType = "OpenTelemetry.Trace.HttpClientInstrumentationTracerProviderBuilderExtensions", + InstrumentationMethod = "AddHttpClientInstrumentation" + }, +#endif + + new() + { + Name = "NEST", + Filename = "OpenTelemetry.Instrumentation.ElasticsearchClient.dll", + FullyQualifiedType = "OpenTelemetry.Trace.TracerProviderBuilderExtensions", + InstrumentationMethod = "AddElasticsearchClientInstrumentation" + }, + + new() + { + Name = "Owin", + Filename = "OpenTelemetry.Instrumentation.Owin.dll", + FullyQualifiedType = "OpenTelemetry.Trace.TracerProviderBuilderExtensions", + InstrumentationMethod = "AddOwinInstrumentation" + }, + + new() + { + Name = "Quartz", + Filename = "OpenTelemetry.Instrumentation.Quartz.dll", + FullyQualifiedType = "OpenTelemetry.Trace.TracerProviderBuilderExtensions", + InstrumentationMethod = "AddQuartzInstrumentation" + }, + + new() + { + Name = "ServiceFabricRemoting", + Filename = "OpenTelemetry.Instrumentation.ServiceFabricRemoting.dll", + FullyQualifiedType = "OpenTelemetry.Trace.TracerProviderBuilderExtensions", + InstrumentationMethod = "AddServiceFabricRemotingInstrumentation" + }, + + new() + { + Name = "SqlClient", + Filename = "OpenTelemetry.Instrumentation.SqlClient.dll", + FullyQualifiedType = "OpenTelemetry.Trace.TracerProviderBuilderExtensions", + InstrumentationMethod = "AddSqlClientInstrumentation" + }, + + new() + { + Name = "StackExchangeRedis", + Filename = "OpenTelemetry.Instrumentation.StackExchangeRedis.dll", + FullyQualifiedType = "OpenTelemetry.Trace.TracerProviderBuilderExtensions", + InstrumentationMethod = "AddRedisInstrumentation" + }, + + new() + { + Name = "WCF", + Filename = "OpenTelemetry.Instrumentation.Wcf.dll", + FullyQualifiedType = "OpenTelemetry.Trace.TracerProviderBuilderExtensions", + InstrumentationMethod = "AddWcfInstrumentation" + }, + ]; +} diff --git a/test-applications/Directory.Build.props b/test-applications/Directory.Build.props new file mode 100644 index 0000000..871656d --- /dev/null +++ b/test-applications/Directory.Build.props @@ -0,0 +1,8 @@ + + + + + true + $(SolutionRoot)\build\keys\keypair.snk + + \ No newline at end of file diff --git a/test-applications/WebApi/Program.cs b/test-applications/WebApi/Program.cs new file mode 100644 index 0000000..d138c31 --- /dev/null +++ b/test-applications/WebApi/Program.cs @@ -0,0 +1,20 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +var builder = WebApplication.CreateBuilder(args); + +var app = builder.Build(); + +app.MapGet("/", () => {}); + +app.MapGet("/http", static async ctx => +{ + var client = new HttpClient(); + using var response = await client.GetAsync("https://example.com"); + ctx.Response.StatusCode = 200; +}); + +app.Run(); + +public partial class Program { } diff --git a/test-applications/WebApi/Properties/launchSettings.json b/test-applications/WebApi/Properties/launchSettings.json new file mode 100644 index 0000000..49d01e6 --- /dev/null +++ b/test-applications/WebApi/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5134", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7208;http://localhost:5134", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/test-applications/WebApi/README.md b/test-applications/WebApi/README.md new file mode 100644 index 0000000..b02e98a --- /dev/null +++ b/test-applications/WebApi/README.md @@ -0,0 +1,4 @@ +# WebApi Test Application + +This is a .NET 9, minimal API project, used for in-memory integration testing via +the `WebApplicationBuilder` from `Microsoft.AspNetCore.Mvc.Testing`. \ No newline at end of file diff --git a/test-applications/WebApi/WebApi.csproj b/test-applications/WebApi/WebApi.csproj new file mode 100644 index 0000000..d04273b --- /dev/null +++ b/test-applications/WebApi/WebApi.csproj @@ -0,0 +1,18 @@ + + + + net9.0 + enable + enable + true + + + + + + + + + + + diff --git a/test-applications/WebApi/appsettings.json b/test-applications/WebApi/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/test-applications/WebApi/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/test-applications/WebApiDotNet8/WebApiDotNet8/Program.cs b/test-applications/WebApiDotNet8/WebApiDotNet8/Program.cs new file mode 100644 index 0000000..eda4df9 --- /dev/null +++ b/test-applications/WebApiDotNet8/WebApiDotNet8/Program.cs @@ -0,0 +1,26 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +namespace WebApiDotNet8; + +public class ProgramV8 +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + var app = builder.Build(); + + app.MapGet("/", () => { }); + + app.MapGet("/http", static async ctx => + { + var client = new HttpClient(); + using var response = await client.GetAsync("https://example.com"); + ctx.Response.StatusCode = 200; + }); + + app.Run(); + } +} diff --git a/test-applications/WebApiDotNet8/WebApiDotNet8/Properties/launchSettings.json b/test-applications/WebApiDotNet8/WebApiDotNet8/Properties/launchSettings.json new file mode 100644 index 0000000..dcda795 --- /dev/null +++ b/test-applications/WebApiDotNet8/WebApiDotNet8/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:18563", + "sslPort": 44316 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "weatherforecast", + "applicationUrl": "http://localhost:5226", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "weatherforecast", + "applicationUrl": "https://localhost:7156;http://localhost:5226", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "weatherforecast", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/test-applications/WebApiDotNet8/WebApiDotNet8/WebApiDotNet8.csproj b/test-applications/WebApiDotNet8/WebApiDotNet8/WebApiDotNet8.csproj new file mode 100644 index 0000000..a298c42 --- /dev/null +++ b/test-applications/WebApiDotNet8/WebApiDotNet8/WebApiDotNet8.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + enable + true + + + + + + + + + + + diff --git a/test-applications/WebApiDotNet8/WebApiDotNet8/WebApiDotNet8.http b/test-applications/WebApiDotNet8/WebApiDotNet8/WebApiDotNet8.http new file mode 100644 index 0000000..4f5e737 --- /dev/null +++ b/test-applications/WebApiDotNet8/WebApiDotNet8/WebApiDotNet8.http @@ -0,0 +1,6 @@ +@WebApiDotNet8_HostAddress = http://localhost:5226 + +GET {{WebApiDotNet8_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/test-applications/WebApiDotNet8/WebApiDotNet8/appsettings.json b/test-applications/WebApiDotNet8/WebApiDotNet8/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/test-applications/WebApiDotNet8/WebApiDotNet8/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/tests/Elastic.OpenTelemetry.Tests/Aot/NativeAotCompatibilityTests.cs b/tests/Elastic.OpenTelemetry.Tests/Aot/NativeAotCompatibilityTests.cs new file mode 100644 index 0000000..b1b14a8 --- /dev/null +++ b/tests/Elastic.OpenTelemetry.Tests/Aot/NativeAotCompatibilityTests.cs @@ -0,0 +1,63 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.Runtime.InteropServices; +using Xunit.Abstractions; + +namespace Elastic.OpenTelemetry.Tests.Aot +{ + public class NativeAotCompatibilityTests(ITestOutputHelper output) + { + private readonly ITestOutputHelper _output = output; + + [Fact] + public async Task CanPublishAotApp() + { + var workingDir = Environment.CurrentDirectory; + var indexOfSolutionFolder = workingDir.AsSpan().IndexOf("elastic-otel-dotnet"); + workingDir = workingDir.AsSpan().Slice(0, indexOfSolutionFolder + "elastic-otel-dotnet".Length).ToString(); + workingDir = Path.Combine(workingDir, "examples", "Example.AspNetCore.WebApiAot"); + + var rid = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "win-x64" : "linux-x64"; + + var dotnetPublishProcess = new Process + { + StartInfo = + { + FileName = "dotnet", + Arguments = $"publish -c Release -r {rid}", + WorkingDirectory = workingDir, + RedirectStandardError = true, + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true + } + }; + + dotnetPublishProcess.ErrorDataReceived += (sender, args) => + { + if (!string.IsNullOrEmpty(args.Data)) + { + _output.WriteLine("[ERROR] " + args.Data); + } + }; + + dotnetPublishProcess.OutputDataReceived += (sender, args) => + { + if (!string.IsNullOrEmpty(args.Data)) + { + _output.WriteLine(args.Data); + } + }; + + dotnetPublishProcess.Start(); + dotnetPublishProcess.BeginOutputReadLine(); + dotnetPublishProcess.BeginErrorReadLine(); + + await dotnetPublishProcess.WaitForExitAsync(); + + Assert.Equal(0, dotnetPublishProcess.ExitCode); + } + } +} diff --git a/tests/Elastic.OpenTelemetry.Tests/Configuration/CompositeElasticOpenTelemetryOptionsTests.cs b/tests/Elastic.OpenTelemetry.Tests/Configuration/CompositeElasticOpenTelemetryOptionsTests.cs index afaab09..0dca5d5 100644 --- a/tests/Elastic.OpenTelemetry.Tests/Configuration/CompositeElasticOpenTelemetryOptionsTests.cs +++ b/tests/Elastic.OpenTelemetry.Tests/Configuration/CompositeElasticOpenTelemetryOptionsTests.cs @@ -2,12 +2,289 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using System.Collections; +using System.Text; using Elastic.OpenTelemetry.Configuration; +using Xunit.Abstractions; +using System.Diagnostics.Tracing; +using static Elastic.OpenTelemetry.Configuration.EnvironmentVariables; +using static Elastic.OpenTelemetry.Diagnostics.LogLevelHelpers; namespace Elastic.OpenTelemetry.Tests.Configuration; -public class CompositeElasticOpenTelemetryOptionsTests +public class CompositeElasticOpenTelemetryOptionsTests(ITestOutputHelper output) { + private const int ExpectedLogsLength = 7; + + [Fact] + public void DefaultCtor_SetsExpectedDefaults_WhenNoEnvironmentVariablesAreConfigured() + { + var sut = new CompositeElasticOpenTelemetryOptions(new Hashtable + { + { OTEL_DOTNET_AUTO_LOG_DIRECTORY, null }, + { OTEL_LOG_LEVEL, null }, + { ELASTIC_OTEL_SKIP_OTLP_EXPORTER, null }, + { ELASTIC_OTEL_SKIP_ASSEMBLY_SCANNING, null }, + }); + + Assert.False(sut.GlobalLogEnabled); + + // these default to null because any other value would enable file logging + Assert.Equal(sut.LogDirectoryDefault, sut.LogDirectory); + Assert.Equal(LogLevel.Warning, sut.LogLevel); + + Assert.False(sut.SkipOtlpExporter); + Assert.False(sut.SkipInstrumentationAssemblyScanning); + + var logger = new TestLogger(output); + + sut.LogConfigSources(logger); + + Assert.Equal(ExpectedLogsLength, logger.Messages.Count); + + foreach (var message in logger.Messages) + Assert.EndsWith("from [Default]", message); + } + + [Fact] + public void DefaultCtor_LoadsConfigurationFromEnvironmentVariables() + { + const string fileLogDirectory = "C:\\Temp"; + const string fileLogLevel = "Critical"; + + var sut = new CompositeElasticOpenTelemetryOptions(new Hashtable + { + { OTEL_DOTNET_AUTO_LOG_DIRECTORY, fileLogDirectory }, + { OTEL_LOG_LEVEL, fileLogLevel }, + { ELASTIC_OTEL_SKIP_OTLP_EXPORTER, true }, + { ELASTIC_OTEL_SKIP_ASSEMBLY_SCANNING, true }, + }); + + Assert.Equal(fileLogDirectory, sut.LogDirectory); + Assert.Equal(ToLogLevel(fileLogLevel), sut.LogLevel); + Assert.True(sut.SkipOtlpExporter); + Assert.True(sut.SkipInstrumentationAssemblyScanning); + + var logger = new TestLogger(output); + + sut.LogConfigSources(logger); + + Assert.Contains(logger.Messages, s => s.EndsWith("from [Environment]")); + Assert.Contains(logger.Messages, s => s.EndsWith("from [Default]")); + Assert.DoesNotContain(logger.Messages, s => s.EndsWith("from [IConfiguration]")); + } + + [Fact] + public void ConfigurationCtor_LoadsConfigurationFromIConfiguration() + { + const string loggingSectionLogLevel = "Warning"; + const string fileLogLevel = "Critical"; + const string enabledElasticDefaults = "None"; + + var json = $$""" + { + "Logging": { + "LogLevel": { + "Default": "Information", + "Elastic.OpenTelemetry": "{{loggingSectionLogLevel}}" + } + }, + "Elastic": { + "OpenTelemetry": { + "LogDirectory": "C:\\Temp", + "LogLevel": "{{fileLogLevel}}", + "ElasticDefaults": "{{enabledElasticDefaults}}", + "SkipOtlpExporter": true, + "SkipInstrumentationAssemblyScanning": true + } + } + } + """; + + var config = new ConfigurationBuilder() + .AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json))) + .Build(); + + var sut = new CompositeElasticOpenTelemetryOptions(config, new Hashtable()); + + Assert.Equal(@"C:\Temp", sut.LogDirectory); + Assert.Equal(ToLogLevel(fileLogLevel), sut.LogLevel); + Assert.True(sut.SkipOtlpExporter); + Assert.True(sut.SkipInstrumentationAssemblyScanning); + Assert.Equal(EventLevel.Warning, sut.EventLogLevel); + Assert.Equal(LogLevel.Critical, sut.LogLevel); + + var logger = new TestLogger(output); + + sut.LogConfigSources(logger); + + Assert.Equal(ExpectedLogsLength, logger.Messages.Count); + + Assert.Contains(logger.Messages, s => s.EndsWith("from [IConfiguration]")); + Assert.Contains(logger.Messages, s => s.EndsWith("from [Default]")); + Assert.DoesNotContain(logger.Messages, s => s.EndsWith("from [Environment]")); + } + + [Fact] + public void ConfigurationCtor_LoadsConfigurationFromIConfiguration_AndFallsBackToLoggingSection_WhenAvailable() + { + const string loggingSectionLogLevel = "Warning"; + const string enabledElasticDefaults = "None"; + + var json = $$""" + { + "Logging": { + "LogLevel": { + "Default": "Information", + "Elastic.OpenTelemetry": "{{loggingSectionLogLevel}}" + } + }, + "Elastic": { + "OpenTelemetry": { + "LogDirectory": "C:\\Temp", + "ElasticDefaults": "{{enabledElasticDefaults}}", + "SkipOtlpExporter": true + } + } + } + """; + + var config = new ConfigurationBuilder() + .AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json))) + .Build(); + + var sut = new CompositeElasticOpenTelemetryOptions(config, new Hashtable()); + + Assert.Equal(@"C:\Temp", sut.LogDirectory); + Assert.Equal(ToLogLevel(loggingSectionLogLevel), sut.LogLevel); + Assert.True(sut.SkipOtlpExporter); + Assert.Equal(EventLevel.Warning, sut.EventLogLevel); + Assert.Equal(LogLevel.Warning, sut.LogLevel); + + var logger = new TestLogger(output); + + sut.LogConfigSources(logger); + + Assert.Contains(logger.Messages, s => s.EndsWith("from [IConfiguration]")); + Assert.Contains(logger.Messages, s => s.EndsWith("from [Default]")); + Assert.DoesNotContain(logger.Messages, s => s.EndsWith("from [Environment]")); + } + + [Fact] + public void ConfigurationCtor_LoadsConfigurationFromIConfiguration_AndFallsBackToLoggingSectionDefault_WhenAvailable() + { + const string loggingSectionDefaultLogLevel = "Information"; + const string enabledElasticDefaults = "None"; + + var json = $$""" + { + "Logging": { + "LogLevel": { + "Default": "{{loggingSectionDefaultLogLevel}}" + } + }, + "Elastic": { + "OpenTelemetry": { + "LogDirectory": "C:\\Temp", + "ElasticDefaults": "{{enabledElasticDefaults}}", + "SkipOtlpExporter": true + } + } + } + """; + + var config = new ConfigurationBuilder() + .AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json))) + .Build(); + + var sut = new CompositeElasticOpenTelemetryOptions(config, new Hashtable()); + + Assert.Equal(@"C:\Temp", sut.LogDirectory); + Assert.Equal(ToLogLevel(loggingSectionDefaultLogLevel), sut.LogLevel); + Assert.True(sut.SkipOtlpExporter); + Assert.Equal(EventLevel.Informational, sut.EventLogLevel); + + var logger = new TestLogger(output); + + sut.LogConfigSources(logger); + + Assert.Contains(logger.Messages, s => s.EndsWith("from [IConfiguration]")); + Assert.Contains(logger.Messages, s => s.EndsWith("from [Default]")); + Assert.DoesNotContain(logger.Messages, s => s.EndsWith("from [Environment]")); + } + + [Fact] + public void EnvironmentVariables_TakePrecedenceOver_ConfigValues() + { + const string fileLogDirectory = "C:\\Temp"; + const string fileLogLevel = "Critical"; + + var json = $$""" + { + "Elastic": { + "OpenTelemetry": { + "LogDirectory": "C:\\Json", + "LogLevel": "Trace", + "ElasticDefaults": "All", + "SkipOtlpExporter": false, + "SkipInstrumentationAssemblyScanning": false + } + } + } + """; + + var config = new ConfigurationBuilder() + .AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json))) + .Build(); + + var sut = new CompositeElasticOpenTelemetryOptions(config, new Hashtable + { + { OTEL_DOTNET_AUTO_LOG_DIRECTORY, fileLogDirectory }, + { OTEL_LOG_LEVEL, fileLogLevel }, + { ELASTIC_OTEL_SKIP_OTLP_EXPORTER, true }, + { ELASTIC_OTEL_SKIP_ASSEMBLY_SCANNING, true } + }); + + Assert.Equal(fileLogDirectory, sut.LogDirectory); + Assert.Equal(ToLogLevel(fileLogLevel), sut.LogLevel); + Assert.True(sut.SkipOtlpExporter); + Assert.True(sut.SkipInstrumentationAssemblyScanning); + } + + [Fact] + public void InitializedProperties_TakePrecedenceOver_EnvironmentValues() + { + const string fileLogDirectory = "C:\\Property"; + const string fileLogLevel = "Critical"; + + var sut = new CompositeElasticOpenTelemetryOptions(new Hashtable + { + { OTEL_DOTNET_AUTO_LOG_DIRECTORY, "C:\\Temp" }, + { OTEL_LOG_LEVEL, "Information" }, + { ELASTIC_OTEL_SKIP_OTLP_EXPORTER, true }, + { ELASTIC_OTEL_SKIP_ASSEMBLY_SCANNING, true } + }) + { + LogDirectory = fileLogDirectory, + LogLevel = ToLogLevel(fileLogLevel) ?? LogLevel.None, + SkipOtlpExporter = false, + SkipInstrumentationAssemblyScanning = false, + }; + + Assert.Equal(fileLogDirectory, sut.LogDirectory); + Assert.Equal(ToLogLevel(fileLogLevel), sut.LogLevel); + Assert.False(sut.SkipOtlpExporter); + Assert.False(sut.SkipInstrumentationAssemblyScanning); + + var logger = new TestLogger(output); + + sut.LogConfigSources(logger); + + Assert.Contains(logger.Messages, s => s.EndsWith("from [Property]")); + Assert.Contains(logger.Messages, s => s.EndsWith("from [Default]")); + Assert.DoesNotContain(logger.Messages, s => s.EndsWith("from [Environment]")); + } + [Fact] public void TwoInstancesAreEqual_WhenAllValuesMatch() { diff --git a/tests/Elastic.OpenTelemetry.Tests/Configuration/ElasticOpenTelemetryOptionsTests.cs b/tests/Elastic.OpenTelemetry.Tests/Configuration/ElasticOpenTelemetryOptionsTests.cs deleted file mode 100644 index ddc3948..0000000 --- a/tests/Elastic.OpenTelemetry.Tests/Configuration/ElasticOpenTelemetryOptionsTests.cs +++ /dev/null @@ -1,279 +0,0 @@ -// Licensed to Elasticsearch B.V under one or more agreements. -// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information - -using System.Collections; -using System.Diagnostics.Tracing; -using System.Text; -using Elastic.OpenTelemetry.Configuration; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Xunit.Abstractions; - -using static Elastic.OpenTelemetry.Configuration.EnvironmentVariables; -using static Elastic.OpenTelemetry.Diagnostics.LogLevelHelpers; - -namespace Elastic.OpenTelemetry.Tests.Configuration; - -public sealed class ElasticOpenTelemetryOptionsTests(ITestOutputHelper output) -{ - private const int ExpectedLogsLength = 7; - - [Fact] - public void DefaultCtor_SetsExpectedDefaults_WhenNoEnvironmentVariablesAreConfigured() - { - var sut = new CompositeElasticOpenTelemetryOptions(new Hashtable - { - { OTEL_DOTNET_AUTO_LOG_DIRECTORY, null }, - { OTEL_LOG_LEVEL, null }, - { ELASTIC_OTEL_SKIP_OTLP_EXPORTER, null }, - }); - - Assert.False(sut.GlobalLogEnabled); - - // these default to null because any other value would enable file logging - Assert.Equal(sut.LogDirectoryDefault, sut.LogDirectory); - Assert.Equal(LogLevel.Warning, sut.LogLevel); - - Assert.False(sut.SkipOtlpExporter); - - var logger = new TestLogger(output); - - sut.LogConfigSources(logger); - - Assert.Equal(ExpectedLogsLength, logger.Messages.Count); - - foreach (var message in logger.Messages) - Assert.EndsWith("from [Default]", message); - } - - [Fact] - public void DefaultCtor_LoadsConfigurationFromEnvironmentVariables() - { - const string fileLogDirectory = "C:\\Temp"; - const string fileLogLevel = "Critical"; - - var sut = new CompositeElasticOpenTelemetryOptions(new Hashtable - { - { OTEL_DOTNET_AUTO_LOG_DIRECTORY, fileLogDirectory }, - { OTEL_LOG_LEVEL, fileLogLevel }, - { ELASTIC_OTEL_SKIP_OTLP_EXPORTER, "true" }, - }); - - Assert.Equal(fileLogDirectory, sut.LogDirectory); - Assert.Equal(ToLogLevel(fileLogLevel), sut.LogLevel); - Assert.True(sut.SkipOtlpExporter); - - var logger = new TestLogger(output); - - sut.LogConfigSources(logger); - - Assert.Contains(logger.Messages, s => s.EndsWith("from [Environment]")); - Assert.Contains(logger.Messages, s => s.EndsWith("from [Default]")); - Assert.DoesNotContain(logger.Messages, s => s.EndsWith("from [IConfiguration]")); - } - - [Fact] - public void ConfigurationCtor_LoadsConfigurationFromIConfiguration() - { - const string loggingSectionLogLevel = "Warning"; - const string fileLogLevel = "Critical"; - const string enabledElasticDefaults = "None"; - - var json = $$""" - { - "Logging": { - "LogLevel": { - "Default": "Information", - "Elastic.OpenTelemetry": "{{loggingSectionLogLevel}}" - } - }, - "Elastic": { - "OpenTelemetry": { - "LogDirectory": "C:\\Temp", - "LogLevel": "{{fileLogLevel}}", - "ElasticDefaults": "{{enabledElasticDefaults}}", - "SkipOtlpExporter": true - } - } - } - """; - - var config = new ConfigurationBuilder() - .AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json))) - .Build(); - - var sut = new CompositeElasticOpenTelemetryOptions(config, new Hashtable()); - - Assert.Equal(@"C:\Temp", sut.LogDirectory); - Assert.Equal(ToLogLevel(fileLogLevel), sut.LogLevel); - Assert.True(sut.SkipOtlpExporter); - Assert.Equal(EventLevel.Warning, sut.EventLogLevel); - Assert.Equal(LogLevel.Critical, sut.LogLevel); - - var logger = new TestLogger(output); - - sut.LogConfigSources(logger); - - Assert.Equal(ExpectedLogsLength, logger.Messages.Count); - - Assert.Contains(logger.Messages, s => s.EndsWith("from [IConfiguration]")); - Assert.Contains(logger.Messages, s => s.EndsWith("from [Default]")); - Assert.DoesNotContain(logger.Messages, s => s.EndsWith("from [Environment]")); - } - - [Fact] - public void ConfigurationCtor_LoadsConfigurationFromIConfiguration_AndFallsBackToLoggingSection_WhenAvailable() - { - const string loggingSectionLogLevel = "Warning"; - const string enabledElasticDefaults = "None"; - - var json = $$""" - { - "Logging": { - "LogLevel": { - "Default": "Information", - "Elastic.OpenTelemetry": "{{loggingSectionLogLevel}}" - } - }, - "Elastic": { - "OpenTelemetry": { - "LogDirectory": "C:\\Temp", - "ElasticDefaults": "{{enabledElasticDefaults}}", - "SkipOtlpExporter": true - } - } - } - """; - - var config = new ConfigurationBuilder() - .AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json))) - .Build(); - - var sut = new CompositeElasticOpenTelemetryOptions(config, new Hashtable()); - - Assert.Equal(@"C:\Temp", sut.LogDirectory); - Assert.Equal(ToLogLevel(loggingSectionLogLevel), sut.LogLevel); - Assert.True(sut.SkipOtlpExporter); - Assert.Equal(EventLevel.Warning, sut.EventLogLevel); - Assert.Equal(LogLevel.Warning, sut.LogLevel); - - var logger = new TestLogger(output); - - sut.LogConfigSources(logger); - - Assert.Contains(logger.Messages, s => s.EndsWith("from [IConfiguration]")); - Assert.Contains(logger.Messages, s => s.EndsWith("from [Default]")); - Assert.DoesNotContain(logger.Messages, s => s.EndsWith("from [Environment]")); - } - - [Fact] - public void ConfigurationCtor_LoadsConfigurationFromIConfiguration_AndFallsBackToLoggingSectionDefault_WhenAvailable() - { - const string loggingSectionDefaultLogLevel = "Information"; - const string enabledElasticDefaults = "None"; - - var json = $$""" - { - "Logging": { - "LogLevel": { - "Default": "{{loggingSectionDefaultLogLevel}}" - } - }, - "Elastic": { - "OpenTelemetry": { - "LogDirectory": "C:\\Temp", - "ElasticDefaults": "{{enabledElasticDefaults}}", - "SkipOtlpExporter": true - } - } - } - """; - - var config = new ConfigurationBuilder() - .AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json))) - .Build(); - - var sut = new CompositeElasticOpenTelemetryOptions(config, new Hashtable()); - - Assert.Equal(@"C:\Temp", sut.LogDirectory); - Assert.Equal(ToLogLevel(loggingSectionDefaultLogLevel), sut.LogLevel); - Assert.True(sut.SkipOtlpExporter); - Assert.Equal(EventLevel.Informational, sut.EventLogLevel); - - var logger = new TestLogger(output); - - sut.LogConfigSources(logger); - - Assert.Contains(logger.Messages, s => s.EndsWith("from [IConfiguration]")); - Assert.Contains(logger.Messages, s => s.EndsWith("from [Default]")); - Assert.DoesNotContain(logger.Messages, s => s.EndsWith("from [Environment]")); - } - - [Fact] - public void EnvironmentVariables_TakePrecedenceOver_ConfigValues() - { - const string fileLogDirectory = "C:\\Temp"; - const string fileLogLevel = "Critical"; - - var json = $$""" - { - "Elastic": { - "OpenTelemetry": { - "LogDirectory": "C:\\Json", - "LogLevel": "Trace", - "ElasticDefaults": "All", - "SkipOtlpExporter": false - } - } - } - """; - - var config = new ConfigurationBuilder() - .AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json))) - .Build(); - - var sut = new CompositeElasticOpenTelemetryOptions(config, new Hashtable - { - { OTEL_DOTNET_AUTO_LOG_DIRECTORY, fileLogDirectory }, - { OTEL_LOG_LEVEL, fileLogLevel }, - { ELASTIC_OTEL_SKIP_OTLP_EXPORTER, "true" }, - }); - - Assert.Equal(fileLogDirectory, sut.LogDirectory); - Assert.Equal(ToLogLevel(fileLogLevel), sut.LogLevel); - Assert.True(sut.SkipOtlpExporter); - - } - - [Fact] - public void InitializedProperties_TakePrecedenceOver_EnvironmentValues() - { - const string fileLogDirectory = "C:\\Property"; - const string fileLogLevel = "Critical"; - - var sut = new CompositeElasticOpenTelemetryOptions(new Hashtable - { - { OTEL_DOTNET_AUTO_LOG_DIRECTORY, "C:\\Temp" }, - { OTEL_LOG_LEVEL, "Information" }, - { ELASTIC_OTEL_SKIP_OTLP_EXPORTER, "true" }, - }) - { - LogDirectory = fileLogDirectory, - LogLevel = ToLogLevel(fileLogLevel) ?? LogLevel.None, - SkipOtlpExporter = false, - }; - - Assert.Equal(fileLogDirectory, sut.LogDirectory); - Assert.Equal(ToLogLevel(fileLogLevel), sut.LogLevel); - Assert.False(sut.SkipOtlpExporter); - - var logger = new TestLogger(output); - - sut.LogConfigSources(logger); - - Assert.Contains(logger.Messages, s => s.EndsWith("from [Property]")); - Assert.Contains(logger.Messages, s => s.EndsWith("from [Default]")); - Assert.DoesNotContain(logger.Messages, s => s.EndsWith("from [Environment]")); - } -} diff --git a/tests/Elastic.OpenTelemetry.Tests/Elastic.OpenTelemetry.Tests.csproj b/tests/Elastic.OpenTelemetry.Tests/Elastic.OpenTelemetry.Tests.csproj index 9ba512f..28ac2e7 100644 --- a/tests/Elastic.OpenTelemetry.Tests/Elastic.OpenTelemetry.Tests.csproj +++ b/tests/Elastic.OpenTelemetry.Tests/Elastic.OpenTelemetry.Tests.csproj @@ -1,7 +1,7 @@ - + - net9.0 + net8.0;net9.0 enable enable false @@ -10,12 +10,16 @@ - + + + + + diff --git a/tests/Elastic.OpenTelemetry.Tests/InstrumentationScanningTests.cs b/tests/Elastic.OpenTelemetry.Tests/InstrumentationScanningTests.cs new file mode 100644 index 0000000..f888103 --- /dev/null +++ b/tests/Elastic.OpenTelemetry.Tests/InstrumentationScanningTests.cs @@ -0,0 +1,113 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.Text.RegularExpressions; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.AspNetCore.TestHost; +using OpenTelemetry; +using Xunit.Abstractions; + +namespace Elastic.OpenTelemetry.Tests; + +#if NET8_0 +public partial class InstrumentationScanningTests(WebApplicationFactory factory, ITestOutputHelper output) + : IClassFixture> +{ + private readonly WebApplicationFactory _factory = factory; +#else +public partial class InstrumentationScanningTests(WebApplicationFactory factory, ITestOutputHelper output) + : IClassFixture> +{ + private readonly WebApplicationFactory _factory = factory; +#endif + private readonly ITestOutputHelper _output = output; + +#if NET8_0 + [GeneratedRegex(@"^\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\]\[\d{5}\]\[-*\]\[Debug\]\s+Added HTTP \(via contrib instrumentation\) to TracerProviderBuilder.*")] + private static partial Regex HttpTracerProviderBuilderRegex(); + + [GeneratedRegex(@"^\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\]\[\d{5}\]\[-*\]\[Debug\]\s+Added HTTP \(via contrib instrumentation\) to MeterProviderBuilder.*")] + private static partial Regex HttpMeterProviderBuilderRegex(); +#elif NET9_0 + [GeneratedRegex(@"^\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\]\[\d{5}\]\[-*\]\[Debug\]\s+Added HTTP \(via native instrumentation\) to TracerProviderBuilder.*")] + private static partial Regex HttpTracerProviderBuilderRegex(); + + [GeneratedRegex(@"^\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\]\[\d{5}\]\[-*\]\[Debug\]\s+Added HTTP \(via native instrumentation\) to MeterProviderBuilder.*")] + private static partial Regex HttpMeterProviderBuilderRegex(); +#endif + + [Fact] + public async Task InstrumentationAssemblyScanning_AddsAspNetCoreInstrumentation() + { + var exportedItems = new List(); + var logger = new TestLogger(_output); + var options = new ElasticOpenTelemetryOptions() + { + SkipOtlpExporter = true, + AdditionalLogger = logger + }; + + using var factory = _factory.WithWebHostBuilder(builder => builder + .ConfigureTestServices(services => services + .AddElasticOpenTelemetry(options) + .WithTracing(tpb => tpb.AddInMemoryExporter(exportedItems)))); + + using var client = factory.CreateClient(); + + using var response = await client.GetAsync("/"); + + response.EnsureSuccessStatusCode(); + + Assert.Equal("GET /", Assert.Single(exportedItems).DisplayName); + } + + [Fact] + public async Task InstrumentationAssemblyScanning_AddsHttpInstrumentation() + { + // NOTE: When this runs on NET8, we expect the contrib library to be used. + // On NET9, the library dependency is not included with Elastic.OpenTelemetry, + // so we expect the native instrumentation to be used. + + var exportedItems = new List(); + var logger = new TestLogger(_output); + var options = new ElasticOpenTelemetryOptions() + { + SkipOtlpExporter = true, + AdditionalLogger = logger + }; + + using var factory = _factory.WithWebHostBuilder(builder => builder + .ConfigureTestServices(services => services + .AddElasticOpenTelemetry(options) + .WithTracing(tpb => tpb.AddInMemoryExporter(exportedItems)))); + + using var client = factory.CreateClient(); + + using var response = await client.GetAsync("/http"); + + response.EnsureSuccessStatusCode(); + + Assert.Equal(2, exportedItems.Count); // One for ASP.NET Core and one for HTTP + + var activity = Assert.Single(exportedItems, a => a.DisplayName.Equals("GET", StringComparison.Ordinal)); + + var urlFull = Assert.Single(activity.TagObjects, a => a.Key.Equals("url.full", StringComparison.Ordinal)); + Assert.Equal("https://example.com/", (string?)urlFull.Value); + + var foundExpectedHttpTracerInstrumentationMessage = false; + var foundExpectedHttpMeterInstrumentationMessage = false; + + foreach (var message in logger.Messages) + { + if (HttpTracerProviderBuilderRegex().IsMatch(message)) + foundExpectedHttpTracerInstrumentationMessage = true; + + if (HttpMeterProviderBuilderRegex().IsMatch(message)) + foundExpectedHttpMeterInstrumentationMessage = true; + } + + Assert.True(foundExpectedHttpTracerInstrumentationMessage); + Assert.True(foundExpectedHttpMeterInstrumentationMessage); + } +} diff --git a/tests/Elastic.OpenTelemetry.Tests/Properties/launchSettings.json b/tests/Elastic.OpenTelemetry.Tests/Properties/launchSettings.json new file mode 100644 index 0000000..9d04ce3 --- /dev/null +++ b/tests/Elastic.OpenTelemetry.Tests/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "Elastic.OpenTelemetry.Tests": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:54104;http://localhost:54105" + } + } +} \ No newline at end of file diff --git a/tests/Elastic.OpenTelemetry.Tests/ServiceCollectionTests.cs b/tests/Elastic.OpenTelemetry.Tests/ServiceCollectionTests.cs index 4d074c2..8ce957d 100644 --- a/tests/Elastic.OpenTelemetry.Tests/ServiceCollectionTests.cs +++ b/tests/Elastic.OpenTelemetry.Tests/ServiceCollectionTests.cs @@ -2,11 +2,8 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information -using Elastic.OpenTelemetry.Configuration; using Elastic.OpenTelemetry.Core; using Elastic.OpenTelemetry.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using OpenTelemetry; using Xunit.Abstractions; @@ -14,6 +11,8 @@ namespace Elastic.OpenTelemetry.Tests; public class ServiceCollectionTests(ITestOutputHelper output) { + private readonly ITestOutputHelper _output = output; + [Fact] public async Task ServiceCollection_AddOpenTelemetry_IsSafeToCallMultipleTimes() { @@ -28,7 +27,7 @@ public async Task ServiceCollection_AddOpenTelemetry_IsSafeToCallMultipleTimes() var options = new ElasticOpenTelemetryOptions() { SkipOtlpExporter = true, - AdditionalLogger = new TestLogger(output) + AdditionalLogger = new TestLogger(_output) }; s.AddElasticOpenTelemetry(options) @@ -69,7 +68,7 @@ public async Task ServiceCollection_AddElasticOpenTelemetry_IsSafeToCallMultiple var options = new ElasticOpenTelemetryOptions() { SkipOtlpExporter = true, - AdditionalLogger = new TestLogger(output) + AdditionalLogger = new TestLogger(_output) }; s.AddElasticOpenTelemetry(options) @@ -97,7 +96,7 @@ public async Task ServiceCollection_AddElasticOpenTelemetry_IsSafeToCallMultiple } [Fact] - public void ServiceCollectionAddElasticOpenTelemetry_ReturnsSameComponents_WhenCalledMultipleTimes() + public void AddElasticOpenTelemetry_ReturnsSameComponents_WhenCalledMultipleTimes() { // Ensure that when AddElasticOpenTelemetry is called multiple times on the same IServiceCollection, // a single instance of the components is registered, as we expect those to be cached per IServiceCollection. From 4921766ff421d75aeb82fde5071beb6712ed3b18 Mon Sep 17 00:00:00 2001 From: Steve Gordon Date: Tue, 25 Feb 2025 11:42:35 +0000 Subject: [PATCH 04/12] Remove UTF-8 BOM encoding and fix some formatting --- .dockerignore | 2 +- Elastic.OpenTelemetry.sln | 2 +- Elastic.OpenTelemetry.sln.DotSettings | 2 +- examples/AppHost/README.md | 1 - examples/Example.AspNetCore.Mvc/Dockerfile | 2 +- examples/Example.AspNetCore.Mvc/Views/E2E/Index.cshtml | 2 +- examples/Example.AspNetCore.Mvc/Views/Home/Index.cshtml | 2 +- examples/Example.AspNetCore.Mvc/Views/Home/Privacy.cshtml | 2 +- examples/Example.AspNetCore.Mvc/Views/Shared/Error.cshtml | 2 +- examples/Example.AspNetCore.Mvc/Views/Shared/_Layout.cshtml | 2 +- .../Example.AspNetCore.Mvc/Views/Shared/_Layout.cshtml.css | 2 +- .../Views/Shared/_ValidationScriptsPartial.cshtml | 2 +- examples/Example.AspNetCore.Mvc/Views/_ViewStart.cshtml | 2 +- examples/Example.AspNetCore.Mvc/wwwroot/js/site.js | 2 +- .../Example.AspNetCore.WebApiAot.csproj | 2 +- examples/Example.AspNetCore.WebApiAot/Program.cs | 4 ++++ .../Example.AutoInstrumentation.csproj | 2 +- examples/Example.MinimalApi/Program.cs | 4 ++-- examples/ServiceDefaults/Extensions.cs | 2 ++ examples/ServiceDefaults/ServiceDefaults.csproj | 2 +- nuget.config | 2 +- src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj | 2 +- .../Extensions/LoggerFactoryExtensions.cs | 2 ++ .../Extensions/ResourceBuilderExtensions.cs | 2 ++ test-applications/WebApi/Properties/launchSettings.json | 2 +- test-applications/WebApi/WebApi.csproj | 2 +- .../WebApiDotNet8/Properties/launchSettings.json | 2 +- .../WebApiDotNet8/WebApiDotNet8/WebApiDotNet8.csproj | 2 +- .../AutoInstrumentation.IntegrationTests.csproj | 2 +- .../Elastic.OpenTelemetry.EndToEndTests.csproj | 2 +- .../Elastic.OpenTelemetry.Tests.csproj | 2 +- 31 files changed, 37 insertions(+), 28 deletions(-) diff --git a/.dockerignore b/.dockerignore index fbdc7ec..b1414a8 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,4 @@ -**/.dockerignore +**/.dockerignore **/.env **/.gitignore **/.project diff --git a/Elastic.OpenTelemetry.sln b/Elastic.OpenTelemetry.sln index d10cafe..427dc20 100644 --- a/Elastic.OpenTelemetry.sln +++ b/Elastic.OpenTelemetry.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.9.34321.82 diff --git a/Elastic.OpenTelemetry.sln.DotSettings b/Elastic.OpenTelemetry.sln.DotSettings index 1a8f956..6c7272c 100644 --- a/Elastic.OpenTelemetry.sln.DotSettings +++ b/Elastic.OpenTelemetry.sln.DotSettings @@ -1,4 +1,4 @@ - + HINT <?xml version="1.0" encoding="utf-16"?><Profile name="Quick Reformat"><FSReformatCode>True</FSReformatCode><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSReformatCode>True</CSReformatCode><IDEA_SETTINGS>&lt;profile version="1.0"&gt; &lt;option name="myName" value="Quick Reformat" /&gt; diff --git a/examples/AppHost/README.md b/examples/AppHost/README.md index 5f28270..e69de29 100644 --- a/examples/AppHost/README.md +++ b/examples/AppHost/README.md @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/Example.AspNetCore.Mvc/Dockerfile b/examples/Example.AspNetCore.Mvc/Dockerfile index ddf5378..6452976 100644 --- a/examples/Example.AspNetCore.Mvc/Dockerfile +++ b/examples/Example.AspNetCore.Mvc/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base USER $APP_UID WORKDIR /app EXPOSE 8080 diff --git a/examples/Example.AspNetCore.Mvc/Views/E2E/Index.cshtml b/examples/Example.AspNetCore.Mvc/Views/E2E/Index.cshtml index bcfd79a..19c8d3f 100644 --- a/examples/Example.AspNetCore.Mvc/Views/E2E/Index.cshtml +++ b/examples/Example.AspNetCore.Mvc/Views/E2E/Index.cshtml @@ -1,4 +1,4 @@ -@{ +@{ ViewData["Title"] = "Home Page"; } diff --git a/examples/Example.AspNetCore.Mvc/Views/Home/Index.cshtml b/examples/Example.AspNetCore.Mvc/Views/Home/Index.cshtml index bcfd79a..19c8d3f 100644 --- a/examples/Example.AspNetCore.Mvc/Views/Home/Index.cshtml +++ b/examples/Example.AspNetCore.Mvc/Views/Home/Index.cshtml @@ -1,4 +1,4 @@ -@{ +@{ ViewData["Title"] = "Home Page"; } diff --git a/examples/Example.AspNetCore.Mvc/Views/Home/Privacy.cshtml b/examples/Example.AspNetCore.Mvc/Views/Home/Privacy.cshtml index af4fb19..d01477f 100644 --- a/examples/Example.AspNetCore.Mvc/Views/Home/Privacy.cshtml +++ b/examples/Example.AspNetCore.Mvc/Views/Home/Privacy.cshtml @@ -1,4 +1,4 @@ -@{ +@{ ViewData["Title"] = "Privacy Policy"; }

@ViewData["Title"]

diff --git a/examples/Example.AspNetCore.Mvc/Views/Shared/Error.cshtml b/examples/Example.AspNetCore.Mvc/Views/Shared/Error.cshtml index a1e0478..0c47bb0 100644 --- a/examples/Example.AspNetCore.Mvc/Views/Shared/Error.cshtml +++ b/examples/Example.AspNetCore.Mvc/Views/Shared/Error.cshtml @@ -1,4 +1,4 @@ -@model ErrorViewModel +@model ErrorViewModel @{ ViewData["Title"] = "Error"; } diff --git a/examples/Example.AspNetCore.Mvc/Views/Shared/_Layout.cshtml b/examples/Example.AspNetCore.Mvc/Views/Shared/_Layout.cshtml index 28051ce..996643e 100644 --- a/examples/Example.AspNetCore.Mvc/Views/Shared/_Layout.cshtml +++ b/examples/Example.AspNetCore.Mvc/Views/Shared/_Layout.cshtml @@ -1,4 +1,4 @@ - + diff --git a/examples/Example.AspNetCore.Mvc/Views/Shared/_Layout.cshtml.css b/examples/Example.AspNetCore.Mvc/Views/Shared/_Layout.cshtml.css index c187c02..104c44c 100644 --- a/examples/Example.AspNetCore.Mvc/Views/Shared/_Layout.cshtml.css +++ b/examples/Example.AspNetCore.Mvc/Views/Shared/_Layout.cshtml.css @@ -1,4 +1,4 @@ -/* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification +/* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification for details on configuring this project to bundle and minify static web assets. */ a.navbar-brand { diff --git a/examples/Example.AspNetCore.Mvc/Views/Shared/_ValidationScriptsPartial.cshtml b/examples/Example.AspNetCore.Mvc/Views/Shared/_ValidationScriptsPartial.cshtml index 5a16d80..7d8dd3e 100644 --- a/examples/Example.AspNetCore.Mvc/Views/Shared/_ValidationScriptsPartial.cshtml +++ b/examples/Example.AspNetCore.Mvc/Views/Shared/_ValidationScriptsPartial.cshtml @@ -1,2 +1,2 @@ - + diff --git a/examples/Example.AspNetCore.Mvc/Views/_ViewStart.cshtml b/examples/Example.AspNetCore.Mvc/Views/_ViewStart.cshtml index a5f1004..820a2f6 100644 --- a/examples/Example.AspNetCore.Mvc/Views/_ViewStart.cshtml +++ b/examples/Example.AspNetCore.Mvc/Views/_ViewStart.cshtml @@ -1,3 +1,3 @@ -@{ +@{ Layout = "_Layout"; } diff --git a/examples/Example.AspNetCore.Mvc/wwwroot/js/site.js b/examples/Example.AspNetCore.Mvc/wwwroot/js/site.js index 0937657..3b7cc5f 100644 --- a/examples/Example.AspNetCore.Mvc/wwwroot/js/site.js +++ b/examples/Example.AspNetCore.Mvc/wwwroot/js/site.js @@ -1,4 +1,4 @@ -// Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification +// Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification // for details on configuring this project to bundle and minify static web assets. // Write your JavaScript code. diff --git a/examples/Example.AspNetCore.WebApiAot/Example.AspNetCore.WebApiAot.csproj b/examples/Example.AspNetCore.WebApiAot/Example.AspNetCore.WebApiAot.csproj index 9bdc0a4..e3b07d7 100644 --- a/examples/Example.AspNetCore.WebApiAot/Example.AspNetCore.WebApiAot.csproj +++ b/examples/Example.AspNetCore.WebApiAot/Example.AspNetCore.WebApiAot.csproj @@ -1,4 +1,4 @@ - + net9.0 diff --git a/examples/Example.AspNetCore.WebApiAot/Program.cs b/examples/Example.AspNetCore.WebApiAot/Program.cs index 32f0fbe..48a67fc 100644 --- a/examples/Example.AspNetCore.WebApiAot/Program.cs +++ b/examples/Example.AspNetCore.WebApiAot/Program.cs @@ -1,3 +1,7 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + using System.Text.Json.Serialization; using OpenTelemetry; using OpenTelemetry.Trace; diff --git a/examples/Example.AutoInstrumentation/Example.AutoInstrumentation.csproj b/examples/Example.AutoInstrumentation/Example.AutoInstrumentation.csproj index e8c41ec..06bd7cc 100644 --- a/examples/Example.AutoInstrumentation/Example.AutoInstrumentation.csproj +++ b/examples/Example.AutoInstrumentation/Example.AutoInstrumentation.csproj @@ -1,4 +1,4 @@ - + Exe diff --git a/examples/Example.MinimalApi/Program.cs b/examples/Example.MinimalApi/Program.cs index 46215a7..f79b9b6 100644 --- a/examples/Example.MinimalApi/Program.cs +++ b/examples/Example.MinimalApi/Program.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information using System.Diagnostics; -using Example.Api; +using Example.MinimalApi; using OpenTelemetry; var builder = WebApplication.CreateBuilder(args); @@ -27,7 +27,7 @@ app.Run(); -namespace Example.Api +namespace Example.MinimalApi { internal static class Api { diff --git a/examples/ServiceDefaults/Extensions.cs b/examples/ServiceDefaults/Extensions.cs index 36f184f..7cea9cb 100644 --- a/examples/ServiceDefaults/Extensions.cs +++ b/examples/ServiceDefaults/Extensions.cs @@ -10,7 +10,9 @@ using OpenTelemetry.Trace; // ReSharper disable once CheckNamespace +#pragma warning disable IDE0130 // Namespace does not match folder structure namespace Microsoft.Extensions.Hosting; +#pragma warning restore IDE0130 // Namespace does not match folder structure public static class Extensions { diff --git a/examples/ServiceDefaults/ServiceDefaults.csproj b/examples/ServiceDefaults/ServiceDefaults.csproj index 97251ca..39ed5c9 100644 --- a/examples/ServiceDefaults/ServiceDefaults.csproj +++ b/examples/ServiceDefaults/ServiceDefaults.csproj @@ -1,4 +1,4 @@ - + net9.0 diff --git a/nuget.config b/nuget.config index 3f0e003..2c70539 100644 --- a/nuget.config +++ b/nuget.config @@ -1,4 +1,4 @@ - + diff --git a/src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj b/src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj index 8daa29b..32cd87a 100644 --- a/src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj +++ b/src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj @@ -1,4 +1,4 @@ - + Library diff --git a/src/Elastic.OpenTelemetry/Extensions/LoggerFactoryExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/LoggerFactoryExtensions.cs index 4eaeee4..d5256bd 100644 --- a/src/Elastic.OpenTelemetry/Extensions/LoggerFactoryExtensions.cs +++ b/src/Elastic.OpenTelemetry/Extensions/LoggerFactoryExtensions.cs @@ -5,7 +5,9 @@ using Elastic.OpenTelemetry.Diagnostics; using Microsoft.Extensions.Logging; +#pragma warning disable IDE0130 // Namespace does not match folder structure namespace Elastic.OpenTelemetry; +#pragma warning restore IDE0130 // Namespace does not match folder structure internal static class LoggerFactoryExtensions { diff --git a/src/Elastic.OpenTelemetry/Extensions/ResourceBuilderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/ResourceBuilderExtensions.cs index ee8aa63..4bc63b2 100644 --- a/src/Elastic.OpenTelemetry/Extensions/ResourceBuilderExtensions.cs +++ b/src/Elastic.OpenTelemetry/Extensions/ResourceBuilderExtensions.cs @@ -45,7 +45,9 @@ public static class ResourceBuilderExtensions public static ResourceBuilder UseElasticDefaults(this ResourceBuilder builder, ILogger? logger = null) { // ReSharper disable once RedundantAssignment +#pragma warning disable IDE0059 // Unnecessary assignment of a value logger ??= NullLogger.Instance; +#pragma warning restore IDE0059 // Unnecessary assignment of a value var defaultServiceName = "unknown_service"; try diff --git a/test-applications/WebApi/Properties/launchSettings.json b/test-applications/WebApi/Properties/launchSettings.json index 49d01e6..d847cc9 100644 --- a/test-applications/WebApi/Properties/launchSettings.json +++ b/test-applications/WebApi/Properties/launchSettings.json @@ -1,4 +1,4 @@ -{ +{ "$schema": "https://json.schemastore.org/launchsettings.json", "profiles": { "http": { diff --git a/test-applications/WebApi/WebApi.csproj b/test-applications/WebApi/WebApi.csproj index d04273b..e5f02df 100644 --- a/test-applications/WebApi/WebApi.csproj +++ b/test-applications/WebApi/WebApi.csproj @@ -1,4 +1,4 @@ - + net9.0 diff --git a/test-applications/WebApiDotNet8/WebApiDotNet8/Properties/launchSettings.json b/test-applications/WebApiDotNet8/WebApiDotNet8/Properties/launchSettings.json index dcda795..197d544 100644 --- a/test-applications/WebApiDotNet8/WebApiDotNet8/Properties/launchSettings.json +++ b/test-applications/WebApiDotNet8/WebApiDotNet8/Properties/launchSettings.json @@ -1,4 +1,4 @@ -{ +{ "$schema": "http://json.schemastore.org/launchsettings.json", "iisSettings": { "windowsAuthentication": false, diff --git a/test-applications/WebApiDotNet8/WebApiDotNet8/WebApiDotNet8.csproj b/test-applications/WebApiDotNet8/WebApiDotNet8/WebApiDotNet8.csproj index a298c42..61aec26 100644 --- a/test-applications/WebApiDotNet8/WebApiDotNet8/WebApiDotNet8.csproj +++ b/test-applications/WebApiDotNet8/WebApiDotNet8/WebApiDotNet8.csproj @@ -1,4 +1,4 @@ - + net8.0 diff --git a/tests/AutoInstrumentation.IntegrationTests/AutoInstrumentation.IntegrationTests.csproj b/tests/AutoInstrumentation.IntegrationTests/AutoInstrumentation.IntegrationTests.csproj index 4fab745..76b2eb6 100644 --- a/tests/AutoInstrumentation.IntegrationTests/AutoInstrumentation.IntegrationTests.csproj +++ b/tests/AutoInstrumentation.IntegrationTests/AutoInstrumentation.IntegrationTests.csproj @@ -1,4 +1,4 @@ - + net8.0 diff --git a/tests/Elastic.OpenTelemetry.EndToEndTests/Elastic.OpenTelemetry.EndToEndTests.csproj b/tests/Elastic.OpenTelemetry.EndToEndTests/Elastic.OpenTelemetry.EndToEndTests.csproj index 5e56560..446b294 100644 --- a/tests/Elastic.OpenTelemetry.EndToEndTests/Elastic.OpenTelemetry.EndToEndTests.csproj +++ b/tests/Elastic.OpenTelemetry.EndToEndTests/Elastic.OpenTelemetry.EndToEndTests.csproj @@ -1,4 +1,4 @@ - + net9.0 diff --git a/tests/Elastic.OpenTelemetry.Tests/Elastic.OpenTelemetry.Tests.csproj b/tests/Elastic.OpenTelemetry.Tests/Elastic.OpenTelemetry.Tests.csproj index 28ac2e7..66ceb7c 100644 --- a/tests/Elastic.OpenTelemetry.Tests/Elastic.OpenTelemetry.Tests.csproj +++ b/tests/Elastic.OpenTelemetry.Tests/Elastic.OpenTelemetry.Tests.csproj @@ -1,4 +1,4 @@ - + net8.0;net9.0 From 13191ebf8551c8c2885fd502dfa9c50b1471fe8a Mon Sep 17 00:00:00 2001 From: Steve Gordon Date: Tue, 25 Feb 2025 11:54:44 +0000 Subject: [PATCH 05/12] Fix formatting and attempt to ignore AOT warning --- .../Example.AspNetCore.WebApiAot/Program.cs | 18 +++++++++--------- .../Parsers/ConfigurationParser.cs | 2 ++ test-applications/WebApi/Program.cs | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/examples/Example.AspNetCore.WebApiAot/Program.cs b/examples/Example.AspNetCore.WebApiAot/Program.cs index 48a67fc..d49e223 100644 --- a/examples/Example.AspNetCore.WebApiAot/Program.cs +++ b/examples/Example.AspNetCore.WebApiAot/Program.cs @@ -12,25 +12,25 @@ builder.Services.ConfigureHttpJsonOptions(options => { - options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); + options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); }); var app = builder.Build(); var sampleTodos = new Todo[] { - new(1, "Walk the dog"), - new(2, "Do the dishes", DateOnly.FromDateTime(DateTime.Now)), - new(3, "Do the laundry", DateOnly.FromDateTime(DateTime.Now.AddDays(1))), - new(4, "Clean the bathroom"), - new(5, "Clean the car", DateOnly.FromDateTime(DateTime.Now.AddDays(2))) + new(1, "Walk the dog"), + new(2, "Do the dishes", DateOnly.FromDateTime(DateTime.Now)), + new(3, "Do the laundry", DateOnly.FromDateTime(DateTime.Now.AddDays(1))), + new(4, "Clean the bathroom"), + new(5, "Clean the car", DateOnly.FromDateTime(DateTime.Now.AddDays(2))) }; var todosApi = app.MapGroup("/todos"); todosApi.MapGet("/", () => sampleTodos); todosApi.MapGet("/{id}", (int id) => - sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo - ? Results.Ok(todo) - : Results.NotFound()); + sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo + ? Results.Ok(todo) + : Results.NotFound()); app.Run(); diff --git a/src/Elastic.OpenTelemetry/Configuration/Parsers/ConfigurationParser.cs b/src/Elastic.OpenTelemetry/Configuration/Parsers/ConfigurationParser.cs index 034c217..20f2da8 100644 --- a/src/Elastic.OpenTelemetry/Configuration/Parsers/ConfigurationParser.cs +++ b/src/Elastic.OpenTelemetry/Configuration/Parsers/ConfigurationParser.cs @@ -17,6 +17,7 @@ internal class ConfigurationParser internal string? LoggingSectionLogLevel { get; } + #pragma warning disable format public ConfigurationParser(IConfiguration configuration) { _configuration = configuration; @@ -29,6 +30,7 @@ public ConfigurationParser(IConfiguration configuration) if (string.IsNullOrEmpty(LoggingSectionLogLevel)) LoggingSectionLogLevel = configuration.GetValue("Logging:LogLevel:Default"); } + #pragma warning restore format private static void SetFromConfiguration(IConfiguration configuration, ConfigCell cell, Func parser) { diff --git a/test-applications/WebApi/Program.cs b/test-applications/WebApi/Program.cs index d138c31..6557929 100644 --- a/test-applications/WebApi/Program.cs +++ b/test-applications/WebApi/Program.cs @@ -6,7 +6,7 @@ var app = builder.Build(); -app.MapGet("/", () => {}); +app.MapGet("/", () => { }); app.MapGet("/http", static async ctx => { From de0e5f5f4ff8b20257d0bb925b2858834b3e763e Mon Sep 17 00:00:00 2001 From: Steve Gordon Date: Tue, 25 Feb 2025 12:00:15 +0000 Subject: [PATCH 06/12] Fix imports ordering --- .../Configuration/Parsers/ConfigurationParser.cs | 2 -- .../Configuration/CompositeElasticOpenTelemetryOptionsTests.cs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Elastic.OpenTelemetry/Configuration/Parsers/ConfigurationParser.cs b/src/Elastic.OpenTelemetry/Configuration/Parsers/ConfigurationParser.cs index 20f2da8..034c217 100644 --- a/src/Elastic.OpenTelemetry/Configuration/Parsers/ConfigurationParser.cs +++ b/src/Elastic.OpenTelemetry/Configuration/Parsers/ConfigurationParser.cs @@ -17,7 +17,6 @@ internal class ConfigurationParser internal string? LoggingSectionLogLevel { get; } - #pragma warning disable format public ConfigurationParser(IConfiguration configuration) { _configuration = configuration; @@ -30,7 +29,6 @@ public ConfigurationParser(IConfiguration configuration) if (string.IsNullOrEmpty(LoggingSectionLogLevel)) LoggingSectionLogLevel = configuration.GetValue("Logging:LogLevel:Default"); } - #pragma warning restore format private static void SetFromConfiguration(IConfiguration configuration, ConfigCell cell, Func parser) { diff --git a/tests/Elastic.OpenTelemetry.Tests/Configuration/CompositeElasticOpenTelemetryOptionsTests.cs b/tests/Elastic.OpenTelemetry.Tests/Configuration/CompositeElasticOpenTelemetryOptionsTests.cs index 0dca5d5..2f29dcb 100644 --- a/tests/Elastic.OpenTelemetry.Tests/Configuration/CompositeElasticOpenTelemetryOptionsTests.cs +++ b/tests/Elastic.OpenTelemetry.Tests/Configuration/CompositeElasticOpenTelemetryOptionsTests.cs @@ -3,10 +3,10 @@ // See the LICENSE file in the project root for more information using System.Collections; +using System.Diagnostics.Tracing; using System.Text; using Elastic.OpenTelemetry.Configuration; using Xunit.Abstractions; -using System.Diagnostics.Tracing; using static Elastic.OpenTelemetry.Configuration.EnvironmentVariables; using static Elastic.OpenTelemetry.Diagnostics.LogLevelHelpers; From 312d11237fa083301f31444f94667a934bb45d19 Mon Sep 17 00:00:00 2001 From: Steve Gordon Date: Tue, 25 Feb 2025 12:02:08 +0000 Subject: [PATCH 07/12] Skip CheckFormat (for now) --- build/scripts/Targets.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/scripts/Targets.fs b/build/scripts/Targets.fs index 2b9aa33..7eaab33 100644 --- a/build/scripts/Targets.fs +++ b/build/scripts/Targets.fs @@ -170,7 +170,7 @@ let Setup (parsed:ParseResults) = | Version -> Build.Step version | Clean -> Build.Cmd [Version] [] clean | Compile -> Build.Step compile - | Build -> Build.Cmd [Clean; CheckFormat; Compile] [] build + | Build -> Build.Cmd [Clean; (* CheckFormat; *) Compile] [] build | End_To_End -> Build.Cmd [] [Build] <| runTests E2E | Integrate -> Build.Cmd [] [Build] <| runTests Integration From e4f70e4a8106fd5eaff2543afa102c004ee83f4e Mon Sep 17 00:00:00 2001 From: Steve Gordon Date: Tue, 25 Feb 2025 12:42:35 +0000 Subject: [PATCH 08/12] AOT test fix --- .../Aot/NativeAotCompatibilityTests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/Elastic.OpenTelemetry.Tests/Aot/NativeAotCompatibilityTests.cs b/tests/Elastic.OpenTelemetry.Tests/Aot/NativeAotCompatibilityTests.cs index b1b14a8..ff7731b 100644 --- a/tests/Elastic.OpenTelemetry.Tests/Aot/NativeAotCompatibilityTests.cs +++ b/tests/Elastic.OpenTelemetry.Tests/Aot/NativeAotCompatibilityTests.cs @@ -11,11 +11,13 @@ public class NativeAotCompatibilityTests(ITestOutputHelper output) { private readonly ITestOutputHelper _output = output; +// Just testing on one platform for now to speed up tests +#if NET9_0 [Fact] public async Task CanPublishAotApp() { var workingDir = Environment.CurrentDirectory; - var indexOfSolutionFolder = workingDir.AsSpan().IndexOf("elastic-otel-dotnet"); + var indexOfSolutionFolder = workingDir.AsSpan().LastIndexOf("elastic-otel-dotnet"); workingDir = workingDir.AsSpan().Slice(0, indexOfSolutionFolder + "elastic-otel-dotnet".Length).ToString(); workingDir = Path.Combine(workingDir, "examples", "Example.AspNetCore.WebApiAot"); @@ -59,5 +61,6 @@ public async Task CanPublishAotApp() Assert.Equal(0, dotnetPublishProcess.ExitCode); } +#endif } } From 4559d8fae760bb9029f06f7b8c43feedcf6062be Mon Sep 17 00:00:00 2001 From: Steve Gordon Date: Tue, 25 Feb 2025 12:53:30 +0000 Subject: [PATCH 09/12] Skip dotnet format in pristinecheck --- .editorconfig | 5 ++++- build/scripts/Targets.fs | 8 ++++---- src/Directory.Build.props | 1 + 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.editorconfig b/.editorconfig index 795584d..8957e3d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -195,6 +195,9 @@ resharper_explicit_caller_info_argument_highlighting=hint dotnet_diagnostic.IDE0057.severity = none +dotnet_analyzer_diagnostic.severity = warning +dotnet_analyzer_diagnostic.category-Style.severity = warning + [*.{sh,bat,ps1}] trim_trailing_whitespace=true insert_final_newline=true @@ -203,4 +206,4 @@ insert_final_newline=true end_of_line = lf [{LICENSE,NOTICE}] -end_of_line = lf +end_of_line = lf \ No newline at end of file diff --git a/build/scripts/Targets.fs b/build/scripts/Targets.fs index 7eaab33..b604caa 100644 --- a/build/scripts/Targets.fs +++ b/build/scripts/Targets.fs @@ -50,10 +50,10 @@ let private pristineCheck (arguments:ParseResults) = | _, true -> printfn "The checkout folder does not have pending changes, proceeding" | _ -> failwithf "The checkout folder has pending changes, aborting. Specify -c to ./build.sh to skip this check" - match skipCheck, (exec { exit_code_of "dotnet" "format" "--verify-no-changes" }) with - | true, _ -> printfn "Skip formatting checks since -c is specified" - | _, 0 -> printfn "There are no dotnet formatting violations, continuing the build." - | _ -> failwithf "There are dotnet formatting violations. Call `dotnet format` to fix or specify -c to ./build.sh to skip this check" + // match skipCheck, (exec { exit_code_of "dotnet" "format" "--verify-no-changes" }) with + // | true, _ -> printfn "Skip formatting checks since -c is specified" + // | _, 0 -> printfn "There are no dotnet formatting violations, continuing the build." + // | _ -> failwithf "There are dotnet formatting violations. Call `dotnet format` to fix or specify -c to ./build.sh to skip this check" let private runTests suite _ = let logger = diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 951c886..84496c6 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -14,6 +14,7 @@ true true True + true From cd20e56cb6f69aa182d2617f3026d0d8da8ddc4d Mon Sep 17 00:00:00 2001 From: Steve Gordon Date: Tue, 25 Feb 2025 13:19:55 +0000 Subject: [PATCH 10/12] Change warnings --- .editorconfig | 5 ---- src/Directory.Build.props | 1 - .../Aot/NativeAotCompatibilityTests.cs | 6 ++-- ...mpositeElasticOpenTelemetryOptionsTests.cs | 30 +++++++++---------- 4 files changed, 18 insertions(+), 24 deletions(-) diff --git a/.editorconfig b/.editorconfig index 8957e3d..db4b54c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -193,11 +193,6 @@ resharper_redundant_case_label_highlighting=do_not_show resharper_redundant_argument_default_value_highlighting=do_not_show resharper_explicit_caller_info_argument_highlighting=hint -dotnet_diagnostic.IDE0057.severity = none - -dotnet_analyzer_diagnostic.severity = warning -dotnet_analyzer_diagnostic.category-Style.severity = warning - [*.{sh,bat,ps1}] trim_trailing_whitespace=true insert_final_newline=true diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 84496c6..951c886 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -14,7 +14,6 @@ true true True - true diff --git a/tests/Elastic.OpenTelemetry.Tests/Aot/NativeAotCompatibilityTests.cs b/tests/Elastic.OpenTelemetry.Tests/Aot/NativeAotCompatibilityTests.cs index ff7731b..f32fd67 100644 --- a/tests/Elastic.OpenTelemetry.Tests/Aot/NativeAotCompatibilityTests.cs +++ b/tests/Elastic.OpenTelemetry.Tests/Aot/NativeAotCompatibilityTests.cs @@ -2,6 +2,8 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +// Just testing on one platform for now to speed up tests +#if NET9_0 using System.Runtime.InteropServices; using Xunit.Abstractions; @@ -11,8 +13,6 @@ public class NativeAotCompatibilityTests(ITestOutputHelper output) { private readonly ITestOutputHelper _output = output; -// Just testing on one platform for now to speed up tests -#if NET9_0 [Fact] public async Task CanPublishAotApp() { @@ -61,6 +61,6 @@ public async Task CanPublishAotApp() Assert.Equal(0, dotnetPublishProcess.ExitCode); } -#endif } } +#endif diff --git a/tests/Elastic.OpenTelemetry.Tests/Configuration/CompositeElasticOpenTelemetryOptionsTests.cs b/tests/Elastic.OpenTelemetry.Tests/Configuration/CompositeElasticOpenTelemetryOptionsTests.cs index 2f29dcb..4d278cc 100644 --- a/tests/Elastic.OpenTelemetry.Tests/Configuration/CompositeElasticOpenTelemetryOptionsTests.cs +++ b/tests/Elastic.OpenTelemetry.Tests/Configuration/CompositeElasticOpenTelemetryOptionsTests.cs @@ -69,9 +69,9 @@ public void DefaultCtor_LoadsConfigurationFromEnvironmentVariables() sut.LogConfigSources(logger); - Assert.Contains(logger.Messages, s => s.EndsWith("from [Environment]")); - Assert.Contains(logger.Messages, s => s.EndsWith("from [Default]")); - Assert.DoesNotContain(logger.Messages, s => s.EndsWith("from [IConfiguration]")); + Assert.Contains(logger.Messages, s => s.EndsWith("from [Environment]", StringComparison.Ordinal)); + Assert.Contains(logger.Messages, s => s.EndsWith("from [Default]", StringComparison.Ordinal)); + Assert.DoesNotContain(logger.Messages, s => s.EndsWith("from [IConfiguration]", StringComparison.Ordinal)); } [Fact] @@ -120,9 +120,9 @@ public void ConfigurationCtor_LoadsConfigurationFromIConfiguration() Assert.Equal(ExpectedLogsLength, logger.Messages.Count); - Assert.Contains(logger.Messages, s => s.EndsWith("from [IConfiguration]")); - Assert.Contains(logger.Messages, s => s.EndsWith("from [Default]")); - Assert.DoesNotContain(logger.Messages, s => s.EndsWith("from [Environment]")); + Assert.Contains(logger.Messages, s => s.EndsWith("from [IConfiguration]", StringComparison.Ordinal)); + Assert.Contains(logger.Messages, s => s.EndsWith("from [Default]", StringComparison.Ordinal)); + Assert.DoesNotContain(logger.Messages, s => s.EndsWith("from [Environment]", StringComparison.Ordinal)); } [Fact] @@ -165,9 +165,9 @@ public void ConfigurationCtor_LoadsConfigurationFromIConfiguration_AndFallsBackT sut.LogConfigSources(logger); - Assert.Contains(logger.Messages, s => s.EndsWith("from [IConfiguration]")); - Assert.Contains(logger.Messages, s => s.EndsWith("from [Default]")); - Assert.DoesNotContain(logger.Messages, s => s.EndsWith("from [Environment]")); + Assert.Contains(logger.Messages, s => s.EndsWith("from [IConfiguration]", StringComparison.Ordinal)); + Assert.Contains(logger.Messages, s => s.EndsWith("from [Default]", StringComparison.Ordinal)); + Assert.DoesNotContain(logger.Messages, s => s.EndsWith("from [Environment]", StringComparison.Ordinal)); } [Fact] @@ -208,9 +208,9 @@ public void ConfigurationCtor_LoadsConfigurationFromIConfiguration_AndFallsBackT sut.LogConfigSources(logger); - Assert.Contains(logger.Messages, s => s.EndsWith("from [IConfiguration]")); - Assert.Contains(logger.Messages, s => s.EndsWith("from [Default]")); - Assert.DoesNotContain(logger.Messages, s => s.EndsWith("from [Environment]")); + Assert.Contains(logger.Messages, s => s.EndsWith("from [IConfiguration]", StringComparison.Ordinal)); + Assert.Contains(logger.Messages, s => s.EndsWith("from [Default]", StringComparison.Ordinal)); + Assert.DoesNotContain(logger.Messages, s => s.EndsWith("from [Environment]", StringComparison.Ordinal)); } [Fact] @@ -280,9 +280,9 @@ public void InitializedProperties_TakePrecedenceOver_EnvironmentValues() sut.LogConfigSources(logger); - Assert.Contains(logger.Messages, s => s.EndsWith("from [Property]")); - Assert.Contains(logger.Messages, s => s.EndsWith("from [Default]")); - Assert.DoesNotContain(logger.Messages, s => s.EndsWith("from [Environment]")); + Assert.Contains(logger.Messages, s => s.EndsWith("from [Property]", StringComparison.Ordinal)); + Assert.Contains(logger.Messages, s => s.EndsWith("from [Default]", StringComparison.Ordinal)); + Assert.DoesNotContain(logger.Messages, s => s.EndsWith("from [Environment]", StringComparison.Ordinal)); } [Fact] From 7caa923a17fc2e2fb1ab8a4abdcb7c049a3097f3 Mon Sep 17 00:00:00 2001 From: Steve Gordon Date: Tue, 25 Feb 2025 14:38:33 +0000 Subject: [PATCH 11/12] Renable dotnet format --- .editorconfig | 12 ++++++++++++ build/scripts/Targets.fs | 10 +++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/.editorconfig b/.editorconfig index db4b54c..797714f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -193,6 +193,18 @@ resharper_redundant_case_label_highlighting=do_not_show resharper_redundant_argument_default_value_highlighting=do_not_show resharper_explicit_caller_info_argument_highlighting=hint +[src/Elastic.OpenTelemetry/Configuration/CompositeElasticOpenTelemetryOptions.cs] +dotnet_diagnostic.IL3050.severity = none +dotnet_diagnostic.IL2026.severity = none + +[examples/Example.AspNetCore.WebApiAot/**.cs] +dotnet_diagnostic.IL3050.severity = none +dotnet_diagnostic.IL2026.severity = none + +[src/Elastic.OpenTelemetry/Configuration/Parsers/ConfigurationParser.cs] +dotnet_diagnostic.IL3050.severity = none +dotnet_diagnostic.IL2026.severity = none + [*.{sh,bat,ps1}] trim_trailing_whitespace=true insert_final_newline=true diff --git a/build/scripts/Targets.fs b/build/scripts/Targets.fs index b604caa..2b9aa33 100644 --- a/build/scripts/Targets.fs +++ b/build/scripts/Targets.fs @@ -50,10 +50,10 @@ let private pristineCheck (arguments:ParseResults) = | _, true -> printfn "The checkout folder does not have pending changes, proceeding" | _ -> failwithf "The checkout folder has pending changes, aborting. Specify -c to ./build.sh to skip this check" - // match skipCheck, (exec { exit_code_of "dotnet" "format" "--verify-no-changes" }) with - // | true, _ -> printfn "Skip formatting checks since -c is specified" - // | _, 0 -> printfn "There are no dotnet formatting violations, continuing the build." - // | _ -> failwithf "There are dotnet formatting violations. Call `dotnet format` to fix or specify -c to ./build.sh to skip this check" + match skipCheck, (exec { exit_code_of "dotnet" "format" "--verify-no-changes" }) with + | true, _ -> printfn "Skip formatting checks since -c is specified" + | _, 0 -> printfn "There are no dotnet formatting violations, continuing the build." + | _ -> failwithf "There are dotnet formatting violations. Call `dotnet format` to fix or specify -c to ./build.sh to skip this check" let private runTests suite _ = let logger = @@ -170,7 +170,7 @@ let Setup (parsed:ParseResults) = | Version -> Build.Step version | Clean -> Build.Cmd [Version] [] clean | Compile -> Build.Step compile - | Build -> Build.Cmd [Clean; (* CheckFormat; *) Compile] [] build + | Build -> Build.Cmd [Clean; CheckFormat; Compile] [] build | End_To_End -> Build.Cmd [] [Build] <| runTests E2E | Integrate -> Build.Cmd [] [Build] <| runTests Integration From efc30bf97b0958637614ce1e3d825aa9fe2b8b1e Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Tue, 25 Feb 2025 16:06:18 +0100 Subject: [PATCH 12/12] Fix formatting issues on aot-testing branch Fixes `dotnet format` woes on `aot-testing` ``` dotnet format analyzers --severity info -v diag --verify-no-changes ``` --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .editorconfig | 11 +++-------- .github/workflows/release.yml | 2 +- Elastic.OpenTelemetry.sln.DotSettings | 7 ++++--- .../Example.AspNetCore.WebApiAot.csproj | 2 ++ examples/Example.AspNetCore.WebApiAot/Program.cs | 6 +++--- .../CompositeElasticOpenTelemetryOptions.cs | 3 +++ .../Configuration/Parsers/ConfigurationParser.cs | 3 +++ .../Elastic.OpenTelemetry.csproj | 2 ++ .../DistributedFixture/DotNetRunApplication.cs | 7 +++++-- 9 files changed, 26 insertions(+), 17 deletions(-) diff --git a/.editorconfig b/.editorconfig index 797714f..bc35029 100644 --- a/.editorconfig +++ b/.editorconfig @@ -193,15 +193,10 @@ resharper_redundant_case_label_highlighting=do_not_show resharper_redundant_argument_default_value_highlighting=do_not_show resharper_explicit_caller_info_argument_highlighting=hint -[src/Elastic.OpenTelemetry/Configuration/CompositeElasticOpenTelemetryOptions.cs] -dotnet_diagnostic.IL3050.severity = none -dotnet_diagnostic.IL2026.severity = none - -[examples/Example.AspNetCore.WebApiAot/**.cs] -dotnet_diagnostic.IL3050.severity = none -dotnet_diagnostic.IL2026.severity = none +dotnet_diagnostic.IL3050.severity = error +dotnet_diagnostic.IL2026.severity = error -[src/Elastic.OpenTelemetry/Configuration/Parsers/ConfigurationParser.cs] +[examples/Example.AspNetCore.WebApiAot/*.cs] dotnet_diagnostic.IL3050.severity = none dotnet_diagnostic.IL2026.severity = none diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 21a327e..00adb1c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -80,7 +80,7 @@ jobs: - name: Build and Push Profiler Docker Image id: docker-push continue-on-error: true # continue for now until we see it working in action - uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0 + uses: docker/build-push-action@0adf9959216b96bec444f325f1e493d4aa344497 # v6.14.0 with: cache-from: type=gha cache-to: type=gha,mode=max diff --git a/Elastic.OpenTelemetry.sln.DotSettings b/Elastic.OpenTelemetry.sln.DotSettings index 6c7272c..15f3b96 100644 --- a/Elastic.OpenTelemetry.sln.DotSettings +++ b/Elastic.OpenTelemetry.sln.DotSettings @@ -1,4 +1,4 @@ - + HINT <?xml version="1.0" encoding="utf-16"?><Profile name="Quick Reformat"><FSReformatCode>True</FSReformatCode><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSReformatCode>True</CSReformatCode><IDEA_SETTINGS>&lt;profile version="1.0"&gt; &lt;option name="myName" value="Quick Reformat" /&gt; @@ -276,7 +276,7 @@ See the LICENSE file in the project root for more information </Entry.Match> <Entry.SortBy> <Kind Is="Member" /> - <Name Is="Enter Pattern Here" /> + <Name /> </Entry.SortBy> </Entry> <Entry DisplayName="Readonly Fields"> @@ -294,7 +294,7 @@ See the LICENSE file in the project root for more information
<Entry.SortBy> <Access /> <Readonly /> - <Name Is="Enter Pattern Here" /> + <Name /> </Entry.SortBy> </Entry> <Entry DisplayName="Constructors"> @@ -570,6 +570,7 @@ See the LICENSE file in the project root for more information True True True + True True True False diff --git a/examples/Example.AspNetCore.WebApiAot/Example.AspNetCore.WebApiAot.csproj b/examples/Example.AspNetCore.WebApiAot/Example.AspNetCore.WebApiAot.csproj index e3b07d7..5671459 100644 --- a/examples/Example.AspNetCore.WebApiAot/Example.AspNetCore.WebApiAot.csproj +++ b/examples/Example.AspNetCore.WebApiAot/Example.AspNetCore.WebApiAot.csproj @@ -7,6 +7,8 @@ true true 205c18a1-356e-4e59-b15f-2a297cb8d4b8 + true + latest diff --git a/examples/Example.AspNetCore.WebApiAot/Program.cs b/examples/Example.AspNetCore.WebApiAot/Program.cs index d49e223..60033ae 100644 --- a/examples/Example.AspNetCore.WebApiAot/Program.cs +++ b/examples/Example.AspNetCore.WebApiAot/Program.cs @@ -2,6 +2,7 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; using OpenTelemetry; using OpenTelemetry.Trace; @@ -34,9 +35,8 @@ app.Run(); +[SuppressMessage("Design", "CA1050:Declare types in namespaces")] public record Todo(int Id, string? Title, DateOnly? DueBy = null, bool IsComplete = false); [JsonSerializable(typeof(Todo[]))] -internal partial class AppJsonSerializerContext : JsonSerializerContext -{ -} +internal partial class AppJsonSerializerContext : JsonSerializerContext; diff --git a/src/Elastic.OpenTelemetry/Configuration/CompositeElasticOpenTelemetryOptions.cs b/src/Elastic.OpenTelemetry/Configuration/CompositeElasticOpenTelemetryOptions.cs index 560cd8f..520cfb8 100644 --- a/src/Elastic.OpenTelemetry/Configuration/CompositeElasticOpenTelemetryOptions.cs +++ b/src/Elastic.OpenTelemetry/Configuration/CompositeElasticOpenTelemetryOptions.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information using System.Collections; +using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Tracing; using System.Runtime.InteropServices; using Elastic.OpenTelemetry.Configuration.Instrumentations; @@ -76,6 +77,8 @@ internal CompositeElasticOpenTelemetryOptions(IDictionary? environmentVariables) parser.ParseInstrumentationVariables(_signals, _tracing, _metrics, _logging); } + [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "Manually verified")] + [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresDynamicCode", Justification = "Manually verified")] internal CompositeElasticOpenTelemetryOptions(IConfiguration? configuration, IDictionary? environmentVariables = null) : this(environmentVariables) { diff --git a/src/Elastic.OpenTelemetry/Configuration/Parsers/ConfigurationParser.cs b/src/Elastic.OpenTelemetry/Configuration/Parsers/ConfigurationParser.cs index 034c217..7aa21b5 100644 --- a/src/Elastic.OpenTelemetry/Configuration/Parsers/ConfigurationParser.cs +++ b/src/Elastic.OpenTelemetry/Configuration/Parsers/ConfigurationParser.cs @@ -2,6 +2,7 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Tracing; using Elastic.OpenTelemetry.Diagnostics; using Microsoft.Extensions.Configuration; @@ -10,6 +11,8 @@ namespace Elastic.OpenTelemetry.Configuration.Parsers; +[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "Manually verified")] +[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3050:RequiresDynamicCode", Justification = "Manually verified")] internal class ConfigurationParser { private readonly IConfiguration _configuration; diff --git a/src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj b/src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj index 32cd87a..fc460e3 100644 --- a/src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj +++ b/src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj @@ -17,6 +17,8 @@ true true + false + true diff --git a/tests/Elastic.OpenTelemetry.EndToEndTests/DistributedFixture/DotNetRunApplication.cs b/tests/Elastic.OpenTelemetry.EndToEndTests/DistributedFixture/DotNetRunApplication.cs index a12831f..dd9ca6e 100644 --- a/tests/Elastic.OpenTelemetry.EndToEndTests/DistributedFixture/DotNetRunApplication.cs +++ b/tests/Elastic.OpenTelemetry.EndToEndTests/DistributedFixture/DotNetRunApplication.cs @@ -9,10 +9,10 @@ namespace Elastic.OpenTelemetry.EndToEndTests.DistributedFixture; -public abstract class DotNetRunApplication +public abstract partial class DotNetRunApplication { private static readonly DirectoryInfo CurrentDirectory = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory!; - private static readonly Regex ProcessIdMatch = new(@"^\s*Process Id (?\d+)"); + private static readonly Regex ProcessIdMatch = ProcessIdMatchRegex(); public static readonly DirectoryInfo Root = GetSolutionRoot(); public static readonly DirectoryInfo LogDirectory = new(Path.Combine(Root.FullName, ".artifacts", "tests")); @@ -114,4 +114,7 @@ public virtual void Dispose() _app.SendControlC(ProcessId.Value); _app.Dispose(); } + + [GeneratedRegex(@"^\s*Process Id (?\d+)")] + private static partial Regex ProcessIdMatchRegex(); }