Revert "[sdk] Use reflection and dynamic types to start providers (#4151)" (#4173)

This reverts commit b549e12148.
This commit is contained in:
Mikel Blanchard 2023-02-09 10:14:12 -08:00 committed by GitHub
parent 287295f9e6
commit 94f67d8a8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 636 additions and 663 deletions

View File

@ -37,8 +37,7 @@
<MicrosoftCodeCoveragePkgVer>[17.4.1]</MicrosoftCodeCoveragePkgVer>
<MicrosoftExtensionsDependencyInjectionPkgVer>[3.1.0,)</MicrosoftExtensionsDependencyInjectionPkgVer>
<MicrosoftExtensionsDependencyInjectionAbstractionsPkgVer>$(MicrosoftExtensionsDependencyInjectionPkgVer)</MicrosoftExtensionsDependencyInjectionAbstractionsPkgVer>
<MicrosoftExtensionsHostingPkgVer>[2.1.0,)</MicrosoftExtensionsHostingPkgVer>
<MicrosoftExtensionsHostingAbstractionsPkgVer>$(MicrosoftExtensionsHostingPkgVer)</MicrosoftExtensionsHostingAbstractionsPkgVer>
<MicrosoftExtensionsHostingAbstractionsPkgVer>[2.1.0,)</MicrosoftExtensionsHostingAbstractionsPkgVer>
<MicrosoftExtensionsLoggingPkgVer>[3.1.0,)</MicrosoftExtensionsLoggingPkgVer>
<MicrosoftExtensionsLoggingConfigurationPkgVer>$(MicrosoftExtensionsLoggingPkgVer)</MicrosoftExtensionsLoggingConfigurationPkgVer>
<MicrosoftExtensionsOptionsPkgVer>[3.1.0,)</MicrosoftExtensionsOptionsPkgVer>

View File

@ -27,7 +27,8 @@ appBuilder.Services.AddOpenTelemetry()
.AddService(serviceName: "OTel.NET Getting Started"))
.WithTracing(builder => builder
.AddAspNetCoreInstrumentation()
.AddConsoleExporter());
.AddConsoleExporter())
.StartWithHost();
var app = appBuilder.Build();

View File

@ -8,6 +8,7 @@
<ItemGroup>
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Console\OpenTelemetry.Exporter.Console.csproj" />
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Extensions.Hosting\OpenTelemetry.Extensions.Hosting.csproj" />
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Instrumentation.AspNetCore\OpenTelemetry.Instrumentation.AspNetCore.csproj" />
</ItemGroup>

View File

@ -10,6 +10,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Extensions.Hosting\OpenTelemetry.Extensions.Hosting.csproj" />
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Console\OpenTelemetry.Exporter.Console.csproj" />
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.OpenTelemetryProtocol\OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj" />
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.OpenTelemetryProtocol.Logs\OpenTelemetry.Exporter.OpenTelemetryProtocol.Logs.csproj" />

View File

@ -44,7 +44,8 @@ Action<ResourceBuilder> configureResource = r => r.AddService(
// for manual instrumentation
appBuilder.Services.AddSingleton<Instrumentation>();
// 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 @@ appBuilder.Services.AddOpenTelemetry()
builder.AddConsoleExporter();
break;
}
});
})
.StartWithHost();
// Clear default logging providers used by WebApplication host.
appBuilder.Logging.ClearProviders();

View File

@ -16,6 +16,7 @@
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Console\OpenTelemetry.Exporter.Console.csproj" />
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Jaeger\OpenTelemetry.Exporter.Jaeger.csproj" />
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Zipkin\OpenTelemetry.Exporter.Zipkin.csproj" />
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Extensions.Hosting\OpenTelemetry.Extensions.Hosting.csproj" />
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Instrumentation.AspNetCore\OpenTelemetry.Instrumentation.AspNetCore.csproj" />
</ItemGroup>

View File

@ -61,7 +61,8 @@ namespace Examples.GrpcService
builder.AddConsoleExporter();
break;
}
});
})
.StartWithHost();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

View File

@ -43,7 +43,8 @@ namespace WebApi
{
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)

View File

@ -10,6 +10,7 @@
<ItemGroup>
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Instrumentation.AspNetCore\OpenTelemetry.Instrumentation.AspNetCore.csproj" />
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Zipkin\OpenTelemetry.Exporter.Zipkin.csproj" />
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Extensions.Hosting\OpenTelemetry.Extensions.Hosting.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -42,7 +42,8 @@ namespace WorkerService
{
var zipkinHostName = Environment.GetEnvironmentVariable("ZIPKIN_HOSTNAME") ?? "localhost";
b.Endpoint = new Uri($"http://{zipkinHostName}:9411/api/v2/spans");
}));
}))
.StartWithHost();
});
}
}

View File

@ -11,6 +11,7 @@
<ItemGroup>
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry\OpenTelemetry.csproj" />
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Zipkin\OpenTelemetry.Exporter.Zipkin.csproj" />
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Extensions.Hosting\OpenTelemetry.Extensions.Hosting.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -98,7 +98,8 @@ services.AddOpenTelemetry()
client.DefaultRequestHeaders.Add("X-MyCustomHeader", "value");
return client;
};
}));
}))
.StartWithHost();
```
For users using

View File

@ -135,7 +135,8 @@ services.AddOpenTelemetry()
client.DefaultRequestHeaders.Add("X-MyCustomHeader", "value");
return client;
};
}));
}))
.StartWithHost();
```
For users using

View File

@ -28,7 +28,8 @@ dotnet add package --prerelease OpenTelemetry.Exporter.Prometheus.AspNetCore
```csharp
services.AddOpenTelemetry()
.WithMetrics(builder => builder
.AddPrometheusExporter());
.AddPrometheusExporter())
.StartWithHost();
```
* Or configure directly:

View File

@ -87,7 +87,8 @@ services.AddOpenTelemetry()
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("X-MyCustomHeader", "value");
return client;
}));
}))
.StartWithHost();
```
For users using

View File

@ -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<OpenTelemetry.Metrics.MeterProviderBuilder> 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<OpenTelemetry.Trace.TracerProviderBuilder> configure) -> Microsoft.Extensions.DependencyInjection.IServiceCollection
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.Configure(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, System.Action<System.IServiceProvider, OpenTelemetry.Metrics.MeterProviderBuilder> 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<System.IServiceProvider, OpenTelemetry.Trace.TracerProviderBuilder> configure) -> OpenTelemetry.Trace.TracerProviderBuilder
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.GetServices(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder) -> Microsoft.Extensions.DependencyInjection.IServiceCollection

View File

@ -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

View File

@ -0,0 +1,41 @@
// <copyright file="HostingExtensionsEventSource.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>
using System.Diagnostics.Tracing;
namespace OpenTelemetry.Extensions.Hosting.Implementation
{
/// <summary>
/// EventSource events emitted from the project.
/// </summary>
[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);
}
}
}

View File

@ -0,0 +1,64 @@
// <copyright file="TelemetryHostedService.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>
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<MeterProvider>();
if (meterProvider == null)
{
HostingExtensionsEventSource.Log.MeterProviderNotRegistered();
}
var tracerProvider = serviceProvider.GetService<TracerProvider>();
if (tracerProvider == null)
{
HostingExtensionsEventSource.Log.TracerProviderNotRegistered();
}
}
}

View File

@ -0,0 +1,54 @@
// <copyright file="OpenTelemetryBuilderHostingExtensions.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using OpenTelemetry.Extensions.Hosting.Implementation;
using OpenTelemetry.Internal;
namespace OpenTelemetry;
/// <summary>
/// Contains hosting extension methods for the <see
/// cref="OpenTelemetryBuilder"/> class.
/// </summary>
public static class OpenTelemetryBuilderHostingExtensions
{
/// <summary>
/// Registers an <see cref="IHostedService"/> to automatically start all
/// configured OpenTelemetry services in the supplied <see
/// cref="IServiceCollection" />.
/// </summary>
/// <remarks>
/// Note: This is safe to be called multiple times. Only a single <see
/// cref="IHostedService"/> will be created for a given <see
/// cref="IServiceCollection"/>. This should generally be called by hosting
/// application code and NOT library authors.
/// </remarks>
/// <param name="builder"><see cref="OpenTelemetryBuilder"/>.</param>
/// <returns>The supplied <see cref="OpenTelemetryBuilder"/> for chaining
/// calls.</returns>
public static OpenTelemetryBuilder StartWithHost(this OpenTelemetryBuilder builder)
{
Guard.ThrowIfNull(builder);
builder.Services.TryAddEnumerable(
ServiceDescriptor.Singleton<IHostedService, TelemetryHostedService>());
return builder;
}
}

View File

@ -51,7 +51,7 @@ public static class OpenTelemetryServicesExtensions
/// <param name="services"><see cref="IServiceCollection"/>.</param>
/// <returns>Supplied <see cref="IServiceCollection"/> for chaining
/// calls.</returns>
[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 class OpenTelemetryServicesExtensions
/// cref="TracerProviderBuilder"/>.</param>
/// <returns>Supplied <see cref="IServiceCollection"/> for chaining
/// calls.</returns>
[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<TracerProviderBuilder> configure)
{
services.AddOpenTelemetry().WithTracing(configure);
services.AddOpenTelemetry().WithTracing(configure).StartWithHost();
return services;
}
@ -100,7 +100,7 @@ public static class OpenTelemetryServicesExtensions
/// <param name="services"><see cref="IServiceCollection"/>.</param>
/// <returns>Supplied <see cref="IServiceCollection"/> for chaining
/// calls.</returns>
[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 class OpenTelemetryServicesExtensions
/// cref="TracerProviderBuilder"/>.</param>
/// <returns>Supplied <see cref="IServiceCollection"/> for chaining
/// calls.</returns>
[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<MeterProviderBuilder> configure)
{
services.AddOpenTelemetry().WithMetrics(configure);
services.AddOpenTelemetry().WithMetrics(configure).StartWithHost();
return services;
}

View File

@ -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();

View File

@ -58,7 +58,8 @@ public void ConfigureServices(IServiceCollection services)
services.AddOpenTelemetry()
.WithTracing(builder => builder
.AddAspNetCoreInstrumentation()
.AddJaegerExporter());
.AddJaegerExporter())
.StartWithHost();
}
```
@ -87,7 +88,8 @@ services.Configure<AspNetCoreInstrumentationOptions>(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),

View File

@ -120,7 +120,8 @@ services.AddOpenTelemetry()
{
activity.SetTag("responseVersion", httpResponseMessage.Version);
};
});
})
.StartWithHost();
```
[Processor](../../docs/trace/extending-the-sdk/README.md#processor),

View File

@ -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

View File

@ -4,24 +4,49 @@
#nullable enable
using Microsoft.Extensions.Configuration;
using System;
using Microsoft.Extensions.Configuration.EnvironmentVariables;
namespace OpenTelemetry.Internal;
/// <summary>
/// Extension methods for registering <see cref="EnvironmentVariablesConfigurationProvider"/> with <see cref="IConfigurationBuilder"/>.
/// </summary>
internal static class EnvironmentVariablesExtensions
namespace Microsoft.Extensions.Configuration
{
/// <summary>
/// Adds an <see cref="IConfigurationProvider"/> that reads configuration values from environment variables.
/// Extension methods for registering <see cref="EnvironmentVariablesConfigurationProvider"/> with <see cref="IConfigurationBuilder"/>.
/// </summary>
/// <param name="configurationBuilder">The <see cref="IConfigurationBuilder"/> to add to.</param>
/// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
public static IConfigurationBuilder AddEnvironmentVariables(this IConfigurationBuilder configurationBuilder)
internal static class EnvironmentVariablesExtensions
{
configurationBuilder.Add(new EnvironmentVariablesConfigurationSource());
return configurationBuilder;
/// <summary>
/// Adds an <see cref="IConfigurationProvider"/> that reads configuration values from environment variables.
/// </summary>
/// <param name="configurationBuilder">The <see cref="IConfigurationBuilder"/> to add to.</param>
/// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
public static IConfigurationBuilder AddEnvironmentVariables(this IConfigurationBuilder configurationBuilder)
{
configurationBuilder.Add(new EnvironmentVariablesConfigurationSource());
return configurationBuilder;
}
/// <summary>
/// Adds an <see cref="IConfigurationProvider"/> that reads configuration values from environment variables
/// with a specified prefix.
/// </summary>
/// <param name="configurationBuilder">The <see cref="IConfigurationBuilder"/> to add to.</param>
/// <param name="prefix">The prefix that environment variable names must start with. The prefix will be removed from the environment variable names.</param>
/// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
public static IConfigurationBuilder AddEnvironmentVariables(
this IConfigurationBuilder configurationBuilder,
string? prefix)
{
configurationBuilder.Add(new EnvironmentVariablesConfigurationSource { Prefix = prefix });
return configurationBuilder;
}
/// <summary>
/// Adds an <see cref="IConfigurationProvider"/> that reads configuration values from environment variables.
/// </summary>
/// <param name="builder">The <see cref="IConfigurationBuilder"/> to add to.</param>
/// <param name="configureSource">Configures the source.</param>
/// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
public static IConfigurationBuilder AddEnvironmentVariables(this IConfigurationBuilder builder, Action<EnvironmentVariablesConfigurationSource>? configureSource)
=> builder.Add(configureSource);
}
}

View File

@ -1,253 +0,0 @@
// <copyright file="HostingHelper.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>
#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<TelemetryHostedServiceHelper>();
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<MeterProvider>();
if (meterProvider == null)
{
OpenTelemetrySdkEventSource.Log.MeterProviderNotRegistered();
}
var tracerProvider = serviceProvider.GetService<TracerProvider>();
if (tracerProvider == null)
{
OpenTelemetrySdkEventSource.Log.TracerProviderNotRegistered();
}
}
}
}

View File

@ -175,15 +175,6 @@ namespace OpenTelemetry.Internal
}
}
[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 @@ namespace OpenTelemetry.Internal
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
{

View File

@ -1,33 +0,0 @@
// <copyright file="IgnoresAccessChecksToAttribute.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>
#nullable enable
namespace System.Runtime.CompilerServices;
/// <summary>
/// IgnoresAccessChecksToAttribute tells the runtime to bypass visibility checks
/// to some assembly. See: <see
/// href="https://github.com/dotnet/runtime/discussions/48024" />.
/// </summary>
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
internal sealed class IgnoresAccessChecksToAttribute : Attribute
{
public IgnoresAccessChecksToAttribute(string assemblyName)
=> this.AssemblyName = assemblyName;
public string AssemblyName { get; }
}

View File

@ -20,7 +20,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="$(MicrosoftExtensionsLoggingConfigurationPkgVer)" />
<PackageReference Include="System.Reflection.Emit" Version="$(SystemReflectionEmitLightweightPkgVer)" Condition="'$(TargetFramework)' != 'net6.0'" />
</ItemGroup>
<ItemGroup>

View File

@ -74,9 +74,18 @@ public class OpenTelemetryBuilder
/// Adds metric services into the builder.
/// </summary>
/// <remarks>
/// Note: This is safe to be called multiple times and by library authors.
/// Notes:
/// <list type="bullet">
/// <item>A <see cref="MeterProvider"/> will not be created automatically
/// using this method. To begin collecting metrics either use the
/// <c>OpenTelemetryBuilder.StartWithHost</c> extension in the
/// <c>OpenTelemetry.Extensions.Hosting</c> package or access the <see
/// cref="MeterProvider"/> through the application <see
/// cref="IServiceProvider"/>.</item>
/// <item>This is safe to be called multiple times and by library authors.
/// Only a single <see cref="MeterProvider"/> will be created for a given
/// <see cref="IServiceCollection"/>.
/// <see cref="IServiceCollection"/>.</item>
/// </list>
/// </remarks>
/// <returns>The supplied <see cref="OpenTelemetryBuilder"/> for chaining
/// calls.</returns>
@ -106,9 +115,18 @@ public class OpenTelemetryBuilder
/// Adds tracing services into the builder.
/// </summary>
/// <remarks>
/// Note: This is safe to be called multiple times and by library authors.
/// Notes:
/// <list type="bullet">
/// <item>A <see cref="TracerProvider"/> will not be created automatically
/// using this method. To begin collecting traces either use the
/// <c>OpenTelemetryBuilder.StartWithHost</c> extension in the
/// <c>OpenTelemetry.Extensions.Hosting</c> package or access the <see
/// cref="TracerProvider"/> through the application <see
/// cref="IServiceProvider"/>.</item>
/// <item>This is safe to be called multiple times and by library authors.
/// Only a single <see cref="TracerProvider"/> will be created for a given
/// <see cref="IServiceCollection"/>.
/// <see cref="IServiceCollection"/>.</item>
/// </list>
/// </remarks>
/// <returns>The supplied <see cref="OpenTelemetryBuilder"/> for chaining
/// calls.</returns>

View File

@ -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:
/// <list type="bullet">
/// <item>A <see cref="TracerProvider"/> and/or <see cref="MeterProvider"/>
/// will be created automatically using this method if a host supporting
/// <c>Microsoft.Extensions.Hosting.IHostedService</c> is detected at
/// runtime. To begin collecting traces and/or metrics when hosting
/// extensions are not being used access the <see cref="TracerProvider"/>
/// and/or <see cref="MeterProvider"/> through the application <see
/// cref="IServiceProvider"/>.</item>
/// will not be created automatically using this method. To begin collecting
/// traces and/or metrics either use the
/// <c>OpenTelemetryBuilder.StartWithHost</c> extension in the
/// <c>OpenTelemetry.Extensions.Hosting</c> package or access the <see
/// cref="TracerProvider"/> and/or <see cref="MeterProvider"/> through the
/// application <see cref="IServiceProvider"/>.</item>
/// <item>This is safe to be called multiple times and by library authors.
/// Only a single <see cref="TracerProvider"/> and/or <see
/// cref="MeterProvider"/> will be created for a given <see
@ -52,9 +51,5 @@ public static class OpenTelemetryServiceCollectionExtensions
/// <returns>The supplied <see cref="OpenTelemetryBuilder"/> for chaining
/// calls.</returns>
public static OpenTelemetryBuilder AddOpenTelemetry(this IServiceCollection services)
{
HostingHelper.AddOpenTelemetryHostedServiceIntoServiceCollection(services);
return new(services);
}
=> new(services);
}

View File

@ -0,0 +1,31 @@
// <copyright file="EventSourceTest.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>
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);
}
}
}

View File

@ -0,0 +1,109 @@
// <copyright file="HostingMeterExtensionTests.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>
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<string, string>
{
["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<IConfiguration>();
var testKeyValue = configuration.GetValue<string>("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();
}
}
}

View File

@ -0,0 +1,109 @@
// <copyright file="HostingTracerExtensionTests.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>
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<string, string>
{
["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<IConfiguration>();
var testKeyValue = configuration.GetValue<string>("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();
}
}
}

View File

@ -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 @@ namespace OpenTelemetry.Extensions.Hosting.Tests
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();

View File

@ -0,0 +1,87 @@
// <copyright file="TelemetryHostedServiceTests.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>
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<MySampler>();
}
catch (NotSupportedException)
{
expectedInnerExceptionThrown = true;
throw;
}
});
}
})
.StartWithHost();
});
var host = builder.Build();
await Assert.ThrowsAsync<NotSupportedException>(() => 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);
}
}

View File

@ -653,7 +653,8 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Tests
.WithTracing(builder => builder
.AddAspNetCoreInstrumentation()
.AddSource(activitySourceName)
.AddInMemoryExporter(exportedItems));
.AddInMemoryExporter(exportedItems))
.StartWithHost();
});
builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
})
@ -691,7 +692,8 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Tests
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

View File

@ -46,7 +46,8 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Tests
{
services.AddOpenTelemetry()
.WithTracing(builder => builder
.AddAspNetCoreInstrumentation(name, configureAspNetCoreInstrumentationOptions: null));
.AddAspNetCoreInstrumentation(name, configureAspNetCoreInstrumentationOptions: null))
.StartWithHost();
services.Configure<AspNetCoreInstrumentationOptions>(name, options =>
{
@ -77,7 +78,8 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Tests
{
services.AddOpenTelemetry()
.WithMetrics(builder => builder
.AddAspNetCoreInstrumentation(name, configureAspNetCoreInstrumentationOptions: null));
.AddAspNetCoreInstrumentation(name, configureAspNetCoreInstrumentationOptions: null))
.StartWithHost();
services.Configure<AspNetCoreMetricsInstrumentationOptions>(name, options =>
{

View File

@ -63,7 +63,8 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Tests
services.AddOpenTelemetry()
.WithTracing(builder => builder
.AddAspNetCoreInstrumentation(options => options.RecordException = recordException)
.AddInMemoryExporter(exportedItems));
.AddInMemoryExporter(exportedItems))
.StartWithHost();
});
builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
})

View File

@ -16,8 +16,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="$(MicrosoftExtensionsHostingPkgVer)" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingPkgVer)" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingPkgVer)" />
</ItemGroup>
<ItemGroup>

View File

@ -1,264 +0,0 @@
// <copyright file="OpenTelemetryServiceCollectionExtensionsTests.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>
#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<IHostedService>();
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<MySampler>();
}
catch (NotSupportedException)
{
expectedInnerExceptionThrown = true;
throw;
}
});
}
});
});
var host = builder.Build();
await Assert.ThrowsAsync<NotSupportedException>(() => 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<string, string>
{
["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<IConfiguration>();
var testKeyValue = configuration.GetValue<string?>("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<string, string>
{
["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<IConfiguration>();
var testKeyValue = configuration.GetValue<string?>("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);
}
}