Skip to content

Commit

Permalink
[hosting] Support .NET 8 IMetricsBuilder API (#4958)
Browse files Browse the repository at this point in the history
  • Loading branch information
CodeBlanch authored Nov 22, 2023
1 parent 0c4f065 commit 1f7f931
Show file tree
Hide file tree
Showing 22 changed files with 1,342 additions and 639 deletions.
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

<PackageVersion Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Configuration" Version="8.0.0" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

6 changes: 6 additions & 0 deletions src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
version to `8.0.0`.
([#5051](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5051))

* The `OpenTelemetryBuilder.WithMetrics` method will now register an
`IMetricsListener` named 'OpenTelemetry' into the `IServiceCollection` to
enable metric management via the new `Microsoft.Extensions.Diagnostics` .NET 8
APIs.
([#4958](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4958))

## 1.7.0-alpha.1

Released 2023-Oct-16
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// <copyright file="OpenTelemetryMetricsListener.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 System.Diagnostics.Metrics;
using Microsoft.Extensions.Diagnostics.Metrics;

namespace OpenTelemetry.Metrics;

internal sealed class OpenTelemetryMetricsListener : IMetricsListener, IDisposable
{
private readonly MeterProviderSdk meterProviderSdk;
private IObservableInstrumentsSource? observableInstrumentsSource;

public OpenTelemetryMetricsListener(MeterProvider meterProvider)
{
var meterProviderSdk = meterProvider as MeterProviderSdk;

Debug.Assert(meterProviderSdk != null, "meterProvider was not MeterProviderSdk");

this.meterProviderSdk = meterProviderSdk!;

this.meterProviderSdk.OnCollectObservableInstruments += this.OnCollectObservableInstruments;
}

public string Name => "OpenTelemetry";

public void Dispose()
{
this.meterProviderSdk.OnCollectObservableInstruments -= this.OnCollectObservableInstruments;
}

public MeasurementHandlers GetMeasurementHandlers()
{
return new MeasurementHandlers()
{
ByteHandler = (instrument, value, tags, state)
=> this.MeasurementRecordedLong(instrument, value, tags, state),
ShortHandler = (instrument, value, tags, state)
=> this.MeasurementRecordedLong(instrument, value, tags, state),
IntHandler = (instrument, value, tags, state)
=> this.MeasurementRecordedLong(instrument, value, tags, state),
LongHandler = this.MeasurementRecordedLong,
FloatHandler = (instrument, value, tags, state)
=> this.MeasurementRecordedDouble(instrument, value, tags, state),
DoubleHandler = this.MeasurementRecordedDouble,
};
}

public bool InstrumentPublished(Instrument instrument, out object? userState)
{
userState = this.meterProviderSdk.InstrumentPublished(instrument, listeningIsManagedExternally: true);
return userState != null;
}

public void MeasurementsCompleted(Instrument instrument, object? userState)
{
var meterProvider = this.meterProviderSdk;

if (meterProvider.ViewCount > 0)
{
meterProvider.MeasurementsCompleted(instrument, userState);
}
else
{
meterProvider.MeasurementsCompletedSingleStream(instrument, userState);
}
}

public void Initialize(IObservableInstrumentsSource source)
{
this.observableInstrumentsSource = source;
}

private void OnCollectObservableInstruments()
{
this.observableInstrumentsSource?.RecordObservableInstruments();
}

private void MeasurementRecordedDouble(Instrument instrument, double value, ReadOnlySpan<KeyValuePair<string, object?>> tagsRos, object? userState)
{
var meterProvider = this.meterProviderSdk;

if (meterProvider.ViewCount > 0)
{
meterProvider.MeasurementRecordedDouble(instrument, value, tagsRos, userState);
}
else
{
meterProvider.MeasurementRecordedDoubleSingleStream(instrument, value, tagsRos, userState);
}
}

private void MeasurementRecordedLong(Instrument instrument, long value, ReadOnlySpan<KeyValuePair<string, object?>> tagsRos, object? userState)
{
var meterProvider = this.meterProviderSdk;

if (meterProvider.ViewCount > 0)
{
meterProvider.MeasurementRecordedLong(instrument, value, tagsRos, userState);
}
else
{
meterProvider.MeasurementRecordedLongSingleStream(instrument, value, tagsRos, userState);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.Abstractions" />
</ItemGroup>

<ItemGroup>
Expand Down
25 changes: 15 additions & 10 deletions src/OpenTelemetry.Extensions.Hosting/OpenTelemetryBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// </copyright>

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.Metrics;
using OpenTelemetry.Internal;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
Expand Down Expand Up @@ -61,13 +62,13 @@ public OpenTelemetryBuilder ConfigureResource(
Guard.ThrowIfNull(configure);

this.Services.ConfigureOpenTelemetryMeterProvider(
(sp, builder) => builder.ConfigureResource(configure));
builder => builder.ConfigureResource(configure));

this.Services.ConfigureOpenTelemetryTracerProvider(
(sp, builder) => builder.ConfigureResource(configure));
builder => builder.ConfigureResource(configure));

this.Services.ConfigureOpenTelemetryLoggerProvider(
(sp, builder) => builder.ConfigureResource(configure));
builder => builder.ConfigureResource(configure));

return this;
}
Expand All @@ -76,9 +77,15 @@ public OpenTelemetryBuilder ConfigureResource(
/// 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>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>
/// <item>This method automatically registers an <see
/// cref="IMetricsListener"/> named 'OpenTelemetry' into the <see
/// cref="IServiceCollection"/>.</item>
/// </list>
/// </remarks>
/// <returns>The supplied <see cref="OpenTelemetryBuilder"/> for chaining
/// calls.</returns>
Expand All @@ -95,11 +102,9 @@ public OpenTelemetryBuilder WithMetrics()
/// calls.</returns>
public OpenTelemetryBuilder WithMetrics(Action<MeterProviderBuilder> configure)
{
Guard.ThrowIfNull(configure);

var builder = new MeterProviderBuilderBase(this.Services);

configure(builder);
OpenTelemetryMetricsBuilderExtensions.RegisterMetricsListener(
this.Services,
configure);

return this;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// <copyright file="OpenTelemetryMetricsBuilderExtensions.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.DependencyInjection.Extensions;
using OpenTelemetry.Internal;
using OpenTelemetry.Metrics;

namespace Microsoft.Extensions.Diagnostics.Metrics;

/// <summary>
/// Contains extension methods for registering OpenTelemetry metrics with an
/// <see cref="IMetricsBuilder"/> instance.
/// </summary>
internal static class OpenTelemetryMetricsBuilderExtensions
{
/// <summary>
/// Adds an OpenTelemetry <see cref="IMetricsListener"/> named 'OpenTelemetry' to the <see cref="IMetricsBuilder"/>.
/// </summary>
/// <remarks>
/// Note: 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"/>.
/// </remarks>
/// <param name="metricsBuilder"><see cref="IMetricsBuilder"/>.</param>
/// <returns>The supplied <see cref="IMetricsBuilder"/> for chaining
/// calls.</returns>
public static IMetricsBuilder UseOpenTelemetry(
this IMetricsBuilder metricsBuilder)
=> UseOpenTelemetry(metricsBuilder, b => { });

/// <summary>
/// Adds an OpenTelemetry <see cref="IMetricsListener"/> named 'OpenTelemetry' to the <see cref="IMetricsBuilder"/>.
/// </summary>
/// <remarks><inheritdoc cref="UseOpenTelemetry(IMetricsBuilder)" path="/remarks"/></remarks>
/// <param name="metricsBuilder"><see cref="IMetricsBuilder"/>.</param>
/// <param name="configure"><see cref="MeterProviderBuilder"/>
/// configuration callback.</param>
/// <returns>The supplied <see cref="IMetricsBuilder"/> for chaining
/// calls.</returns>
public static IMetricsBuilder UseOpenTelemetry(
this IMetricsBuilder metricsBuilder,
Action<MeterProviderBuilder> configure)
{
Guard.ThrowIfNull(metricsBuilder);

RegisterMetricsListener(metricsBuilder.Services, configure);

return metricsBuilder;
}

internal static void RegisterMetricsListener(
IServiceCollection services,
Action<MeterProviderBuilder> configure)
{
Debug.Assert(services != null, "services was null");

Guard.ThrowIfNull(configure);

var builder = new MeterProviderBuilderBase(services!);

services!.TryAddEnumerable(
ServiceDescriptor.Singleton<IMetricsListener, OpenTelemetryMetricsListener>());

configure(builder);
}
}
12 changes: 12 additions & 0 deletions src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,18 @@ public void LoggerProcessStateSkipped(string type, string reason)
this.WriteEvent(51, type, reason);
}

[Event(52, Message = "Instrument '{0}', Meter '{1}' has been deactivated.", Level = EventLevel.Informational)]
public void MetricInstrumentDeactivated(string instrumentName, string meterName)
{
this.WriteEvent(52, instrumentName, meterName);
}

[Event(53, Message = "Instrument '{0}', Meter '{1}' has been removed.", Level = EventLevel.Informational)]
public void MetricInstrumentRemoved(string instrumentName, string meterName)
{
this.WriteEvent(53, instrumentName, meterName);
}

#if DEBUG
public class OpenTelemetryEventListener : EventListener
{
Expand Down
55 changes: 33 additions & 22 deletions src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,43 @@ public static class OpenTelemetryLoggingExtensions
/// <returns>The supplied <see cref="ILoggingBuilder"/> for call chaining.</returns>
public static ILoggingBuilder AddOpenTelemetry(
this ILoggingBuilder builder)
=> AddOpenTelemetryInternal(builder, configureBuilder: null, configureOptions: null);

/// <summary>
/// Adds an OpenTelemetry logger named 'OpenTelemetry' to the <see cref="ILoggerFactory"/>.
/// </summary>
/// <remarks><inheritdoc cref="AddOpenTelemetry(ILoggingBuilder)" path="/remarks"/></remarks>
/// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
/// <param name="configure">Optional configuration action.</param>
/// <returns>The supplied <see cref="ILoggingBuilder"/> for call chaining.</returns>
public static ILoggingBuilder AddOpenTelemetry(
this ILoggingBuilder builder,
Action<OpenTelemetryLoggerOptions>? configure)
=> AddOpenTelemetryInternal(builder, configureBuilder: null, configureOptions: configure);

private static ILoggingBuilder AddOpenTelemetryInternal(
ILoggingBuilder builder,
Action<LoggerProviderBuilder>? configureBuilder,
Action<OpenTelemetryLoggerOptions>? configureOptions)
{
Guard.ThrowIfNull(builder);

builder.AddConfiguration();

var services = builder.Services;

if (configureOptions != null)
{
// TODO: Move this below the RegisterLoggerProviderOptions call so
// that user-supplied delegate fires AFTER the options are bound to
// Logging:OpenTelemetry configuration.
services.Configure(configureOptions);
}

// Note: This will bind logger options element (eg "Logging:OpenTelemetry") to OpenTelemetryLoggerOptions
RegisterLoggerProviderOptions(builder.Services);
RegisterLoggerProviderOptions(services);

new LoggerProviderBuilderBase(builder.Services).ConfigureBuilder(
var loggingBuilder = new LoggerProviderBuilderBase(services).ConfigureBuilder(
(sp, logging) =>
{
var options = sp.GetRequiredService<IOptionsMonitor<OpenTelemetryLoggerOptions>>().CurrentValue;
Expand All @@ -78,7 +106,9 @@ public static ILoggingBuilder AddOpenTelemetry(
options.Processors.Clear();
});

builder.Services.TryAddEnumerable(
configureBuilder?.Invoke(loggingBuilder);

services.TryAddEnumerable(
ServiceDescriptor.Singleton<ILoggerProvider, OpenTelemetryLoggerProvider>(
sp => new OpenTelemetryLoggerProvider(
sp.GetRequiredService<LoggerProvider>(),
Expand Down Expand Up @@ -107,23 +137,4 @@ static void RegisterLoggerProviderOptions(IServiceCollection services)
LoggerProviderOptions.RegisterProviderOptions<OpenTelemetryLoggerOptions, OpenTelemetryLoggerProvider>(services);
}
}

/// <summary>
/// Adds an OpenTelemetry logger named 'OpenTelemetry' to the <see cref="ILoggerFactory"/>.
/// </summary>
/// <remarks><inheritdoc cref="AddOpenTelemetry(ILoggingBuilder)" path="/remarks"/></remarks>
/// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
/// <param name="configure">Optional configuration action.</param>
/// <returns>The supplied <see cref="ILoggingBuilder"/> for call chaining.</returns>
public static ILoggingBuilder AddOpenTelemetry(
this ILoggingBuilder builder,
Action<OpenTelemetryLoggerOptions>? configure)
{
if (configure != null)
{
builder.Services.Configure(configure);
}

return AddOpenTelemetry(builder);
}
}
Loading

0 comments on commit 1f7f931

Please sign in to comment.