From 94f67d8a8cd7b60ed309d3bb632b373f3415dc38 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Thu, 9 Feb 2023 10:14:12 -0800 Subject: [PATCH] Revert "[sdk] Use reflection and dynamic types to start providers (#4151)" (#4173) This reverts commit b549e12148aa76e447a76c5c77701727e30a6c7d. --- 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, 636 insertions(+), 663 deletions(-) create mode 100644 src/OpenTelemetry.Extensions.Hosting/Implementation/HostingExtensionsEventSource.cs create mode 100644 src/OpenTelemetry.Extensions.Hosting/Implementation/TelemetryHostedService.cs create mode 100644 src/OpenTelemetry.Extensions.Hosting/OpenTelemetryBuilderHostingExtensions.cs delete mode 100644 src/OpenTelemetry/Internal/HostingHelper.cs delete mode 100644 src/OpenTelemetry/Internal/Shims/IgnoresAccessChecksToAttribute.cs create mode 100644 test/OpenTelemetry.Extensions.Hosting.Tests/EventSourceTest.cs create mode 100644 test/OpenTelemetry.Extensions.Hosting.Tests/HostingMeterExtensionTests.cs create mode 100644 test/OpenTelemetry.Extensions.Hosting.Tests/HostingTracerExtensionTests.cs create mode 100644 test/OpenTelemetry.Extensions.Hosting.Tests/TelemetryHostedServiceTests.cs delete mode 100644 test/OpenTelemetry.Tests/OpenTelemetryServiceCollectionExtensionsTests.cs diff --git a/build/Common.props b/build/Common.props index a1d08f1e37d..d47bf985f5e 100644 --- a/build/Common.props +++ b/build/Common.props @@ -37,8 +37,7 @@ [17.4.1] [3.1.0,) $(MicrosoftExtensionsDependencyInjectionPkgVer) - [2.1.0,) - $(MicrosoftExtensionsHostingPkgVer) + [2.1.0,) [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 a2914077175..2feba0bd229 100644 --- a/docs/trace/getting-started-aspnetcore/Program.cs +++ b/docs/trace/getting-started-aspnetcore/Program.cs @@ -27,7 +27,8 @@ .AddService(serviceName: "OTel.NET Getting Started")) .WithTracing(builder => builder .AddAspNetCoreInstrumentation() - .AddConsoleExporter()); + .AddConsoleExporter()) + .StartWithHost(); 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 321fbcd608b..1956f427004 100644 --- a/docs/trace/getting-started-aspnetcore/getting-started-aspnetcore.csproj +++ b/docs/trace/getting-started-aspnetcore/getting-started-aspnetcore.csproj @@ -8,6 +8,7 @@ + diff --git a/examples/AspNetCore/Examples.AspNetCore.csproj b/examples/AspNetCore/Examples.AspNetCore.csproj index 7e29680340f..b8b09bcd9d4 100644 --- a/examples/AspNetCore/Examples.AspNetCore.csproj +++ b/examples/AspNetCore/Examples.AspNetCore.csproj @@ -10,6 +10,7 @@ + diff --git a/examples/AspNetCore/Program.cs b/examples/AspNetCore/Program.cs index 80527159685..53245406d57 100644 --- a/examples/AspNetCore/Program.cs +++ b/examples/AspNetCore/Program.cs @@ -44,7 +44,8 @@ // for manual instrumentation appBuilder.Services.AddSingleton(); -// Configure OpenTelemetry tracing & metrics. +// Configure OpenTelemetry tracing & metrics with auto-start using the +// StartWithHost extension from OpenTelemetry.Extensions.Hosting. appBuilder.Services.AddOpenTelemetry() .ConfigureResource(configureResource) .WithTracing(builder => @@ -126,7 +127,8 @@ 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 7123737717a..2a9028e8650 100644 --- a/examples/GrpcService/Examples.GrpcService.csproj +++ b/examples/GrpcService/Examples.GrpcService.csproj @@ -16,6 +16,7 @@ + diff --git a/examples/GrpcService/Startup.cs b/examples/GrpcService/Startup.cs index 32d595ab63d..2c875eae04b 100644 --- a/examples/GrpcService/Startup.cs +++ b/examples/GrpcService/Startup.cs @@ -61,7 +61,8 @@ 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 d3966c26aa2..90e66783302 100644 --- a/examples/MicroserviceExample/WebApi/Startup.cs +++ b/examples/MicroserviceExample/WebApi/Startup.cs @@ -43,7 +43,8 @@ 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 5e1695e9c86..876450b7176 100644 --- a/examples/MicroserviceExample/WebApi/WebApi.csproj +++ b/examples/MicroserviceExample/WebApi/WebApi.csproj @@ -10,6 +10,7 @@ + diff --git a/examples/MicroserviceExample/WorkerService/Program.cs b/examples/MicroserviceExample/WorkerService/Program.cs index 75ad1223e16..665265cedd4 100644 --- a/examples/MicroserviceExample/WorkerService/Program.cs +++ b/examples/MicroserviceExample/WorkerService/Program.cs @@ -42,7 +42,8 @@ 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 29d72978cd5..926e55dd6fd 100644 --- a/examples/MicroserviceExample/WorkerService/WorkerService.csproj +++ b/examples/MicroserviceExample/WorkerService/WorkerService.csproj @@ -11,6 +11,7 @@ + diff --git a/src/OpenTelemetry.Exporter.Jaeger/README.md b/src/OpenTelemetry.Exporter.Jaeger/README.md index 643d9874819..411e3d40283 100644 --- a/src/OpenTelemetry.Exporter.Jaeger/README.md +++ b/src/OpenTelemetry.Exporter.Jaeger/README.md @@ -98,7 +98,8 @@ 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 177f9639443..369b6e1574e 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md @@ -135,7 +135,8 @@ 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 e6676934961..6bcd236b5bb 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/README.md +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/README.md @@ -28,7 +28,8 @@ dotnet add package --prerelease OpenTelemetry.Exporter.Prometheus.AspNetCore ```csharp services.AddOpenTelemetry() .WithMetrics(builder => builder - .AddPrometheusExporter()); + .AddPrometheusExporter()) + .StartWithHost(); ``` * Or configure directly: diff --git a/src/OpenTelemetry.Exporter.Zipkin/README.md b/src/OpenTelemetry.Exporter.Zipkin/README.md index aa277b7fdc2..20d794c051d 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/README.md +++ b/src/OpenTelemetry.Exporter.Zipkin/README.md @@ -87,7 +87,8 @@ 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 63141a61d05..dbee0f5b37d 100644 --- a/src/OpenTelemetry.Extensions.Hosting/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Extensions.Hosting/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,5 +1,6 @@ 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 @@ -7,5 +8,6 @@ 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 b31b6c4ccb3..8e7983ec511 100644 --- a/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md +++ b/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md @@ -2,12 +2,6 @@ ## 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 new file mode 100644 index 00000000000..f6f86038cfe --- /dev/null +++ b/src/OpenTelemetry.Extensions.Hosting/Implementation/HostingExtensionsEventSource.cs @@ -0,0 +1,41 @@ +// +// 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 new file mode 100644 index 00000000000..78e50395b59 --- /dev/null +++ b/src/OpenTelemetry.Extensions.Hosting/Implementation/TelemetryHostedService.cs @@ -0,0 +1,64 @@ +// +// 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 new file mode 100644 index 00000000000..646ca614f44 --- /dev/null +++ b/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryBuilderHostingExtensions.cs @@ -0,0 +1,54 @@ +// +// 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 ce238c273f7..64e837fbf16 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) pattern instead. This method will be removed in a future version.")] + [Obsolete("Use the AddOpenTelemetry().WithTracing(configure).StartWithHost() 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) pattern instead. This method will be removed in a future version.")] + [Obsolete("Use the AddOpenTelemetry().WithTracing(configure).StartWithHost() pattern instead. This method will be removed in a future version.")] public static IServiceCollection AddOpenTelemetryTracing(this IServiceCollection services, Action configure) { - services.AddOpenTelemetry().WithTracing(configure); + services.AddOpenTelemetry().WithTracing(configure).StartWithHost(); return services; } @@ -100,7 +100,7 @@ public static IServiceCollection AddOpenTelemetryTracing(this IServiceCollection /// . /// Supplied for chaining /// calls. - [Obsolete("Use the AddOpenTelemetry().WithMetrics(configure) pattern instead. This method will be removed in a future version.")] + [Obsolete("Use the AddOpenTelemetry().WithMetrics(configure).StartWithHost() 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) pattern instead. This method will be removed in a future version.")] + [Obsolete("Use the AddOpenTelemetry().WithMetrics(configure).StartWithHost() pattern instead. This method will be removed in a future version.")] public static IServiceCollection AddOpenTelemetryMetrics(this IServiceCollection services, Action configure) { - services.AddOpenTelemetry().WithMetrics(configure); + services.AddOpenTelemetry().WithMetrics(configure).StartWithHost(); return services; } diff --git a/src/OpenTelemetry.Extensions.Hosting/README.md b/src/OpenTelemetry.Extensions.Hosting/README.md index 59014925824..a1703580f07 100644 --- a/src/OpenTelemetry.Extensions.Hosting/README.md +++ b/src/OpenTelemetry.Extensions.Hosting/README.md @@ -21,6 +21,17 @@ 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 @@ -61,9 +72,10 @@ using OpenTelemetry.Trace; var appBuilder = WebApplication.CreateBuilder(args); -appBuilder.Services.AddOpenTelemetryTracing(builder => builder.AddConsoleExporter()); - -appBuilder.Services.AddOpenTelemetryMetrics(builder => builder.AddConsoleExporter()); +appBuilder.Services.AddOpenTelemetry() + .WithTracing(builder => builder.AddConsoleExporter()) + .WithMetrics(builder => builder.AddConsoleExporter()) + .StartWithHost(); var app = appBuilder.Build(); diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/README.md b/src/OpenTelemetry.Instrumentation.AspNetCore/README.md index fd535b1682c..59e489b8676 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/README.md +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/README.md @@ -58,7 +58,8 @@ public void ConfigureServices(IServiceCollection services) services.AddOpenTelemetry() .WithTracing(builder => builder .AddAspNetCoreInstrumentation() - .AddJaegerExporter()); + .AddJaegerExporter()) + .StartWithHost(); } ``` @@ -87,7 +88,8 @@ services.Configure(options => services.AddOpenTelemetry() .WithTracing(builder => builder .AddAspNetCoreInstrumentation() - .AddJaegerExporter()); + .AddJaegerExporter()) + .StartWithHost(); ``` ### Filter @@ -110,7 +112,8 @@ services.AddOpenTelemetry() // only collect telemetry about HTTP GET requests return httpContext.Request.Method.Equals("GET"); }) - .AddJaegerExporter()); + .AddJaegerExporter()) + .StartWithHost(); ``` It is important to note that this `Filter` option is specific to this @@ -147,7 +150,8 @@ 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 bfb6dfbb701..62bb0853aea 100644 --- a/src/OpenTelemetry.Instrumentation.GrpcNetClient/README.md +++ b/src/OpenTelemetry.Instrumentation.GrpcNetClient/README.md @@ -120,7 +120,8 @@ 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 2b03fe7dcaa..9940288268f 100644 --- a/src/OpenTelemetry/CHANGELOG.md +++ b/src/OpenTelemetry/CHANGELOG.md @@ -5,10 +5,6 @@ * 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 02928d154d5..5b97e90ce77 100644 --- a/src/OpenTelemetry/Internal/EnvironmentVariables/EnvironmentVariablesExtensions.cs +++ b/src/OpenTelemetry/Internal/EnvironmentVariables/EnvironmentVariablesExtensions.cs @@ -4,24 +4,49 @@ #nullable enable -using Microsoft.Extensions.Configuration; +using System; using Microsoft.Extensions.Configuration.EnvironmentVariables; -namespace OpenTelemetry.Internal; - -/// -/// Extension methods for registering with . -/// -internal static class EnvironmentVariablesExtensions +namespace Microsoft.Extensions.Configuration { /// - /// Adds an that reads configuration values from environment variables. + /// Extension methods for registering with . /// - /// The to add to. - /// The . - public static IConfigurationBuilder AddEnvironmentVariables(this IConfigurationBuilder configurationBuilder) + internal static class EnvironmentVariablesExtensions { - configurationBuilder.Add(new EnvironmentVariablesConfigurationSource()); - return 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); } } diff --git a/src/OpenTelemetry/Internal/HostingHelper.cs b/src/OpenTelemetry/Internal/HostingHelper.cs deleted file mode 100644 index 58708f0aad6..00000000000 --- a/src/OpenTelemetry/Internal/HostingHelper.cs +++ /dev/null @@ -1,253 +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. -// - -#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 68a756d0d7b..848737de80f 100644 --- a/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs +++ b/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs @@ -175,15 +175,6 @@ 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() { @@ -424,36 +415,6 @@ 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 deleted file mode 100644 index d4fee58cfbd..00000000000 --- a/src/OpenTelemetry/Internal/Shims/IgnoresAccessChecksToAttribute.cs +++ /dev/null @@ -1,33 +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. -// - -#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 5decd71ff36..65e87e8f969 100644 --- a/src/OpenTelemetry/OpenTelemetry.csproj +++ b/src/OpenTelemetry/OpenTelemetry.csproj @@ -20,7 +20,6 @@ - diff --git a/src/OpenTelemetry/OpenTelemetryBuilder.cs b/src/OpenTelemetry/OpenTelemetryBuilder.cs index 216343c9f60..f817e0bfe68 100644 --- a/src/OpenTelemetry/OpenTelemetryBuilder.cs +++ b/src/OpenTelemetry/OpenTelemetryBuilder.cs @@ -74,9 +74,18 @@ public OpenTelemetryBuilder ConfigureResource( /// Adds metric services into the builder. /// /// - /// Note: This is safe to be called multiple times and by library authors. + /// 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. /// Only a single will be created for a given - /// . + /// . + /// /// /// The supplied for chaining /// calls. @@ -106,9 +115,18 @@ public OpenTelemetryBuilder WithMetrics(Action configure) /// Adds tracing services into the builder. /// /// - /// Note: This is safe to be called multiple times and by library authors. + /// 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. /// 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 0fc0f408976..2541a85f7bb 100644 --- a/src/OpenTelemetry/OpenTelemetryServiceCollectionExtensions.cs +++ b/src/OpenTelemetry/OpenTelemetryServiceCollectionExtensions.cs @@ -17,7 +17,6 @@ #nullable enable using Microsoft.Extensions.DependencyInjection; -using OpenTelemetry.Internal; using OpenTelemetry.Metrics; using OpenTelemetry.Trace; @@ -36,12 +35,12 @@ public static class OpenTelemetryServiceCollectionExtensions /// Notes: /// /// A and/or - /// 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 . + /// 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 . /// 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) - { - HostingHelper.AddOpenTelemetryHostedServiceIntoServiceCollection(services); - - return new(services); - } + => new(services); } diff --git a/test/OpenTelemetry.Extensions.Hosting.Tests/EventSourceTest.cs b/test/OpenTelemetry.Extensions.Hosting.Tests/EventSourceTest.cs new file mode 100644 index 00000000000..64b4ab634bf --- /dev/null +++ b/test/OpenTelemetry.Extensions.Hosting.Tests/EventSourceTest.cs @@ -0,0 +1,31 @@ +// +// 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 new file mode 100644 index 00000000000..fe267d558a5 --- /dev/null +++ b/test/OpenTelemetry.Extensions.Hosting.Tests/HostingMeterExtensionTests.cs @@ -0,0 +1,109 @@ +// +// 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 new file mode 100644 index 00000000000..1122a9a8e86 --- /dev/null +++ b/test/OpenTelemetry.Extensions.Hosting.Tests/HostingTracerExtensionTests.cs @@ -0,0 +1,109 @@ +// +// 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 cb8d05ab9ad..f32a85aedf3 100644 --- a/test/OpenTelemetry.Extensions.Hosting.Tests/InMemoryExporterMetricsExtensionsTests.cs +++ b/test/OpenTelemetry.Extensions.Hosting.Tests/InMemoryExporterMetricsExtensionsTests.cs @@ -16,8 +16,11 @@ #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; @@ -86,7 +89,7 @@ private static async Task RunMetricsTest(Action configure, using var host = await new HostBuilder() .ConfigureWebHost(webBuilder => webBuilder .UseTestServer() - .ConfigureServices(services => services.AddOpenTelemetry().WithMetrics(configure)) + .ConfigureServices(services => services.AddOpenTelemetry().WithMetrics(configure).StartWithHost()) .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 new file mode 100644 index 00000000000..3900a74028f --- /dev/null +++ b/test/OpenTelemetry.Extensions.Hosting.Tests/TelemetryHostedServiceTests.cs @@ -0,0 +1,87 @@ +// +// 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 cb51bd437c4..cc7ffcee3c4 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs @@ -653,7 +653,8 @@ public async Task ActivitiesStartedInMiddlewareBySettingHostActivityToNullShould .WithTracing(builder => builder .AddAspNetCoreInstrumentation() .AddSource(activitySourceName) - .AddInMemoryExporter(exportedItems)); + .AddInMemoryExporter(exportedItems)) + .StartWithHost(); }); builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders()); }) @@ -691,7 +692,8 @@ void ConfigureTestServices(IServiceCollection services) services.AddOpenTelemetry() .WithTracing(builder => builder .AddAspNetCoreInstrumentation() - .AddInMemoryExporter(exportedItems)); + .AddInMemoryExporter(exportedItems)) + .StartWithHost(); // 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 3631ca9a1ef..16136110509 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/DependencyInjectionConfigTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/DependencyInjectionConfigTests.cs @@ -46,7 +46,8 @@ void ConfigureTestServices(IServiceCollection services) { services.AddOpenTelemetry() .WithTracing(builder => builder - .AddAspNetCoreInstrumentation(name, configureAspNetCoreInstrumentationOptions: null)); + .AddAspNetCoreInstrumentation(name, configureAspNetCoreInstrumentationOptions: null)) + .StartWithHost(); services.Configure(name, options => { @@ -77,7 +78,8 @@ void ConfigureTestServices(IServiceCollection services) { services.AddOpenTelemetry() .WithMetrics(builder => builder - .AddAspNetCoreInstrumentation(name, configureAspNetCoreInstrumentationOptions: null)); + .AddAspNetCoreInstrumentation(name, configureAspNetCoreInstrumentationOptions: null)) + .StartWithHost(); services.Configure(name, options => { diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs index 8500ac00a22..971920474e9 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs @@ -63,7 +63,8 @@ public async Task SuccessfulTemplateControllerCallGeneratesASpan( services.AddOpenTelemetry() .WithTracing(builder => builder .AddAspNetCoreInstrumentation(options => options.RecordException = recordException) - .AddInMemoryExporter(exportedItems)); + .AddInMemoryExporter(exportedItems)) + .StartWithHost(); }); builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders()); }) diff --git a/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj b/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj index c2db74d6d20..c87707d50eb 100644 --- a/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj +++ b/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj @@ -16,8 +16,7 @@ - - + diff --git a/test/OpenTelemetry.Tests/OpenTelemetryServiceCollectionExtensionsTests.cs b/test/OpenTelemetry.Tests/OpenTelemetryServiceCollectionExtensionsTests.cs deleted file mode 100644 index fa425c2d470..00000000000 --- a/test/OpenTelemetry.Tests/OpenTelemetryServiceCollectionExtensionsTests.cs +++ /dev/null @@ -1,264 +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. -// - -#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); - } -}