Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Instrumentation.EventCounters] DI support for configuring #2078

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ OpenTelemetry.Instrumentation.EventCounters.EventCountersInstrumentationOptions.
OpenTelemetry.Instrumentation.EventCounters.EventCountersInstrumentationOptions.RefreshIntervalSecs.get -> int
OpenTelemetry.Instrumentation.EventCounters.EventCountersInstrumentationOptions.RefreshIntervalSecs.set -> void
OpenTelemetry.Metrics.MeterProviderBuilderExtensions
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddEventCountersInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, System.Action<OpenTelemetry.Instrumentation.EventCounters.EventCountersInstrumentationOptions!>? configure = null) -> OpenTelemetry.Metrics.MeterProviderBuilder!
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddEventCountersInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder! builder) -> OpenTelemetry.Metrics.MeterProviderBuilder!
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddEventCountersInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, string? name, System.Action<OpenTelemetry.Instrumentation.EventCounters.EventCountersInstrumentationOptions!>? configure) -> OpenTelemetry.Metrics.MeterProviderBuilder!
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddEventCountersInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, System.Action<OpenTelemetry.Instrumentation.EventCounters.EventCountersInstrumentationOptions!>! configure) -> OpenTelemetry.Metrics.MeterProviderBuilder!
10 changes: 10 additions & 0 deletions src/OpenTelemetry.Instrumentation.EventCounters/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

## Unreleased

* Added support for configuring `EventCounters` instrumentation via dependency
injection APIs: You can now enable `EventCounters` instrumentation and
configure options using the `AddEventCountersInstrumentation` method alongside
`IDeferredMeterProviderBuilder`. Configuration can be provided through
an `IConfigurationSection`, allowing easier setup through app configuration.
New environment variables introduced for further customization:
`OTEL_DOTNET_EVENTCOUNTERS_REFRESH_INTERVAL_SECS`: Specifies the refresh interval.
`OTEL_DOTNET_EVENTCOUNTERS_SOURCES`: Defines the sources.
([#2078](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/2078))

* `Meter.Version` is set to NuGet package version.
([#1624](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1624))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,23 @@
// SPDX-License-Identifier: Apache-2.0

using System.Diagnostics.Tracing;
using Microsoft.Extensions.Configuration;

namespace OpenTelemetry.Instrumentation.EventCounters;

/// <summary>
/// EventSource events emitted from the project.
/// </summary>
[EventSource(Name = "OpenTelemetry-Instrumentation-EventCounters")]
internal sealed class EventCountersInstrumentationEventSource : EventSource
internal sealed class EventCountersInstrumentationEventSource : EventSource, IConfigurationExtensionsLogger
{
public static readonly EventCountersInstrumentationEventSource Log = new();

void IConfigurationExtensionsLogger.LogInvalidConfigurationValue(string key, string value)
{
this.InvalidConfigurationValue(key, value);
}

[Event(1, Level = EventLevel.Warning, Message = "Error while writing event from source: {0} - {1}.")]
internal void ErrorWhileWritingEvent(string eventSourceName, string exceptionMessage)
{
Expand Down Expand Up @@ -42,4 +48,10 @@ internal void IgnoreNonEventCountersName(string eventSourceName)
{
this.WriteEvent(5, eventSourceName);
}

[Event(6, Level = EventLevel.Warning, Message = "Configuration key '{0}' has an invalid value: '{1}'")]
internal void InvalidConfigurationValue(string key, string value)
{
this.WriteEvent(6, key, value);
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,47 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using System.Diagnostics;
using Microsoft.Extensions.Configuration;

namespace OpenTelemetry.Instrumentation.EventCounters;

/// <summary>
/// EventCounters Instrumentation Options.
/// </summary>
public class EventCountersInstrumentationOptions
{
internal readonly HashSet<string> EventSourceNames = new();
internal readonly HashSet<string> EventSourceNames = [];

/// <summary>
/// Initializes a new instance of the <see cref="EventCountersInstrumentationOptions"/> class.
/// </summary>
public EventCountersInstrumentationOptions()
: this(new ConfigurationBuilder().AddEnvironmentVariables().Build())
{
}

internal EventCountersInstrumentationOptions(IConfiguration configuration)
{
Debug.Assert(configuration != null, "configuration was null");

if (configuration!.TryGetIntValue(
EventCountersInstrumentationEventSource.Log,
"OTEL_DOTNET_EVENTCOUNTERS_REFRESH_INTERVAL_SECS",
out var refreshIntervalSecs))
{
this.RefreshIntervalSecs = refreshIntervalSecs;
}

if (configuration!.TryGetValue<string[]>(
EventCountersInstrumentationEventSource.Log,
"OTEL_DOTNET_EVENTCOUNTERS_SOURCES",
this.TrySplitString,
out var eventSourceNames) && eventSourceNames != null)
{
this.AddEventSources(eventSourceNames);
}
}

/// <summary>
/// Gets or sets the subscription interval in seconds for reading values
Expand Down Expand Up @@ -39,4 +72,22 @@ internal bool ShouldListenToSource(string eventSourceName)
{
return this.EventSourceNames.Contains(eventSourceName);
}

/// <summary>
/// Tries to split the provided string using a comma as the separator.
/// </summary>
/// <param name="value">The string to split.</param>
/// <param name="parsedValue">The array of strings after the split.</param>
/// <returns><c>true</c> if the split was successful; otherwise, <c>false</c>.</returns>
private bool TrySplitString(string value, out string[]? parsedValue)
{
if (!string.IsNullOrEmpty(value))
{
parsedValue = value.Split(',');
return true;
}

parsedValue = null;
return false;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenTelemetry.Instrumentation.EventCounters;
using OpenTelemetry.Internal;

Expand All @@ -15,18 +17,60 @@ public static class MeterProviderBuilderExtensions
/// Enables EventCounter instrumentation.
/// </summary>
/// <param name="builder"><see cref="MeterProviderBuilder"/> being configured.</param>
/// <returns>The instance of <see cref="MeterProviderBuilder"/> to chain the calls.</returns>
public static MeterProviderBuilder AddEventCountersInstrumentation(this MeterProviderBuilder builder)
=> AddEventCountersInstrumentation(builder, name: null, configure: null);

/// <summary>
/// Enables EventCounter instrumentation.
/// </summary>
/// <param name="builder"><see cref="MeterProviderBuilder"/> being configured.</param>
/// <param name="configure">EventCounters instrumentation options.</param>
/// <returns>The instance of <see cref="MeterProviderBuilder"/> to chain the calls.</returns>
public static MeterProviderBuilder AddEventCountersInstrumentation(
this MeterProviderBuilder builder,
Action<EventCountersInstrumentationOptions> configure)
=> AddEventCountersInstrumentation(builder, name: null, configure: configure);

/// <summary>
/// Enables EventCounter instrumentation.
/// </summary>
/// <param name="builder"><see cref="MeterProviderBuilder"/> being configured.</param>
/// <param name="name">The name of the instrumentation.</param>
/// <param name="configure">EventCounters instrumentation options.</param>
/// <returns>The instance of <see cref="MeterProviderBuilder"/> to chain the calls.</returns>
public static MeterProviderBuilder AddEventCountersInstrumentation(
this MeterProviderBuilder builder,
Action<EventCountersInstrumentationOptions>? configure = null)
string? name,
Action<EventCountersInstrumentationOptions>? configure)
{
Guard.ThrowIfNull(builder);

name ??= Options.DefaultName;

var options = new EventCountersInstrumentationOptions();
configure?.Invoke(options);

builder.AddMeter(EventCountersMetrics.MeterInstance.Name);
return builder.AddInstrumentation(() => new EventCountersMetrics(options));
builder.ConfigureServices(services =>
{
if (configure != null)
{
services.Configure(name, configure);
}
});

if (builder is IDeferredMeterProviderBuilder deferredMeterProviderBuilder)
{
deferredMeterProviderBuilder.Configure((sp, builder) =>
{
builder.AddMeter(EventCountersMetrics.MeterInstance.Name);
});
}

return builder.AddInstrumentation(sp =>
{
var options = sp.GetRequiredService<IOptionsMonitor<EventCountersInstrumentationOptions>>().Get(name);
return new EventCountersMetrics(options);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="OpenTelemetry.Api" Version="$(OpenTelemetryCoreLatestVersion)" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="$(MicrosoftExtensionsConfigurationPkgVer)" />
<PackageReference Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsOptionsPkgVer)" />
<PackageReference Include="OpenTelemetry.Api.ProviderBuilderExtensions" Version="$(OpenTelemetryCoreLatestVersion)" />
</ItemGroup>

<ItemGroup>
<Compile Include="$(RepoRoot)\src\Shared\AssemblyVersionExtensions.cs" Link="Includes\AssemblyVersionExtensions.cs" />
<Compile Include="$(RepoRoot)\src\Shared\Configuration\*.cs" Link="Includes\Configuration\%(Filename).cs" />
<Compile Include="$(RepoRoot)\src\Shared\EnvironmentVariables\*.cs" Link="Includes\EnvironmentVariables\%(Filename).cs" />
<Compile Include="$(RepoRoot)\src\Shared\Guard.cs" Link="Includes\Guard.cs" />
</ItemGroup>

Expand Down
57 changes: 53 additions & 4 deletions src/OpenTelemetry.Instrumentation.EventCounters/README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# EventCounters Instrumentation for OpenTelemetry .NET

| Status | |
| ------------- |-----------|
| Stability | [Alpha](../../README.md#alpha)|
| Code Owners | [@hananiel](https://github.com/hananiel), [@mic-max](https://github.com/mic-max)|
| Status | |
| ----------- | -------------------------------------------------------------------------------- |
| Stability | [Alpha](../../README.md#alpha) |
| Code Owners | [@hananiel](https://github.com/hananiel), [@mic-max](https://github.com/mic-max) |

[![NuGet version badge](https://img.shields.io/nuget/v/OpenTelemetry.Instrumentation.EventCounters)](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.EventCounters)
[![NuGet download count badge](https://img.shields.io/nuget/dt/OpenTelemetry.Instrumentation.EventCounters)](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.EventCounters)
Expand Down Expand Up @@ -32,6 +32,8 @@ dotnet add package OpenTelemetry.Instrumentation.EventCounters --prerelease

### Step 2: Enable EventCounters Instrumentation

#### Using Direct Configuration

EventCounters instrumentation should be enabled at application startup using the
`AddEventCountersInstrumentation` extension on the `MeterProviderBuilder`:

Expand All @@ -50,6 +52,53 @@ requires adding the package
[`OpenTelemetry.Exporter.Prometheus`](https://github.com/open-telemetry/opentelemetry-dotnet/blob/main/src/OpenTelemetry.Exporter.Prometheus.HttpListener/README.md)
to the application.

#### Using Dependency Injection (DI)

To enable EventCounters instrumentation via DI, configure it as follows:

```csharp
public void ConfigureServices(IServiceCollection services)
{
services.AddOpenTelemetry().WithMetrics(builder =>
{
builder.AddEventCountersInstrumentation(options =>
{
options.RefreshIntervalSecs = 1;
options.AddEventSources("MyEventSource");
})
.AddConsoleExporter();
});
}
```

This method allows providing options via `IConfigurationSection`
and integrates seamlessly with DI-based setups.

Alternatively, you can configure the EventCounters instrumentation using settings
from an IConfigurationSection. This allows you to integrate configuration with
your app's settings (e.g., appsettings.json or environment variables).

Example using IConfigurationSection:

```csharp
public void ConfigureServices(IServiceCollection services)
{
services.AddOpenTelemetryMetrics((builder, config) =>
{
var eventCountersSection = config.GetSection("EventCountersOptions");
builder.AddEventCountersInstrumentation(eventCountersSection);
});
}
```

This method allows seamless integration with DI-based setups and leverages
configuration files or environment variables:

| Parameter | Type | Description | Example |
| ------------------------------------------------- | -------------------- | -------------------------------------------------- | ------------------------------- |
| `OTEL_DOTNET_EVENTCOUNTERS_REFRESH_INTERVAL_SECS` | Number | Specifies the refresh interval for event counters. | 1 |
| `OTEL_DOTNET_EVENTCOUNTERS_SOURCES` | Comma-separated list | Defines the sources for event counters. | "MyEventSource1,MyEventSource2" |

### Step 3: Create EventCounters

Learn about [EventCounters in
Expand Down
2 changes: 2 additions & 0 deletions src/Shared/Options/SingletonOptionsManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ namespace Microsoft.Extensions.Options;
#if NET
internal sealed class SingletonOptionsManager<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TOptions> : IOptionsMonitor<TOptions>, IOptionsSnapshot<TOptions>
#else
#pragma warning disable CA1812 // Is an internal class that is apparently never instantiated
internal sealed class SingletonOptionsManager<TOptions> : IOptionsMonitor<TOptions>, IOptionsSnapshot<TOptions>
#pragma warning restore CA1812 // Is an internal class that is apparently never instantiated
#endif
where TOptions : class
{
Expand Down
Loading