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

feat: Add custom dimensions for metrics #122

Closed
wants to merge 10 commits into from
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Collections.Generic;

namespace OpenFeature.Contrib.Hooks.Otel
{
/// <summary>
/// Represents a custom dimension list for a metric hook.
/// </summary>
public class MetricHookCustomDimensions
{
private readonly List<KeyValuePair<string, object>> _keyValuePairs = new List<KeyValuePair<string, object>>();

/// <summary>
/// Adds a custom dimension to the list.
/// </summary>
/// <param name="key">The key of the custom dimension.</param>
/// <param name="value">The value of the custom dimension.</param>
/// <returns>The custom dimension list.</returns>
public MetricHookCustomDimensions Add(string key, object value)
{
_keyValuePairs.Add(new KeyValuePair<string, object>(key, value));
return this;
}

internal KeyValuePair<string, object>[] GetTagList()
{
return _keyValuePairs.ToArray();
}
}
}
8 changes: 6 additions & 2 deletions src/OpenFeature.Contrib.Hooks.Otel/MetricsHook.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@ public class MetricsHook : Hook
private readonly Counter<long> _evaluationRequestCounter;
private readonly Counter<long> _evaluationSuccessCounter;
private readonly Counter<long> _evaluationErrorCounter;
private readonly Func<FlagEvaluationDetails<T>, KeyValuePair<string, object>[]> _customDimensions;

/// <summary>
/// Initializes a new instance of the <see cref="MetricsHook"/> class.
/// </summary>
public MetricsHook()
/// <param name="customDimensions">The optional custom dimensions.</param>
public MetricsHook(Func<FlagEvaluationDetails<T>, KeyValuePair<string, object>[]> customDimensions = null)
{
_customDimensions = customDimensions;
var meter = new Meter(InstrumentationName, InstrumentationVersion);

_evaluationActiveUpDownCounter = meter.CreateUpDownCounter<long>(MetricsConstants.ActiveCountName, description: MetricsConstants.ActiveDescription);
Expand Down Expand Up @@ -73,7 +76,8 @@ public override Task<EvaluationContext> Before<T>(HookContext<T> context, IReadO
/// <returns>The evaluation context.</returns>
public override Task After<T>(HookContext<T> context, FlagEvaluationDetails<T> details, IReadOnlyDictionary<string, object> hints = null)
{
var tagList = new TagList
var initalTagList = _customDimensions != null ? _customDimensions(details) : Array.Empty<KeyValuePair<string, object>>();
var tagList = new TagList(initalTagList)
{
{ MetricsConstants.KeyAttr, context.FlagKey },
{ MetricsConstants.ProviderNameAttr, context.ProviderMetadata.Name },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.4.0" />
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="OpenFeature.Contrib.Hooks.Otel.Test" />
</ItemGroup>

</Project>
60 changes: 54 additions & 6 deletions src/OpenFeature.Contrib.Hooks.Otel/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,12 @@ For this hook to function correctly a global `MeterProvider` must be set.

Below are the metrics extracted by this hook and dimensions they carry:

| Metric key | Description | Unit | Dimensions |
| -------------------------------------- | ------------------------------- | ------------ | ----------------------------------- |
| feature_flag.evaluation_requests_total | Number of evaluation requests | {request} | key, provider name |
| feature_flag.evaluation_success_total | Flag evaluation successes | {impression} | key, provider name, reason, variant |
| feature_flag.evaluation_error_total | Flag evaluation errors | Counter | key, provider name |
| feature_flag.evaluation_active_count | Active flag evaluations counter | Counter | key |
| Metric key | Description | Unit | Dimensions |
| -------------------------------------- | ------------------------------- | ------------ | -------------------------------------------------------- |
| feature_flag.evaluation_requests_total | Number of evaluation requests | {request} | key, provider name |
| feature_flag.evaluation_success_total | Flag evaluation successes | {impression} | key, provider name, reason, variant, custom dimensions\* |
| feature_flag.evaluation_error_total | Flag evaluation errors | Counter | key, provider name |
| feature_flag.evaluation_active_count | Active flag evaluations counter | Counter | key |

Consider the following code example for usage.

Expand Down Expand Up @@ -125,6 +125,54 @@ namespace OpenFeatureTestApp

After running this example, you should be able to see some metrics being generated into the console.

### Custom dimensions

The metrics hook can be enriched with custom dimensions. This is only available for the `feature_flag.evaluation_success_total` metric.
In order to use it, you need to create a instance of the `MetricsHook` class with a dictionary of custom dimensions.

See example:

```csharp
using OpenFeature.Contrib.Providers.Flagd;
using OpenFeature;
using OpenFeature.Contrib.Hooks.Otel;
using OpenTelemetry;
using OpenTelemetry.Metrics;

namespace OpenFeatureTestApp
{
class Hello {
static void Main(string[] args) {

// set up the OpenTelemetry OTLP exporter
var meterProvider = Sdk.CreateMeterProviderBuilder()
.AddMeter("OpenFeature.Contrib.Hooks.Otel")
.ConfigureResource(r => r.AddService("openfeature-test"))
.AddConsoleExporter()
.Build();

// add the Otel Hook to the OpenFeature instance
var options = new MetricHookCustomDimensions()
.Add("key", "value")
.Add("test", "test");
OpenFeature.Api.Instance.AddHooks(new MetricsHook(options));

var flagdProvider = new FlagdProvider(new Uri("http://localhost:8013"));

// Set the flagdProvider as the provider for the OpenFeature SDK
OpenFeature.Api.Instance.SetProvider(flagdProvider);

var client = OpenFeature.Api.Instance.GetClient("my-app");

var val = client.GetBooleanValue("myBoolFlag", false, null);

// Print the value of the 'myBoolFlag' feature flag
System.Console.WriteLine(val.Result.ToString());
}
}
}
```

## License

Apache 2.0 - See [LICENSE](./../../LICENSE) for more information.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Xunit;

namespace OpenFeature.Contrib.Hooks.Otel.Test
{
public class MetricHookCustomDimensionsTest
{
[Fact]
public void Adds_CustomDimension_HasValues()
{
// Arrange
var customDimensions = new MetricHookCustomDimensions();
string key = "dimensionKey";
object value = "dimensionValue";

// Act
customDimensions.Add(key, value);

// Assert
var tagList = customDimensions.GetTagList();
Assert.Single(tagList);
Assert.Equal(key, tagList[0].Key);
Assert.Equal(value, tagList[0].Value);
}

[Fact]
public void CustomDimensionToList_IsEmpty()
{
// Arrange
var customDimensions = new MetricHookCustomDimensions();

// Act

// Assert
var tagList = customDimensions.GetTagList();
Assert.Empty(tagList);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
<ProjectReference Include="..\..\src\OpenFeature.Contrib.Hooks.Otel\OpenFeature.Contrib.Hooks.Otel.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="OpenTelemetry" Version="1.4.0" />
<PackageReference Include="OpenTelemetry.Exporter.InMemory" Version="1.4.0" />
<ItemGroup>
<PackageReference Include="OpenTelemetry.Exporter.InMemory" Version="1.4.0" />
</ItemGroup>

</Project>
Loading