diff --git a/src/OpenTelemetry.Api.ProviderBuilderExtensions/AssemblyInfo.cs b/src/OpenTelemetry.Api.ProviderBuilderExtensions/AssemblyInfo.cs index e1ba813e7aa..c8c47e0bd17 100644 --- a/src/OpenTelemetry.Api.ProviderBuilderExtensions/AssemblyInfo.cs +++ b/src/OpenTelemetry.Api.ProviderBuilderExtensions/AssemblyInfo.cs @@ -17,5 +17,6 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("OpenTelemetry" + AssemblyInfo.PublicKey)] +[assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Hosting" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("OpenTelemetry.Tests" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("OpenTelemetry.Api.ProviderBuilderExtensions.Tests" + AssemblyInfo.PublicKey)] diff --git a/src/OpenTelemetry.Api/AssemblyInfo.cs b/src/OpenTelemetry.Api/AssemblyInfo.cs index 7dd7f324147..1da065d0ab5 100644 --- a/src/OpenTelemetry.Api/AssemblyInfo.cs +++ b/src/OpenTelemetry.Api/AssemblyInfo.cs @@ -20,6 +20,8 @@ [assembly: InternalsVisibleTo("OpenTelemetry.Api.ProviderBuilderExtensions" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("OpenTelemetry.Api.ProviderBuilderExtensions.Tests" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("OpenTelemetry.Api.Tests" + AssemblyInfo.PublicKey)] +[assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Hosting" + AssemblyInfo.PublicKey)] +[assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Hosting.Tests" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("OpenTelemetry.Shims.OpenTracing.Tests" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("OpenTelemetry.Tests" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2" + AssemblyInfo.MoqPublicKey)] diff --git a/src/OpenTelemetry.Extensions.Hosting/AssemblyInfo.cs b/src/OpenTelemetry.Extensions.Hosting/AssemblyInfo.cs index abf21ce8c15..312e279db0b 100644 --- a/src/OpenTelemetry.Extensions.Hosting/AssemblyInfo.cs +++ b/src/OpenTelemetry.Extensions.Hosting/AssemblyInfo.cs @@ -19,6 +19,7 @@ [assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Hosting.Tests" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2" + AssemblyInfo.MoqPublicKey)] +/* Note: OpenTelemetry.Extensions.Hosting temporarily sees OpenTelemetry.Api internals for LoggerProvider #if SIGNED internal static class AssemblyInfo { @@ -32,3 +33,4 @@ internal static class AssemblyInfo public const string MoqPublicKey = ""; } #endif +*/ diff --git a/src/OpenTelemetry.Extensions.Hosting/Implementation/HostingExtensionsEventSource.cs b/src/OpenTelemetry.Extensions.Hosting/Implementation/HostingExtensionsEventSource.cs index f6f86038cfe..ac54416399b 100644 --- a/src/OpenTelemetry.Extensions.Hosting/Implementation/HostingExtensionsEventSource.cs +++ b/src/OpenTelemetry.Extensions.Hosting/Implementation/HostingExtensionsEventSource.cs @@ -37,5 +37,11 @@ public void MeterProviderNotRegistered() { this.WriteEvent(2); } + + [Event(3, Message = "OpenTelemetry LoggerProvider was not found in application services. Logging will remain disabled.", Level = EventLevel.Warning)] + public void LoggerProviderNotRegistered() + { + this.WriteEvent(3); + } } } diff --git a/src/OpenTelemetry.Extensions.Hosting/Implementation/TelemetryHostedService.cs b/src/OpenTelemetry.Extensions.Hosting/Implementation/TelemetryHostedService.cs index 3e260c6e41a..60023e38b7b 100644 --- a/src/OpenTelemetry.Extensions.Hosting/Implementation/TelemetryHostedService.cs +++ b/src/OpenTelemetry.Extensions.Hosting/Implementation/TelemetryHostedService.cs @@ -17,6 +17,7 @@ using System.Diagnostics; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using OpenTelemetry.Logs; using OpenTelemetry.Metrics; using OpenTelemetry.Trace; @@ -64,5 +65,11 @@ internal static void Initialize(IServiceProvider serviceProvider) { HostingExtensionsEventSource.Log.TracerProviderNotRegistered(); } + + var loggerProvider = serviceProvider!.GetService(); + if (loggerProvider == null) + { + HostingExtensionsEventSource.Log.LoggerProviderNotRegistered(); + } } } diff --git a/src/OpenTelemetry.Extensions.Hosting/OpenTelemetry.Extensions.Hosting.csproj b/src/OpenTelemetry.Extensions.Hosting/OpenTelemetry.Extensions.Hosting.csproj index 133f193acc6..cbb587629a8 100644 --- a/src/OpenTelemetry.Extensions.Hosting/OpenTelemetry.Extensions.Hosting.csproj +++ b/src/OpenTelemetry.Extensions.Hosting/OpenTelemetry.Extensions.Hosting.csproj @@ -18,8 +18,10 @@ + diff --git a/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryBuilder.cs b/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryBuilder.cs index 110d2b89915..8ea31498630 100644 --- a/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryBuilder.cs +++ b/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryBuilder.cs @@ -15,7 +15,9 @@ // using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using OpenTelemetry.Internal; +using OpenTelemetry.Logs; using OpenTelemetry.Metrics; using OpenTelemetry.Resources; using OpenTelemetry.Trace; @@ -44,7 +46,7 @@ internal OpenTelemetryBuilder(IServiceCollection services) /// /// Registers an action to configure the s used - /// by tracing and metrics. + /// by tracing, metrics, and logging. /// /// /// Note: This is safe to be called multiple times and by library authors. @@ -65,6 +67,9 @@ public OpenTelemetryBuilder ConfigureResource( this.Services.ConfigureOpenTelemetryTracerProvider( (sp, builder) => builder.ConfigureResource(configure)); + this.Services.ConfigureOpenTelemetryLoggerProvider( + (sp, builder) => builder.ConfigureResource(configure)); + return this; } @@ -131,4 +136,45 @@ public OpenTelemetryBuilder WithTracing(Action configure) return this; } + + /// + /// Adds logging services into the builder. + /// + /// + /// Notes: + /// + /// This is safe to be called multiple times and by library authors. + /// Only a single will be created for a given + /// . + /// This operation enables integration + /// automatically by calling . + /// + /// + /// The supplied for chaining + /// calls. + internal OpenTelemetryBuilder WithLogging() + => this.WithLogging(b => { }); + + /// + /// Adds logging services into the builder. + /// + /// + /// + /// configuration callback. + /// The supplied for chaining + /// calls. + internal OpenTelemetryBuilder WithLogging(Action configure) + { + Guard.ThrowIfNull(configure); + + // Note: This enables ILogger integration + this.Services.AddLogging().AddOpenTelemetry(); + + var builder = new LoggerProviderServiceCollectionBuilder(this.Services); + + configure(builder); + + return this; + } } diff --git a/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryBuilderTests.cs b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryBuilderTests.cs new file mode 100644 index 00000000000..0fb793a09bc --- /dev/null +++ b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryBuilderTests.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 OpenTelemetry.Logs; +using OpenTelemetry.Metrics; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; +using Xunit; + +namespace OpenTelemetry.Extensions.Hosting.Tests; + +public class OpenTelemetryBuilderTests +{ + [Fact] + public void ConfigureResourceTest() + { + var services = new ServiceCollection(); + + services + .AddOpenTelemetry() + .ConfigureResource(r => r.AddResource(new Resource(new Dictionary { ["key1"] = "value1" }))) + .WithLogging() + .WithMetrics() + .WithTracing(); + + using var sp = services.BuildServiceProvider(); + + var tracerProvider = sp.GetRequiredService() as TracerProviderSdk; + var meterProvider = sp.GetRequiredService() as MeterProviderSdk; + var loggerProvider = sp.GetRequiredService() as LoggerProviderSdk; + + Assert.NotNull(tracerProvider); + Assert.NotNull(meterProvider); + Assert.NotNull(loggerProvider); + + Assert.Contains(tracerProvider.Resource.Attributes, kvp => kvp.Key == "key1" && (string)kvp.Value == "value1"); + Assert.Contains(meterProvider.Resource.Attributes, kvp => kvp.Key == "key1" && (string)kvp.Value == "value1"); + Assert.Contains(loggerProvider.Resource.Attributes, kvp => kvp.Key == "key1" && (string)kvp.Value == "value1"); + } +} diff --git a/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryServicesExtensionsTests.cs b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryServicesExtensionsTests.cs index a0d1265799c..c6a3b0a8108 100644 --- a/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryServicesExtensionsTests.cs +++ b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryServicesExtensionsTests.cs @@ -17,6 +17,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using OpenTelemetry.Logs; using OpenTelemetry.Metrics; using OpenTelemetry.Trace; using Xunit; @@ -326,6 +327,129 @@ public void AddOpenTelemetry_WithMetrics_NestedResolutionUsingConfigureTest() Assert.True(innerTestExecuted); } + [Fact] + public void AddOpenTelemetry_WithLogging_SingleProviderForServiceCollectionTest() + { + var services = new ServiceCollection(); + + services.AddOpenTelemetry().WithLogging(builder => { }); + + services.AddOpenTelemetry().WithLogging(builder => { }); + + using var serviceProvider = services.BuildServiceProvider(); + + Assert.NotNull(serviceProvider); + + var loggerProviders = serviceProvider.GetServices(); + + Assert.Single(loggerProviders); + } + + [Fact] + public void AddOpenTelemetry_WithLogging_DisposalTest() + { + var services = new ServiceCollection(); + + bool testRun = false; + + services.AddOpenTelemetry().WithLogging(builder => + { + testRun = true; + + // Note: Build can't be called directly on builder tied to external services + Assert.Throws(() => builder.Build()); + }); + + Assert.True(testRun); + + var serviceProvider = services.BuildServiceProvider(); + + var provider = serviceProvider.GetRequiredService() as LoggerProviderSdk; + + Assert.NotNull(provider); + Assert.Null(provider.OwnedServiceProvider); + + Assert.NotNull(serviceProvider); + Assert.NotNull(provider); + + Assert.False(provider.Disposed); + + serviceProvider.Dispose(); + + Assert.True(provider.Disposed); + } + + [Fact] + public async Task AddOpenTelemetry_WithLogging_HostConfigurationHonoredTest() + { + bool configureBuilderCalled = false; + + var builder = new HostBuilder() + .ConfigureAppConfiguration(builder => + { + builder.AddInMemoryCollection(new Dictionary + { + ["TEST_KEY"] = "TEST_KEY_VALUE", + }); + }) + .ConfigureServices(services => + { + services.AddOpenTelemetry() + .WithLogging(builder => + { + if (builder is IDeferredLoggerProviderBuilder deferredLoggerProviderBuilder) + { + deferredLoggerProviderBuilder.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 void AddOpenTelemetry_WithLogging_NestedResolutionUsingConfigureTest() + { + bool innerTestExecuted = false; + + var services = new ServiceCollection(); + + services.AddOpenTelemetry().WithLogging(builder => + { + if (builder is IDeferredLoggerProviderBuilder deferredLoggerProviderBuilder) + { + deferredLoggerProviderBuilder.Configure((sp, builder) => + { + innerTestExecuted = true; + Assert.Throws(() => sp.GetService()); + }); + } + }); + + using var serviceProvider = services.BuildServiceProvider(); + var resolvedProvider = serviceProvider.GetRequiredService(); + Assert.True(innerTestExecuted); + } + private sealed class MySampler : Sampler { public override SamplingResult ShouldSample(in SamplingParameters samplingParameters)