[hosting-logs] Add WithLogging (#4483)

Co-authored-by: Utkarsh Umesan Pillai <utpilla@microsoft.com>
This commit is contained in:
Mikel Blanchard 2023-05-12 15:36:16 -07:00 committed by GitHub
parent 01f3891b95
commit 5357f17444
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 245 additions and 1 deletions

View File

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

View File

@ -20,6 +20,8 @@ using System.Runtime.CompilerServices;
[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)]

View File

@ -19,6 +19,7 @@ using System.Runtime.CompilerServices;
[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
*/

View File

@ -37,5 +37,11 @@ namespace OpenTelemetry.Extensions.Hosting.Implementation
{
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);
}
}
}

View File

@ -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 sealed class TelemetryHostedService : IHostedService
{
HostingExtensionsEventSource.Log.TracerProviderNotRegistered();
}
var loggerProvider = serviceProvider!.GetService<LoggerProvider>();
if (loggerProvider == null)
{
HostingExtensionsEventSource.Log.LoggerProviderNotRegistered();
}
}
}

View File

@ -18,8 +18,10 @@
</ItemGroup>
<ItemGroup>
<!-- Note: OpenTelemetry.Extensions.Hosting temporarily sees OpenTelemetry.Api internals for LoggerProvider
<Compile Include="$(RepoRoot)\src\OpenTelemetry.Api\Internal\ExceptionExtensions.cs" Link="Includes\ExceptionExtensions.cs" />
<Compile Include="$(RepoRoot)\src\OpenTelemetry.Api\Internal\Guard.cs" Link="Includes\Guard.cs" />
-->
</ItemGroup>
</Project>

View File

@ -15,7 +15,9 @@
// </copyright>
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 @@ public sealed class OpenTelemetryBuilder
/// <summary>
/// Registers an action to configure the <see cref="ResourceBuilder"/>s used
/// by tracing and metrics.
/// by tracing, metrics, and logging.
/// </summary>
/// <remarks>
/// Note: This is safe to be called multiple times and by library authors.
@ -65,6 +67,9 @@ public sealed class OpenTelemetryBuilder
this.Services.ConfigureOpenTelemetryTracerProvider(
(sp, builder) => builder.ConfigureResource(configure));
this.Services.ConfigureOpenTelemetryLoggerProvider(
(sp, builder) => builder.ConfigureResource(configure));
return this;
}
@ -131,4 +136,45 @@ public sealed class OpenTelemetryBuilder
return this;
}
/// <summary>
/// Adds logging services into the builder.
/// </summary>
/// <remarks>
/// Notes:
/// <list type="bullet">
/// <item>This is safe to be called multiple times and by library authors.
/// Only a single <see cref="LoggerProvider"/> will be created for a given
/// <see cref="IServiceCollection"/>.</item>
/// <item>This operation enables <see cref="ILogger"/> integration
/// automatically by calling <see
/// cref="OpenTelemetryLoggingExtensions.AddOpenTelemetry(ILoggingBuilder)"/>.</item>
/// </list>
/// </remarks>
/// <returns>The supplied <see cref="OpenTelemetryBuilder"/> for chaining
/// calls.</returns>
internal OpenTelemetryBuilder WithLogging()
=> this.WithLogging(b => { });
/// <summary>
/// Adds logging services into the builder.
/// </summary>
/// <remarks><inheritdoc cref="WithLogging()" path="/remarks"/></remarks>
/// <param name="configure"><see cref="LoggerProviderBuilder"/>
/// configuration callback.</param>
/// <returns>The supplied <see cref="OpenTelemetryBuilder"/> for chaining
/// calls.</returns>
internal OpenTelemetryBuilder WithLogging(Action<LoggerProviderBuilder> configure)
{
Guard.ThrowIfNull(configure);
// Note: This enables ILogger integration
this.Services.AddLogging().AddOpenTelemetry();
var builder = new LoggerProviderServiceCollectionBuilder(this.Services);
configure(builder);
return this;
}
}

View File

@ -0,0 +1,54 @@
// <copyright file="OpenTelemetryBuilderTests.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 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<string, object> { ["key1"] = "value1" })))
.WithLogging()
.WithMetrics()
.WithTracing();
using var sp = services.BuildServiceProvider();
var tracerProvider = sp.GetRequiredService<TracerProvider>() as TracerProviderSdk;
var meterProvider = sp.GetRequiredService<MeterProvider>() as MeterProviderSdk;
var loggerProvider = sp.GetRequiredService<LoggerProvider>() 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");
}
}

View File

@ -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 class OpenTelemetryServicesExtensionsTests
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<LoggerProvider>();
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<NotSupportedException>(() => builder.Build());
});
Assert.True(testRun);
var serviceProvider = services.BuildServiceProvider();
var provider = serviceProvider.GetRequiredService<LoggerProvider>() 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<string, string>
{
["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<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 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<NotSupportedException>(() => sp.GetService<LoggerProvider>());
});
}
});
using var serviceProvider = services.BuildServiceProvider();
var resolvedProvider = serviceProvider.GetRequiredService<LoggerProvider>();
Assert.True(innerTestExecuted);
}
private sealed class MySampler : Sampler
{
public override SamplingResult ShouldSample(in SamplingParameters samplingParameters)