From b549e12148aa76e447a76c5c77701727e30a6c7d Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Wed, 8 Feb 2023 15:20:02 -0800 Subject: [PATCH] [sdk] Use reflection and dynamic types to start providers (#4151) * Remove StartWithHost extension and using reflection to do the work in SDK. * Warning cleanup. * Fix up. * Improved logging. * Remove OpenTelemetry.Extensions.Hosting dependency from examples & docs. * Test fixes. * Fixes and test updates. * Bug fix. * Patch CHANGELOGs. * Clean up. * Support Azure Functions TelemetryHostedService dependency inspection logic. * Code review. * Code review. * Code review. --------- Co-authored-by: Cijo Thomas --- build/Common.props | 3 +- .../getting-started-aspnetcore/Program.cs | 3 +- .../getting-started-aspnetcore.csproj | 1 - .../AspNetCore/Examples.AspNetCore.csproj | 1 - examples/AspNetCore/Program.cs | 6 +- .../GrpcService/Examples.GrpcService.csproj | 1 - examples/GrpcService/Startup.cs | 3 +- .../MicroserviceExample/WebApi/Startup.cs | 3 +- .../MicroserviceExample/WebApi/WebApi.csproj | 1 - .../WorkerService/Program.cs | 3 +- .../WorkerService/WorkerService.csproj | 1 - src/OpenTelemetry.Exporter.Jaeger/README.md | 3 +- .../README.md | 3 +- .../README.md | 3 +- src/OpenTelemetry.Exporter.Zipkin/README.md | 3 +- .../netstandard2.0/PublicAPI.Unshipped.txt | 2 - .../CHANGELOG.md | 6 + .../HostingExtensionsEventSource.cs | 41 --- .../Implementation/TelemetryHostedService.cs | 64 ----- .../OpenTelemetryBuilderHostingExtensions.cs | 54 ---- .../OpenTelemetryServicesExtensions.cs | 12 +- .../README.md | 18 +- .../README.md | 12 +- .../README.md | 3 +- src/OpenTelemetry/CHANGELOG.md | 4 + .../EnvironmentVariablesExtensions.cs | 51 +--- src/OpenTelemetry/Internal/HostingHelper.cs | 253 +++++++++++++++++ .../Internal/OpenTelemetrySdkEventSource.cs | 39 +++ .../Shims/IgnoresAccessChecksToAttribute.cs | 33 +++ src/OpenTelemetry/OpenTelemetry.csproj | 1 + src/OpenTelemetry/OpenTelemetryBuilder.cs | 26 +- ...penTelemetryServiceCollectionExtensions.cs | 19 +- .../EventSourceTest.cs | 31 -- .../HostingMeterExtensionTests.cs | 109 -------- .../HostingTracerExtensionTests.cs | 109 -------- .../InMemoryExporterMetricsExtensionsTests.cs | 5 +- .../TelemetryHostedServiceTests.cs | 87 ------ .../BasicTests.cs | 6 +- .../DependencyInjectionConfigTests.cs | 6 +- ...stsCollectionsIsAccordingToTheSpecTests.cs | 3 +- .../OpenTelemetry.Tests.csproj | 3 +- ...lemetryServiceCollectionExtensionsTests.cs | 264 ++++++++++++++++++ 42 files changed, 663 insertions(+), 636 deletions(-) delete mode 100644 src/OpenTelemetry.Extensions.Hosting/Implementation/HostingExtensionsEventSource.cs delete mode 100644 src/OpenTelemetry.Extensions.Hosting/Implementation/TelemetryHostedService.cs delete mode 100644 src/OpenTelemetry.Extensions.Hosting/OpenTelemetryBuilderHostingExtensions.cs create mode 100644 src/OpenTelemetry/Internal/HostingHelper.cs create mode 100644 src/OpenTelemetry/Internal/Shims/IgnoresAccessChecksToAttribute.cs delete mode 100644 test/OpenTelemetry.Extensions.Hosting.Tests/EventSourceTest.cs delete mode 100644 test/OpenTelemetry.Extensions.Hosting.Tests/HostingMeterExtensionTests.cs delete mode 100644 test/OpenTelemetry.Extensions.Hosting.Tests/HostingTracerExtensionTests.cs delete mode 100644 test/OpenTelemetry.Extensions.Hosting.Tests/TelemetryHostedServiceTests.cs create mode 100644 test/OpenTelemetry.Tests/OpenTelemetryServiceCollectionExtensionsTests.cs diff --git a/build/Common.props b/build/Common.props index d47bf985f5e..a1d08f1e37d 100644 --- a/build/Common.props +++ b/build/Common.props @@ -37,7 +37,8 @@ [17.4.1] [3.1.0,) $(MicrosoftExtensionsDependencyInjectionPkgVer) - [2.1.0,) + [2.1.0,) + $(MicrosoftExtensionsHostingPkgVer) [3.1.0,) $(MicrosoftExtensionsLoggingPkgVer) [3.1.0,) diff --git a/docs/trace/getting-started-aspnetcore/Program.cs b/docs/trace/getting-started-aspnetcore/Program.cs index 2feba0bd229..a2914077175 100644 --- a/docs/trace/getting-started-aspnetcore/Program.cs +++ b/docs/trace/getting-started-aspnetcore/Program.cs @@ -27,8 +27,7 @@ .AddService(serviceName: "OTel.NET Getting Started")) .WithTracing(builder => builder .AddAspNetCoreInstrumentation() - .AddConsoleExporter()) - .StartWithHost(); + .AddConsoleExporter()); var app = appBuilder.Build(); diff --git a/docs/trace/getting-started-aspnetcore/getting-started-aspnetcore.csproj b/docs/trace/getting-started-aspnetcore/getting-started-aspnetcore.csproj index 1956f427004..321fbcd608b 100644 --- a/docs/trace/getting-started-aspnetcore/getting-started-aspnetcore.csproj +++ b/docs/trace/getting-started-aspnetcore/getting-started-aspnetcore.csproj @@ -8,7 +8,6 @@ - diff --git a/examples/AspNetCore/Examples.AspNetCore.csproj b/examples/AspNetCore/Examples.AspNetCore.csproj index b8b09bcd9d4..7e29680340f 100644 --- a/examples/AspNetCore/Examples.AspNetCore.csproj +++ b/examples/AspNetCore/Examples.AspNetCore.csproj @@ -10,7 +10,6 @@ - diff --git a/examples/AspNetCore/Program.cs b/examples/AspNetCore/Program.cs index 53245406d57..80527159685 100644 --- a/examples/AspNetCore/Program.cs +++ b/examples/AspNetCore/Program.cs @@ -44,8 +44,7 @@ // for manual instrumentation appBuilder.Services.AddSingleton(); -// Configure OpenTelemetry tracing & metrics with auto-start using the -// StartWithHost extension from OpenTelemetry.Extensions.Hosting. +// Configure OpenTelemetry tracing & metrics. appBuilder.Services.AddOpenTelemetry() .ConfigureResource(configureResource) .WithTracing(builder => @@ -127,8 +126,7 @@ builder.AddConsoleExporter(); break; } - }) - .StartWithHost(); + }); // Clear default logging providers used by WebApplication host. appBuilder.Logging.ClearProviders(); diff --git a/examples/GrpcService/Examples.GrpcService.csproj b/examples/GrpcService/Examples.GrpcService.csproj index 2a9028e8650..7123737717a 100644 --- a/examples/GrpcService/Examples.GrpcService.csproj +++ b/examples/GrpcService/Examples.GrpcService.csproj @@ -16,7 +16,6 @@ - diff --git a/examples/GrpcService/Startup.cs b/examples/GrpcService/Startup.cs index 2c875eae04b..32d595ab63d 100644 --- a/examples/GrpcService/Startup.cs +++ b/examples/GrpcService/Startup.cs @@ -61,8 +61,7 @@ public void ConfigureServices(IServiceCollection services) builder.AddConsoleExporter(); break; } - }) - .StartWithHost(); + }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) diff --git a/examples/MicroserviceExample/WebApi/Startup.cs b/examples/MicroserviceExample/WebApi/Startup.cs index 90e66783302..d3966c26aa2 100644 --- a/examples/MicroserviceExample/WebApi/Startup.cs +++ b/examples/MicroserviceExample/WebApi/Startup.cs @@ -43,8 +43,7 @@ public void ConfigureServices(IServiceCollection services) { var zipkinHostName = Environment.GetEnvironmentVariable("ZIPKIN_HOSTNAME") ?? "localhost"; b.Endpoint = new Uri($"http://{zipkinHostName}:9411/api/v2/spans"); - })) - .StartWithHost(); + })); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) diff --git a/examples/MicroserviceExample/WebApi/WebApi.csproj b/examples/MicroserviceExample/WebApi/WebApi.csproj index 876450b7176..5e1695e9c86 100644 --- a/examples/MicroserviceExample/WebApi/WebApi.csproj +++ b/examples/MicroserviceExample/WebApi/WebApi.csproj @@ -10,7 +10,6 @@ - diff --git a/examples/MicroserviceExample/WorkerService/Program.cs b/examples/MicroserviceExample/WorkerService/Program.cs index 665265cedd4..75ad1223e16 100644 --- a/examples/MicroserviceExample/WorkerService/Program.cs +++ b/examples/MicroserviceExample/WorkerService/Program.cs @@ -42,8 +42,7 @@ public static IHostBuilder CreateHostBuilder(string[] args) => { var zipkinHostName = Environment.GetEnvironmentVariable("ZIPKIN_HOSTNAME") ?? "localhost"; b.Endpoint = new Uri($"http://{zipkinHostName}:9411/api/v2/spans"); - })) - .StartWithHost(); + })); }); } } diff --git a/examples/MicroserviceExample/WorkerService/WorkerService.csproj b/examples/MicroserviceExample/WorkerService/WorkerService.csproj index 926e55dd6fd..29d72978cd5 100644 --- a/examples/MicroserviceExample/WorkerService/WorkerService.csproj +++ b/examples/MicroserviceExample/WorkerService/WorkerService.csproj @@ -11,7 +11,6 @@ - diff --git a/src/OpenTelemetry.Exporter.Jaeger/README.md b/src/OpenTelemetry.Exporter.Jaeger/README.md index 411e3d40283..643d9874819 100644 --- a/src/OpenTelemetry.Exporter.Jaeger/README.md +++ b/src/OpenTelemetry.Exporter.Jaeger/README.md @@ -98,8 +98,7 @@ services.AddOpenTelemetry() client.DefaultRequestHeaders.Add("X-MyCustomHeader", "value"); return client; }; - })) - .StartWithHost(); + })); ``` For users using diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md index 369b6e1574e..177f9639443 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md @@ -135,8 +135,7 @@ services.AddOpenTelemetry() client.DefaultRequestHeaders.Add("X-MyCustomHeader", "value"); return client; }; - })) - .StartWithHost(); + })); ``` For users using diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/README.md b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/README.md index 6bcd236b5bb..e6676934961 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/README.md +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/README.md @@ -28,8 +28,7 @@ dotnet add package --prerelease OpenTelemetry.Exporter.Prometheus.AspNetCore ```csharp services.AddOpenTelemetry() .WithMetrics(builder => builder - .AddPrometheusExporter()) - .StartWithHost(); + .AddPrometheusExporter()); ``` * Or configure directly: diff --git a/src/OpenTelemetry.Exporter.Zipkin/README.md b/src/OpenTelemetry.Exporter.Zipkin/README.md index 20d794c051d..aa277b7fdc2 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/README.md +++ b/src/OpenTelemetry.Exporter.Zipkin/README.md @@ -87,8 +87,7 @@ services.AddOpenTelemetry() HttpClient client = new HttpClient(); client.DefaultRequestHeaders.Add("X-MyCustomHeader", "value"); return client; - })) - .StartWithHost(); + })); ``` For users using diff --git a/src/OpenTelemetry.Extensions.Hosting/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Extensions.Hosting/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index dbee0f5b37d..63141a61d05 100644 --- a/src/OpenTelemetry.Extensions.Hosting/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Extensions.Hosting/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,6 +1,5 @@ Microsoft.Extensions.DependencyInjection.OpenTelemetryServicesExtensions OpenTelemetry.Metrics.MeterProviderBuilderExtensions -OpenTelemetry.OpenTelemetryBuilderHostingExtensions OpenTelemetry.Trace.TracerProviderBuilderExtensions static Microsoft.Extensions.DependencyInjection.OpenTelemetryServicesExtensions.AddOpenTelemetryMetrics(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection static Microsoft.Extensions.DependencyInjection.OpenTelemetryServicesExtensions.AddOpenTelemetryMetrics(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configure) -> Microsoft.Extensions.DependencyInjection.IServiceCollection @@ -8,6 +7,5 @@ static Microsoft.Extensions.DependencyInjection.OpenTelemetryServicesExtensions. static Microsoft.Extensions.DependencyInjection.OpenTelemetryServicesExtensions.AddOpenTelemetryTracing(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configure) -> Microsoft.Extensions.DependencyInjection.IServiceCollection static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.Configure(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, System.Action configure) -> OpenTelemetry.Metrics.MeterProviderBuilder static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.GetServices(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder) -> Microsoft.Extensions.DependencyInjection.IServiceCollection -static OpenTelemetry.OpenTelemetryBuilderHostingExtensions.StartWithHost(this OpenTelemetry.OpenTelemetryBuilder builder) -> OpenTelemetry.OpenTelemetryBuilder static OpenTelemetry.Trace.TracerProviderBuilderExtensions.Configure(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, System.Action configure) -> OpenTelemetry.Trace.TracerProviderBuilder static OpenTelemetry.Trace.TracerProviderBuilderExtensions.GetServices(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder) -> Microsoft.Extensions.DependencyInjection.IServiceCollection diff --git a/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md b/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md index 8e7983ec511..b31b6c4ccb3 100644 --- a/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md +++ b/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +* Removed the `OpenTelemetryBuilder.StartWithHost` extension and moved the + functionality into the SDK `AddOpenTelemetry` extension. With this change + `OpenTelemetry.Extensions.Hosting` is no longer needed and will be marked as + deprecated. + ([#4151](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4151)) + ## 1.4.0-rc.3 Released 2023-Feb-01 diff --git a/src/OpenTelemetry.Extensions.Hosting/Implementation/HostingExtensionsEventSource.cs b/src/OpenTelemetry.Extensions.Hosting/Implementation/HostingExtensionsEventSource.cs deleted file mode 100644 index f6f86038cfe..00000000000 --- a/src/OpenTelemetry.Extensions.Hosting/Implementation/HostingExtensionsEventSource.cs +++ /dev/null @@ -1,41 +0,0 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System.Diagnostics.Tracing; - -namespace OpenTelemetry.Extensions.Hosting.Implementation -{ - /// - /// EventSource events emitted from the project. - /// - [EventSource(Name = "OpenTelemetry-Extensions-Hosting")] - internal sealed class HostingExtensionsEventSource : EventSource - { - public static HostingExtensionsEventSource Log = new(); - - [Event(1, Message = "OpenTelemetry TracerProvider was not found in application services. Tracing will remain disabled.", Level = EventLevel.Warning)] - public void TracerProviderNotRegistered() - { - this.WriteEvent(1); - } - - [Event(2, Message = "OpenTelemetry MeterProvider was not found in application services. Metrics will remain disabled.", Level = EventLevel.Warning)] - public void MeterProviderNotRegistered() - { - this.WriteEvent(2); - } - } -} diff --git a/src/OpenTelemetry.Extensions.Hosting/Implementation/TelemetryHostedService.cs b/src/OpenTelemetry.Extensions.Hosting/Implementation/TelemetryHostedService.cs deleted file mode 100644 index 78e50395b59..00000000000 --- a/src/OpenTelemetry.Extensions.Hosting/Implementation/TelemetryHostedService.cs +++ /dev/null @@ -1,64 +0,0 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System.Diagnostics; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using OpenTelemetry.Metrics; -using OpenTelemetry.Trace; - -namespace OpenTelemetry.Extensions.Hosting.Implementation; - -internal sealed class TelemetryHostedService : IHostedService -{ - private readonly IServiceProvider serviceProvider; - - public TelemetryHostedService(IServiceProvider serviceProvider) - { - this.serviceProvider = serviceProvider; - } - - public Task StartAsync(CancellationToken cancellationToken) - { - // The sole purpose of this HostedService is to ensure all - // instrumentations, exporters, etc., are created and started. - Initialize(this.serviceProvider); - - return Task.CompletedTask; - } - - public Task StopAsync(CancellationToken cancellationToken) - { - return Task.CompletedTask; - } - - internal static void Initialize(IServiceProvider serviceProvider) - { - Debug.Assert(serviceProvider != null, "serviceProvider was null"); - - var meterProvider = serviceProvider.GetService(); - if (meterProvider == null) - { - HostingExtensionsEventSource.Log.MeterProviderNotRegistered(); - } - - var tracerProvider = serviceProvider.GetService(); - if (tracerProvider == null) - { - HostingExtensionsEventSource.Log.TracerProviderNotRegistered(); - } - } -} diff --git a/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryBuilderHostingExtensions.cs b/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryBuilderHostingExtensions.cs deleted file mode 100644 index 646ca614f44..00000000000 --- a/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryBuilderHostingExtensions.cs +++ /dev/null @@ -1,54 +0,0 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Hosting; -using OpenTelemetry.Extensions.Hosting.Implementation; -using OpenTelemetry.Internal; - -namespace OpenTelemetry; - -/// -/// Contains hosting extension methods for the class. -/// -public static class OpenTelemetryBuilderHostingExtensions -{ - /// - /// Registers an to automatically start all - /// configured OpenTelemetry services in the supplied . - /// - /// - /// Note: This is safe to be called multiple times. Only a single will be created for a given . This should generally be called by hosting - /// application code and NOT library authors. - /// - /// . - /// The supplied for chaining - /// calls. - public static OpenTelemetryBuilder StartWithHost(this OpenTelemetryBuilder builder) - { - Guard.ThrowIfNull(builder); - - builder.Services.TryAddEnumerable( - ServiceDescriptor.Singleton()); - - return builder; - } -} diff --git a/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryServicesExtensions.cs b/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryServicesExtensions.cs index 64e837fbf16..ce238c273f7 100644 --- a/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryServicesExtensions.cs +++ b/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryServicesExtensions.cs @@ -51,7 +51,7 @@ public static class OpenTelemetryServicesExtensions /// . /// Supplied for chaining /// calls. - [Obsolete("Use the AddOpenTelemetry().WithTracing(configure).StartWithHost() pattern instead. This method will be removed in a future version.")] + [Obsolete("Use the AddOpenTelemetry().WithTracing(configure) pattern instead. This method will be removed in a future version.")] public static IServiceCollection AddOpenTelemetryTracing(this IServiceCollection services) => AddOpenTelemetryTracing(services, b => { }); @@ -68,10 +68,10 @@ public static IServiceCollection AddOpenTelemetryTracing(this IServiceCollection /// cref="TracerProviderBuilder"/>. /// Supplied for chaining /// calls. - [Obsolete("Use the AddOpenTelemetry().WithTracing(configure).StartWithHost() pattern instead. This method will be removed in a future version.")] + [Obsolete("Use the AddOpenTelemetry().WithTracing(configure) pattern instead. This method will be removed in a future version.")] public static IServiceCollection AddOpenTelemetryTracing(this IServiceCollection services, Action configure) { - services.AddOpenTelemetry().WithTracing(configure).StartWithHost(); + services.AddOpenTelemetry().WithTracing(configure); return services; } @@ -100,7 +100,7 @@ public static IServiceCollection AddOpenTelemetryTracing(this IServiceCollection /// . /// Supplied for chaining /// calls. - [Obsolete("Use the AddOpenTelemetry().WithMetrics(configure).StartWithHost() pattern instead. This method will be removed in a future version.")] + [Obsolete("Use the AddOpenTelemetry().WithMetrics(configure) pattern instead. This method will be removed in a future version.")] public static IServiceCollection AddOpenTelemetryMetrics(this IServiceCollection services) => AddOpenTelemetryMetrics(services, b => { }); @@ -117,10 +117,10 @@ public static IServiceCollection AddOpenTelemetryMetrics(this IServiceCollection /// cref="TracerProviderBuilder"/>. /// Supplied for chaining /// calls. - [Obsolete("Use the AddOpenTelemetry().WithMetrics(configure).StartWithHost() pattern instead. This method will be removed in a future version.")] + [Obsolete("Use the AddOpenTelemetry().WithMetrics(configure) pattern instead. This method will be removed in a future version.")] public static IServiceCollection AddOpenTelemetryMetrics(this IServiceCollection services, Action configure) { - services.AddOpenTelemetry().WithMetrics(configure).StartWithHost(); + services.AddOpenTelemetry().WithMetrics(configure); return services; } diff --git a/src/OpenTelemetry.Extensions.Hosting/README.md b/src/OpenTelemetry.Extensions.Hosting/README.md index a1703580f07..59014925824 100644 --- a/src/OpenTelemetry.Extensions.Hosting/README.md +++ b/src/OpenTelemetry.Extensions.Hosting/README.md @@ -21,17 +21,6 @@ and metrics (`MeterProvider`) in [ASP.NET ## Extension method reference -### Current OpenTelemetry SDK v1.4.0 and newer extensions - -Targeting `OpenTelemetry.OpenTelemetryBuilder`: - -* `StartWithHost`: Registers an - [IHostedService](https://learn.microsoft.com/dotnet/api/microsoft.extensions.hosting.ihostedservice) - to automatically start tracing and/or metric services in the supplied - [IServiceCollection](https://learn.microsoft.com/dotnet/api/microsoft.extensions.dependencyinjection.iservicecollection). - -### Obsolete OpenTelemetry SDK pre-1.4.0 extensions - > **Note** > The below extension methods should be called by application host code only. Library authors see: [Registration extension method guidance for library @@ -72,10 +61,9 @@ using OpenTelemetry.Trace; var appBuilder = WebApplication.CreateBuilder(args); -appBuilder.Services.AddOpenTelemetry() - .WithTracing(builder => builder.AddConsoleExporter()) - .WithMetrics(builder => builder.AddConsoleExporter()) - .StartWithHost(); +appBuilder.Services.AddOpenTelemetryTracing(builder => builder.AddConsoleExporter()); + +appBuilder.Services.AddOpenTelemetryMetrics(builder => builder.AddConsoleExporter()); var app = appBuilder.Build(); diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/README.md b/src/OpenTelemetry.Instrumentation.AspNetCore/README.md index 59e489b8676..fd535b1682c 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/README.md +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/README.md @@ -58,8 +58,7 @@ public void ConfigureServices(IServiceCollection services) services.AddOpenTelemetry() .WithTracing(builder => builder .AddAspNetCoreInstrumentation() - .AddJaegerExporter()) - .StartWithHost(); + .AddJaegerExporter()); } ``` @@ -88,8 +87,7 @@ services.Configure(options => services.AddOpenTelemetry() .WithTracing(builder => builder .AddAspNetCoreInstrumentation() - .AddJaegerExporter()) - .StartWithHost(); + .AddJaegerExporter()); ``` ### Filter @@ -112,8 +110,7 @@ services.AddOpenTelemetry() // only collect telemetry about HTTP GET requests return httpContext.Request.Method.Equals("GET"); }) - .AddJaegerExporter()) - .StartWithHost(); + .AddJaegerExporter()); ``` It is important to note that this `Filter` option is specific to this @@ -150,8 +147,7 @@ services.AddOpenTelemetry() { activity.SetTag("exceptionType", exception.GetType().ToString()); }; - })) - .StartWithHost(); + })); ``` [Processor](../../docs/trace/extending-the-sdk/README.md#processor), diff --git a/src/OpenTelemetry.Instrumentation.GrpcNetClient/README.md b/src/OpenTelemetry.Instrumentation.GrpcNetClient/README.md index 62bb0853aea..bfb6dfbb701 100644 --- a/src/OpenTelemetry.Instrumentation.GrpcNetClient/README.md +++ b/src/OpenTelemetry.Instrumentation.GrpcNetClient/README.md @@ -120,8 +120,7 @@ services.AddOpenTelemetry() { activity.SetTag("responseVersion", httpResponseMessage.Version); }; - }) - .StartWithHost(); + }); ``` [Processor](../../docs/trace/extending-the-sdk/README.md#processor), diff --git a/src/OpenTelemetry/CHANGELOG.md b/src/OpenTelemetry/CHANGELOG.md index 9940288268f..2b03fe7dcaa 100644 --- a/src/OpenTelemetry/CHANGELOG.md +++ b/src/OpenTelemetry/CHANGELOG.md @@ -5,6 +5,10 @@ * Removed the dependency on System.Reflection.Emit.Lightweight ([#4140](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4140)) +* The `AddOpenTelemetry` extension will now register an `IHostedService` if + `Microsoft.Extensions.Hosting.Abstractions` is detected. + ([#4151](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4151)) + ## 1.4.0-rc.3 Released 2023-Feb-01 diff --git a/src/OpenTelemetry/Internal/EnvironmentVariables/EnvironmentVariablesExtensions.cs b/src/OpenTelemetry/Internal/EnvironmentVariables/EnvironmentVariablesExtensions.cs index 5b97e90ce77..02928d154d5 100644 --- a/src/OpenTelemetry/Internal/EnvironmentVariables/EnvironmentVariablesExtensions.cs +++ b/src/OpenTelemetry/Internal/EnvironmentVariables/EnvironmentVariablesExtensions.cs @@ -4,49 +4,24 @@ #nullable enable -using System; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration.EnvironmentVariables; -namespace Microsoft.Extensions.Configuration +namespace OpenTelemetry.Internal; + +/// +/// Extension methods for registering with . +/// +internal static class EnvironmentVariablesExtensions { /// - /// Extension methods for registering with . + /// Adds an that reads configuration values from environment variables. /// - internal static class EnvironmentVariablesExtensions + /// The to add to. + /// The . + public static IConfigurationBuilder AddEnvironmentVariables(this IConfigurationBuilder configurationBuilder) { - /// - /// Adds an that reads configuration values from environment variables. - /// - /// The to add to. - /// The . - public static IConfigurationBuilder AddEnvironmentVariables(this IConfigurationBuilder configurationBuilder) - { - configurationBuilder.Add(new EnvironmentVariablesConfigurationSource()); - return configurationBuilder; - } - - /// - /// Adds an that reads configuration values from environment variables - /// with a specified prefix. - /// - /// The to add to. - /// The prefix that environment variable names must start with. The prefix will be removed from the environment variable names. - /// The . - public static IConfigurationBuilder AddEnvironmentVariables( - this IConfigurationBuilder configurationBuilder, - string? prefix) - { - configurationBuilder.Add(new EnvironmentVariablesConfigurationSource { Prefix = prefix }); - return configurationBuilder; - } - - /// - /// Adds an that reads configuration values from environment variables. - /// - /// The to add to. - /// Configures the source. - /// The . - public static IConfigurationBuilder AddEnvironmentVariables(this IConfigurationBuilder builder, Action? configureSource) - => builder.Add(configureSource); + configurationBuilder.Add(new EnvironmentVariablesConfigurationSource()); + return configurationBuilder; } } diff --git a/src/OpenTelemetry/Internal/HostingHelper.cs b/src/OpenTelemetry/Internal/HostingHelper.cs new file mode 100644 index 00000000000..58708f0aad6 --- /dev/null +++ b/src/OpenTelemetry/Internal/HostingHelper.cs @@ -0,0 +1,253 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#nullable enable + +using System.Diagnostics; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.CompilerServices; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace OpenTelemetry.Internal; + +internal static class HostingHelper +{ + private static readonly object LockObject = new(); + private static bool initialized; + private static Type? hostedServiceImplementation; + + public static void AddOpenTelemetryHostedServiceIntoServiceCollection(IServiceCollection services) + { + if (TryAddOpenTelemetryHostedServiceIntoServiceCollection(services, out var reason)) + { + OpenTelemetrySdkEventSource.Log.HostedServiceRegistered(); + } + else + { + OpenTelemetrySdkEventSource.Log.HostedServiceRegistrationSkipped(reason); + } + } + + private static bool TryAddOpenTelemetryHostedServiceIntoServiceCollection(IServiceCollection services, out string? reason) + { +#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER + bool isDynamicCodeSupported = RuntimeFeature.IsDynamicCodeSupported; +#else + // Note: This is for .NET Framework and/or .NET Standard 2.0 targets. + bool isDynamicCodeSupported = true; +#endif + if (!isDynamicCodeSupported) + { + reason = "Dynamic code not supported"; + return false; + } + + var iHostedServiceType = Type.GetType( + "Microsoft.Extensions.Hosting.IHostedService, Microsoft.Extensions.Hosting.Abstractions", throwOnError: false); + + if (iHostedServiceType == null) + { + reason = "Microsoft.Extensions.Hosting.IHostedService not found"; + return false; + } + + lock (LockObject) + { + if (!initialized) + { + try + { + hostedServiceImplementation = CreateHostedServiceImplementation(iHostedServiceType); + } + catch (Exception ex) + { + OpenTelemetrySdkEventSource.Log.HostedServiceRegistrationFailure(ex); + } + finally + { + initialized = true; + } + } + } + + if (hostedServiceImplementation == null) + { + reason = "Initialization failure"; + return false; + } + + services.TryAddSingleton(); + services.TryAddEnumerable(ServiceDescriptor.Singleton(iHostedServiceType, hostedServiceImplementation)); + + reason = null; + return true; + } + + private static Type CreateHostedServiceImplementation(Type iHostedServiceType) + { + /* + * Note: This code builds a class dynamically that does this... + * + * namespace OpenTelemetry.Extensions.Hosting.Implementation; + * + * class TelemetryHostedService : IHostedService + * { + * private readonly TelemetryHostedServiceHelper telemetryHostedServiceHelper; + * + * public TelemetryHostedService(TelemetryHostedServiceHelper telemetryHostedServiceHelper) + * { + * this.telemetryHostedServiceHelper = telemetryHostedServiceHelper; + * } + * + * public Task StartAsync(CancellationToken cancellationToken) + * { + * this.telemetryHostedServiceHelper.Start(); + * return Task.CompletedTask; + * } + * + * public Task StopAsync(CancellationToken cancellationToken) + * { + * return Task.CompletedTask; + * } + * } + */ + + var getCompletedTaskMethod = typeof(Task).GetProperty(nameof(Task.CompletedTask), BindingFlags.Static | BindingFlags.Public)?.GetMethod + ?? throw new InvalidOperationException("Task.CompletedTask could not be found reflectively."); + + // Note: It is important that the assembly is named + // OpenTelemetry.Extensions.Hosting and the type is named + // OpenTelemetry.Extensions.Hosting.Implementation.TelemetryHostedService + // to preserve compatibility with Azure Functions: + // https://github.com/Azure/azure-functions-host/blob/d4655cc4fbb34fc14e6861731991118a9acd02ed/src/WebJobs.Script.WebHost/DependencyInjection/DependencyValidator/DependencyValidator.cs#L57. + var assemblyName = new AssemblyName("OpenTelemetry.Extensions.Hosting"); + + assemblyName.SetPublicKey(typeof(HostingHelper).Assembly.GetName().GetPublicKey()); + + var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); + + // Note: We use IgnoresAccessChecksToAttribute to allow the dynamic + // assembly to call TelemetryHostedService which is internal to + // OpenTelemetry.dll. + var ignoresAccessChecksTo = new CustomAttributeBuilder( + typeof(IgnoresAccessChecksToAttribute).GetConstructor(new Type[] { typeof(string) }) ?? throw new InvalidOperationException("IgnoresAccessChecksToAttribute constructor could not be found reflectively."), + new object[] { typeof(TelemetryHostedServiceHelper).Assembly.GetName().Name! }); + + assemblyBuilder.SetCustomAttribute(ignoresAccessChecksTo); + + var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name!); + + var typeBuilder = moduleBuilder.DefineType("OpenTelemetry.Extensions.Hosting.Implementation.TelemetryHostedService", TypeAttributes.NotPublic); + + typeBuilder.AddInterfaceImplementation(iHostedServiceType); + + var hostedServiceImplementationField = typeBuilder.DefineField( + "telemetryHostedServiceHelper", + typeof(TelemetryHostedServiceHelper), + FieldAttributes.Private | FieldAttributes.InitOnly); + + var constructor = typeBuilder.DefineConstructor( + MethodAttributes.Public, + CallingConventions.Standard, + new Type[] { typeof(TelemetryHostedServiceHelper) }); + + var constructorGenerator = constructor.GetILGenerator(); + + constructorGenerator.Emit(OpCodes.Ldarg_0); + constructorGenerator.Emit( + OpCodes.Call, + typeof(object).GetConstructor(Type.EmptyTypes) ?? throw new InvalidOperationException("Object constructor could not be found reflectively.")); + constructorGenerator.Emit(OpCodes.Ldarg_0); + constructorGenerator.Emit(OpCodes.Ldarg_1); + constructorGenerator.Emit(OpCodes.Stfld, hostedServiceImplementationField); + constructorGenerator.Emit(OpCodes.Ret); + + var startAsyncMethodBuilder = typeBuilder.DefineMethod( + "StartAsync", + MethodAttributes.Public | MethodAttributes.Virtual, + typeof(Task), + new Type[] { typeof(CancellationToken) }); + + var startAsyncMethodGenerator = startAsyncMethodBuilder.GetILGenerator(); + + startAsyncMethodGenerator.Emit(OpCodes.Ldarg_0); + startAsyncMethodGenerator.Emit(OpCodes.Ldfld, hostedServiceImplementationField); + startAsyncMethodGenerator.Emit( + OpCodes.Call, + typeof(TelemetryHostedServiceHelper).GetMethod(nameof(TelemetryHostedServiceHelper.Start)) ?? throw new InvalidOperationException($"{nameof(TelemetryHostedServiceHelper)}.{nameof(TelemetryHostedServiceHelper.Start)} could not be found reflectively.")); + startAsyncMethodGenerator.Emit(OpCodes.Call, getCompletedTaskMethod); + startAsyncMethodGenerator.Emit(OpCodes.Ret); + + typeBuilder.DefineMethodOverride( + startAsyncMethodBuilder, + iHostedServiceType.GetMethod("StartAsync") ?? throw new InvalidOperationException("IHostedService.StartAsync could not be found reflectively.")); + + var stopAsyncMethodBuilder = typeBuilder.DefineMethod( + "StopAsync", + MethodAttributes.Public | MethodAttributes.Virtual, + typeof(Task), + new Type[] { typeof(CancellationToken) }); + + var stopAsyncMethodGenerator = stopAsyncMethodBuilder.GetILGenerator(); + + stopAsyncMethodGenerator.Emit(OpCodes.Call, getCompletedTaskMethod); + stopAsyncMethodGenerator.Emit(OpCodes.Ret); + + typeBuilder.DefineMethodOverride( + stopAsyncMethodBuilder, + iHostedServiceType.GetMethod("StopAsync") ?? throw new InvalidOperationException("IHostedService.StopAsync could not be found reflectively.")); + +#if !NETSTANDARD2_0 + return typeBuilder.CreateType() +#else + return typeBuilder.CreateTypeInfo() +#endif + ?? throw new InvalidOperationException("IHostedService implementation could not be created dynamically."); + } + + private sealed class TelemetryHostedServiceHelper + { + private readonly IServiceProvider serviceProvider; + + public TelemetryHostedServiceHelper(IServiceProvider serviceProvider) + { + Debug.Assert(serviceProvider != null, "serviceProvider was null"); + + this.serviceProvider = serviceProvider!; + } + + public void Start() + { + var serviceProvider = this.serviceProvider; + + var meterProvider = serviceProvider.GetService(); + if (meterProvider == null) + { + OpenTelemetrySdkEventSource.Log.MeterProviderNotRegistered(); + } + + var tracerProvider = serviceProvider.GetService(); + if (tracerProvider == null) + { + OpenTelemetrySdkEventSource.Log.TracerProviderNotRegistered(); + } + } + } +} diff --git a/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs b/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs index 848737de80f..68a756d0d7b 100644 --- a/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs +++ b/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs @@ -175,6 +175,15 @@ public void DroppedExportProcessorItems(string exportProcessorName, string expor } } + [NonEvent] + public void HostedServiceRegistrationFailure(Exception ex) + { + if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) + { + this.HostedServiceRegistrationFailure(ex.ToInvariantString()); + } + } + [Event(1, Message = "Span processor queue size reached maximum. Throttling spans.", Level = EventLevel.Warning)] public void SpanProcessorQueueIsExhausted() { @@ -415,6 +424,36 @@ public void InvalidEnvironmentVariable(string key, string value) this.WriteEvent(47, key, value); } + [Event(48, Message = "OpenTelemetry IHostedService registered in application services.", Level = EventLevel.Informational)] + public void HostedServiceRegistered() + { + this.WriteEvent(48); + } + + [Event(49, Message = "OpenTelemetry IHostedService application services registration skipped. Reason: '{0}'", Level = EventLevel.Warning)] + public void HostedServiceRegistrationSkipped(string reason) + { + this.WriteEvent(49, reason); + } + + [Event(50, Message = "OpenTelemetry IHostedService could not be registered in application services. Error: '{0}'", Level = EventLevel.Error)] + public void HostedServiceRegistrationFailure(string error) + { + this.WriteEvent(50, error); + } + + [Event(51, Message = "OpenTelemetry TracerProvider was not found in application services. Tracing will remain disabled.", Level = EventLevel.Warning)] + public void TracerProviderNotRegistered() + { + this.WriteEvent(51); + } + + [Event(52, Message = "OpenTelemetry MeterProvider was not found in application services. Metrics will remain disabled.", Level = EventLevel.Warning)] + public void MeterProviderNotRegistered() + { + this.WriteEvent(52); + } + #if DEBUG public class OpenTelemetryEventListener : EventListener { diff --git a/src/OpenTelemetry/Internal/Shims/IgnoresAccessChecksToAttribute.cs b/src/OpenTelemetry/Internal/Shims/IgnoresAccessChecksToAttribute.cs new file mode 100644 index 00000000000..d4fee58cfbd --- /dev/null +++ b/src/OpenTelemetry/Internal/Shims/IgnoresAccessChecksToAttribute.cs @@ -0,0 +1,33 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#nullable enable + +namespace System.Runtime.CompilerServices; + +/// +/// IgnoresAccessChecksToAttribute tells the runtime to bypass visibility checks +/// to some assembly. See: . +/// +[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] +internal sealed class IgnoresAccessChecksToAttribute : Attribute +{ + public IgnoresAccessChecksToAttribute(string assemblyName) + => this.AssemblyName = assemblyName; + + public string AssemblyName { get; } +} diff --git a/src/OpenTelemetry/OpenTelemetry.csproj b/src/OpenTelemetry/OpenTelemetry.csproj index 65e87e8f969..5decd71ff36 100644 --- a/src/OpenTelemetry/OpenTelemetry.csproj +++ b/src/OpenTelemetry/OpenTelemetry.csproj @@ -20,6 +20,7 @@ + diff --git a/src/OpenTelemetry/OpenTelemetryBuilder.cs b/src/OpenTelemetry/OpenTelemetryBuilder.cs index f817e0bfe68..216343c9f60 100644 --- a/src/OpenTelemetry/OpenTelemetryBuilder.cs +++ b/src/OpenTelemetry/OpenTelemetryBuilder.cs @@ -74,18 +74,9 @@ public OpenTelemetryBuilder ConfigureResource( /// Adds metric services into the builder. /// /// - /// Notes: - /// - /// A will not be created automatically - /// using this method. To begin collecting metrics either use the - /// OpenTelemetryBuilder.StartWithHost extension in the - /// OpenTelemetry.Extensions.Hosting package or access the through the application . - /// This is safe to be called multiple times and by library authors. + /// Note: This is safe to be called multiple times and by library authors. /// Only a single will be created for a given - /// . - /// + /// . /// /// The supplied for chaining /// calls. @@ -115,18 +106,9 @@ public OpenTelemetryBuilder WithMetrics(Action configure) /// Adds tracing services into the builder. /// /// - /// Notes: - /// - /// A will not be created automatically - /// using this method. To begin collecting traces either use the - /// OpenTelemetryBuilder.StartWithHost extension in the - /// OpenTelemetry.Extensions.Hosting package or access the through the application . - /// This is safe to be called multiple times and by library authors. + /// Note: This is safe to be called multiple times and by library authors. /// Only a single will be created for a given - /// . - /// + /// . /// /// The supplied for chaining /// calls. diff --git a/src/OpenTelemetry/OpenTelemetryServiceCollectionExtensions.cs b/src/OpenTelemetry/OpenTelemetryServiceCollectionExtensions.cs index 2541a85f7bb..0fc0f408976 100644 --- a/src/OpenTelemetry/OpenTelemetryServiceCollectionExtensions.cs +++ b/src/OpenTelemetry/OpenTelemetryServiceCollectionExtensions.cs @@ -17,6 +17,7 @@ #nullable enable using Microsoft.Extensions.DependencyInjection; +using OpenTelemetry.Internal; using OpenTelemetry.Metrics; using OpenTelemetry.Trace; @@ -35,12 +36,12 @@ public static class OpenTelemetryServiceCollectionExtensions /// Notes: /// /// A and/or - /// will not be created automatically using this method. To begin collecting - /// traces and/or metrics either use the - /// OpenTelemetryBuilder.StartWithHost extension in the - /// OpenTelemetry.Extensions.Hosting package or access the and/or through the - /// application . + /// will be created automatically using this method if a host supporting + /// Microsoft.Extensions.Hosting.IHostedService is detected at + /// runtime. To begin collecting traces and/or metrics when hosting + /// extensions are not being used access the + /// and/or through the application . /// This is safe to be called multiple times and by library authors. /// Only a single and/or will be created for a given The supplied for chaining /// calls. public static OpenTelemetryBuilder AddOpenTelemetry(this IServiceCollection services) - => new(services); + { + HostingHelper.AddOpenTelemetryHostedServiceIntoServiceCollection(services); + + return new(services); + } } diff --git a/test/OpenTelemetry.Extensions.Hosting.Tests/EventSourceTest.cs b/test/OpenTelemetry.Extensions.Hosting.Tests/EventSourceTest.cs deleted file mode 100644 index 64b4ab634bf..00000000000 --- a/test/OpenTelemetry.Extensions.Hosting.Tests/EventSourceTest.cs +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using OpenTelemetry.Extensions.Hosting.Implementation; -using OpenTelemetry.Tests; -using Xunit; - -namespace OpenTelemetry.Extensions.Hosting.Tests -{ - public class EventSourceTest - { - [Fact] - public void EventSourceTest_HostingExtensionsEventSource() - { - EventSourceTestHelper.MethodsAreImplementedConsistentlyWithTheirAttributes(HostingExtensionsEventSource.Log); - } - } -} diff --git a/test/OpenTelemetry.Extensions.Hosting.Tests/HostingMeterExtensionTests.cs b/test/OpenTelemetry.Extensions.Hosting.Tests/HostingMeterExtensionTests.cs deleted file mode 100644 index fe267d558a5..00000000000 --- a/test/OpenTelemetry.Extensions.Hosting.Tests/HostingMeterExtensionTests.cs +++ /dev/null @@ -1,109 +0,0 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using OpenTelemetry.Metrics; -using Xunit; - -namespace OpenTelemetry.Extensions.Hosting.Tests -{ - public class HostingMeterExtensionTests - { - [Fact] - public async Task AddOpenTelemetry_StartWithHost_CreationAndDisposal() - { - var callbackRun = false; - - var builder = new HostBuilder().ConfigureServices(services => - { - services.AddOpenTelemetry() - .WithMetrics(builder => builder - .AddInstrumentation(() => - { - callbackRun = true; - return new object(); - })) - .StartWithHost(); - }); - - var host = builder.Build(); - - Assert.False(callbackRun); - - await host.StartAsync().ConfigureAwait(false); - - Assert.True(callbackRun); - - await host.StopAsync().ConfigureAwait(false); - - Assert.True(callbackRun); - - host.Dispose(); - - Assert.True(callbackRun); - } - - [Fact] - public async Task AddOpenTelemetry_StartWithHost_HostConfigurationHonoredTest() - { - bool configureBuilderCalled = false; - - var builder = new HostBuilder() - .ConfigureAppConfiguration(builder => - { - builder.AddInMemoryCollection(new Dictionary - { - ["TEST_KEY"] = "TEST_KEY_VALUE", - }); - }) - .ConfigureServices(services => - { - services.AddOpenTelemetry() - .WithMetrics(builder => - { - if (builder is IDeferredMeterProviderBuilder deferredMeterProviderBuilder) - { - deferredMeterProviderBuilder.Configure((sp, builder) => - { - configureBuilderCalled = true; - - var configuration = sp.GetRequiredService(); - - var testKeyValue = configuration.GetValue("TEST_KEY", null); - - Assert.Equal("TEST_KEY_VALUE", testKeyValue); - }); - } - }) - .StartWithHost(); - }); - - var host = builder.Build(); - - Assert.False(configureBuilderCalled); - - await host.StartAsync().ConfigureAwait(false); - - Assert.True(configureBuilderCalled); - - await host.StopAsync().ConfigureAwait(false); - - host.Dispose(); - } - } -} diff --git a/test/OpenTelemetry.Extensions.Hosting.Tests/HostingTracerExtensionTests.cs b/test/OpenTelemetry.Extensions.Hosting.Tests/HostingTracerExtensionTests.cs deleted file mode 100644 index 1122a9a8e86..00000000000 --- a/test/OpenTelemetry.Extensions.Hosting.Tests/HostingTracerExtensionTests.cs +++ /dev/null @@ -1,109 +0,0 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using OpenTelemetry.Trace; -using Xunit; - -namespace OpenTelemetry.Extensions.Hosting.Tests -{ - public class HostingTracerExtensionTests - { - [Fact] - public async Task AddOpenTelemetry_StartWithHost_CreationAndDisposal() - { - var callbackRun = false; - - var builder = new HostBuilder().ConfigureServices(services => - { - services.AddOpenTelemetry() - .WithTracing(builder => builder - .AddInstrumentation(() => - { - callbackRun = true; - return new object(); - })) - .StartWithHost(); - }); - - var host = builder.Build(); - - Assert.False(callbackRun); - - await host.StartAsync().ConfigureAwait(false); - - Assert.True(callbackRun); - - await host.StopAsync().ConfigureAwait(false); - - Assert.True(callbackRun); - - host.Dispose(); - - Assert.True(callbackRun); - } - - [Fact] - public async Task AddOpenTelemetry_StartWithHost_HostConfigurationHonoredTest() - { - bool configureBuilderCalled = false; - - var builder = new HostBuilder() - .ConfigureAppConfiguration(builder => - { - builder.AddInMemoryCollection(new Dictionary - { - ["TEST_KEY"] = "TEST_KEY_VALUE", - }); - }) - .ConfigureServices(services => - { - services.AddOpenTelemetry() - .WithTracing(builder => - { - if (builder is IDeferredTracerProviderBuilder deferredTracerProviderBuilder) - { - deferredTracerProviderBuilder.Configure((sp, builder) => - { - configureBuilderCalled = true; - - var configuration = sp.GetRequiredService(); - - var testKeyValue = configuration.GetValue("TEST_KEY", null); - - Assert.Equal("TEST_KEY_VALUE", testKeyValue); - }); - } - }) - .StartWithHost(); - }); - - var host = builder.Build(); - - Assert.False(configureBuilderCalled); - - await host.StartAsync().ConfigureAwait(false); - - Assert.True(configureBuilderCalled); - - await host.StopAsync().ConfigureAwait(false); - - host.Dispose(); - } - } -} diff --git a/test/OpenTelemetry.Extensions.Hosting.Tests/InMemoryExporterMetricsExtensionsTests.cs b/test/OpenTelemetry.Extensions.Hosting.Tests/InMemoryExporterMetricsExtensionsTests.cs index f32a85aedf3..cb8d05ab9ad 100644 --- a/test/OpenTelemetry.Extensions.Hosting.Tests/InMemoryExporterMetricsExtensionsTests.cs +++ b/test/OpenTelemetry.Extensions.Hosting.Tests/InMemoryExporterMetricsExtensionsTests.cs @@ -16,11 +16,8 @@ #if NET6_0_OR_GREATER -using System; -using System.Collections.Generic; using System.Diagnostics.Metrics; using System.Net; -using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -89,7 +86,7 @@ private static async Task RunMetricsTest(Action configure, using var host = await new HostBuilder() .ConfigureWebHost(webBuilder => webBuilder .UseTestServer() - .ConfigureServices(services => services.AddOpenTelemetry().WithMetrics(configure).StartWithHost()) + .ConfigureServices(services => services.AddOpenTelemetry().WithMetrics(configure)) .Configure(app => app.Run(httpContext => { testAction.Invoke(); diff --git a/test/OpenTelemetry.Extensions.Hosting.Tests/TelemetryHostedServiceTests.cs b/test/OpenTelemetry.Extensions.Hosting.Tests/TelemetryHostedServiceTests.cs deleted file mode 100644 index 3900a74028f..00000000000 --- a/test/OpenTelemetry.Extensions.Hosting.Tests/TelemetryHostedServiceTests.cs +++ /dev/null @@ -1,87 +0,0 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using Microsoft.Extensions.Hosting; -using OpenTelemetry.Trace; -using Xunit; - -namespace OpenTelemetry.Extensions.Hosting.Tests; - -public class TelemetryHostedServiceTests -{ - [Fact] - public async Task StartWithoutProvidersDoesNotThrow() - { - var builder = new HostBuilder().ConfigureServices(services => - { - services.AddOpenTelemetry() - .StartWithHost(); - }); - - var host = builder.Build(); - - await host.StartAsync().ConfigureAwait(false); - - await host.StopAsync().ConfigureAwait(false); - } - - [Fact] - public async Task StartWithExceptionsThrows() - { - bool expectedInnerExceptionThrown = false; - - var builder = new HostBuilder().ConfigureServices(services => - { - services.AddOpenTelemetry() - .WithTracing(builder => - { - if (builder is IDeferredTracerProviderBuilder deferredTracerProviderBuilder) - { - deferredTracerProviderBuilder.Configure((sp, sdkBuilder) => - { - try - { - // Note: This throws because services cannot be - // registered after IServiceProvider has been - // created. - sdkBuilder.SetSampler(); - } - catch (NotSupportedException) - { - expectedInnerExceptionThrown = true; - throw; - } - }); - } - }) - .StartWithHost(); - }); - - var host = builder.Build(); - - await Assert.ThrowsAsync(() => host.StartAsync()).ConfigureAwait(false); - - await host.StopAsync().ConfigureAwait(false); - - Assert.True(expectedInnerExceptionThrown); - } - - private sealed class MySampler : Sampler - { - public override SamplingResult ShouldSample(in SamplingParameters samplingParameters) - => new SamplingResult(SamplingDecision.RecordAndSample); - } -} diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs index cc7ffcee3c4..cb51bd437c4 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs @@ -653,8 +653,7 @@ public async Task ActivitiesStartedInMiddlewareBySettingHostActivityToNullShould .WithTracing(builder => builder .AddAspNetCoreInstrumentation() .AddSource(activitySourceName) - .AddInMemoryExporter(exportedItems)) - .StartWithHost(); + .AddInMemoryExporter(exportedItems)); }); builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders()); }) @@ -692,8 +691,7 @@ void ConfigureTestServices(IServiceCollection services) services.AddOpenTelemetry() .WithTracing(builder => builder .AddAspNetCoreInstrumentation() - .AddInMemoryExporter(exportedItems)) - .StartWithHost(); + .AddInMemoryExporter(exportedItems)); // Register ActivitySource here so that it will be used // by ASP.NET Core to create activities diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/DependencyInjectionConfigTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/DependencyInjectionConfigTests.cs index 16136110509..3631ca9a1ef 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/DependencyInjectionConfigTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/DependencyInjectionConfigTests.cs @@ -46,8 +46,7 @@ void ConfigureTestServices(IServiceCollection services) { services.AddOpenTelemetry() .WithTracing(builder => builder - .AddAspNetCoreInstrumentation(name, configureAspNetCoreInstrumentationOptions: null)) - .StartWithHost(); + .AddAspNetCoreInstrumentation(name, configureAspNetCoreInstrumentationOptions: null)); services.Configure(name, options => { @@ -78,8 +77,7 @@ void ConfigureTestServices(IServiceCollection services) { services.AddOpenTelemetry() .WithMetrics(builder => builder - .AddAspNetCoreInstrumentation(name, configureAspNetCoreInstrumentationOptions: null)) - .StartWithHost(); + .AddAspNetCoreInstrumentation(name, configureAspNetCoreInstrumentationOptions: null)); services.Configure(name, options => { diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs index 971920474e9..8500ac00a22 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs @@ -63,8 +63,7 @@ public async Task SuccessfulTemplateControllerCallGeneratesASpan( services.AddOpenTelemetry() .WithTracing(builder => builder .AddAspNetCoreInstrumentation(options => options.RecordException = recordException) - .AddInMemoryExporter(exportedItems)) - .StartWithHost(); + .AddInMemoryExporter(exportedItems)); }); builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders()); }) diff --git a/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj b/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj index c87707d50eb..c2db74d6d20 100644 --- a/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj +++ b/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj @@ -16,7 +16,8 @@ - + + diff --git a/test/OpenTelemetry.Tests/OpenTelemetryServiceCollectionExtensionsTests.cs b/test/OpenTelemetry.Tests/OpenTelemetryServiceCollectionExtensionsTests.cs new file mode 100644 index 00000000000..fa425c2d470 --- /dev/null +++ b/test/OpenTelemetry.Tests/OpenTelemetryServiceCollectionExtensionsTests.cs @@ -0,0 +1,264 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#nullable enable + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; +using Xunit; + +namespace OpenTelemetry.Tests; + +public class OpenTelemetryServiceCollectionExtensionsTests +{ + [Fact] + public void AddOpenTelemetry_HostedService_Registered() + { + var services = new ServiceCollection(); + + services.AddOpenTelemetry(); + + using var serviceProvider = services.BuildServiceProvider(); + + var hostedServices = serviceProvider.GetServices(); + + Assert.NotEmpty(hostedServices); + } + + [Fact] + public async Task AddOpenTelemetry_HostedService_WithoutProvidersDoesNotThrow() + { + var builder = new HostBuilder().ConfigureServices(services => + { + services.AddOpenTelemetry(); + }); + + var host = builder.Build(); + + await host.StartAsync().ConfigureAwait(false); + + await host.StopAsync().ConfigureAwait(false); + } + + [Fact] + public async Task AddOpenTelemetry_HostedService_StartWithExceptionsThrows() + { + bool expectedInnerExceptionThrown = false; + + var builder = new HostBuilder().ConfigureServices(services => + { + services.AddOpenTelemetry() + .WithTracing(builder => + { + if (builder is IDeferredTracerProviderBuilder deferredTracerProviderBuilder) + { + deferredTracerProviderBuilder.Configure((sp, sdkBuilder) => + { + try + { + // Note: This throws because services cannot be + // registered after IServiceProvider has been + // created. + sdkBuilder.SetSampler(); + } + catch (NotSupportedException) + { + expectedInnerExceptionThrown = true; + throw; + } + }); + } + }); + }); + + var host = builder.Build(); + + await Assert.ThrowsAsync(() => host.StartAsync()).ConfigureAwait(false); + + await host.StopAsync().ConfigureAwait(false); + + Assert.True(expectedInnerExceptionThrown); + } + + [Fact] + public async Task AddOpenTelemetry_WithMetrics_CreationAndDisposal() + { + var callbackRun = false; + + var builder = new HostBuilder().ConfigureServices(services => + { + services.AddOpenTelemetry() + .WithMetrics(builder => builder + .AddInstrumentation(() => + { + callbackRun = true; + return new object(); + })); + }); + + var host = builder.Build(); + + Assert.False(callbackRun); + + await host.StartAsync().ConfigureAwait(false); + + Assert.True(callbackRun); + + await host.StopAsync().ConfigureAwait(false); + + Assert.True(callbackRun); + + host.Dispose(); + + Assert.True(callbackRun); + } + + [Fact] + public async Task AddOpenTelemetry_WithMetrics_HostConfigurationHonoredTest() + { + bool configureBuilderCalled = false; + + var builder = new HostBuilder() + .ConfigureAppConfiguration(builder => + { + builder.AddInMemoryCollection(new Dictionary + { + ["TEST_KEY"] = "TEST_KEY_VALUE", + }); + }) + .ConfigureServices(services => + { + services.AddOpenTelemetry() + .WithMetrics(builder => + { + if (builder is IDeferredMeterProviderBuilder deferredMeterProviderBuilder) + { + deferredMeterProviderBuilder.Configure((sp, builder) => + { + configureBuilderCalled = true; + + var configuration = sp.GetRequiredService(); + + var testKeyValue = configuration.GetValue("TEST_KEY", null); + + Assert.Equal("TEST_KEY_VALUE", testKeyValue); + }); + } + }); + }); + + var host = builder.Build(); + + Assert.False(configureBuilderCalled); + + await host.StartAsync().ConfigureAwait(false); + + Assert.True(configureBuilderCalled); + + await host.StopAsync().ConfigureAwait(false); + + host.Dispose(); + } + + [Fact] + public async Task AddOpenTelemetry_WithTracing_CreationAndDisposal() + { + var callbackRun = false; + + var builder = new HostBuilder().ConfigureServices(services => + { + services.AddOpenTelemetry() + .WithTracing(builder => builder + .AddInstrumentation(() => + { + callbackRun = true; + return new object(); + })); + }); + + var host = builder.Build(); + + Assert.False(callbackRun); + + await host.StartAsync().ConfigureAwait(false); + + Assert.True(callbackRun); + + await host.StopAsync().ConfigureAwait(false); + + Assert.True(callbackRun); + + host.Dispose(); + + Assert.True(callbackRun); + } + + [Fact] + public async Task AddOpenTelemetry_WithTracing_HostConfigurationHonoredTest() + { + bool configureBuilderCalled = false; + + var builder = new HostBuilder() + .ConfigureAppConfiguration(builder => + { + builder.AddInMemoryCollection(new Dictionary + { + ["TEST_KEY"] = "TEST_KEY_VALUE", + }); + }) + .ConfigureServices(services => + { + services.AddOpenTelemetry() + .WithTracing(builder => + { + if (builder is IDeferredTracerProviderBuilder deferredTracerProviderBuilder) + { + deferredTracerProviderBuilder.Configure((sp, builder) => + { + configureBuilderCalled = true; + + var configuration = sp.GetRequiredService(); + + var testKeyValue = configuration.GetValue("TEST_KEY", null); + + Assert.Equal("TEST_KEY_VALUE", testKeyValue); + }); + } + }); + }); + + var host = builder.Build(); + + Assert.False(configureBuilderCalled); + + await host.StartAsync().ConfigureAwait(false); + + Assert.True(configureBuilderCalled); + + await host.StopAsync().ConfigureAwait(false); + + host.Dispose(); + } + + private sealed class MySampler : Sampler + { + public override SamplingResult ShouldSample(in SamplingParameters samplingParameters) + => new SamplingResult(SamplingDecision.RecordAndSample); + } +}