From 4de9a9a10c1685f14f5035b8ea669cbd694204cb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Silva?=
<2493377+askpt@users.noreply.github.com>
Date: Thu, 21 Dec 2023 17:55:46 +0000
Subject: [PATCH 1/8] Adding class for MetricHookCustomDimensions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: André Silva <2493377+askpt@users.noreply.github.com>
---
.../MetricHookCustomDimensions.cs | 29 +++++++++++++++++++
1 file changed, 29 insertions(+)
create mode 100644 src/OpenFeature.Contrib.Hooks.Otel/MetricHookCustomDimensions.cs
diff --git a/src/OpenFeature.Contrib.Hooks.Otel/MetricHookCustomDimensions.cs b/src/OpenFeature.Contrib.Hooks.Otel/MetricHookCustomDimensions.cs
new file mode 100644
index 00000000..593186e5
--- /dev/null
+++ b/src/OpenFeature.Contrib.Hooks.Otel/MetricHookCustomDimensions.cs
@@ -0,0 +1,29 @@
+using System.Diagnostics;
+
+namespace OpenFeature.Contrib.Hooks.Otel
+{
+ ///
+ /// Represents a custom dimension list for a metric hook.
+ ///
+ public class MetricHookCustomDimensions
+ {
+ private readonly TagList _keyValuePairs = new TagList();
+
+ ///
+ /// Adds a custom dimension to the list.
+ ///
+ /// The key of the custom dimension.
+ /// The value of the custom dimension.
+ /// The custom dimension list.
+ public MetricHookCustomDimensions Add(string key, string value)
+ {
+ _keyValuePairs.Add(key, value);
+ return this;
+ }
+
+ internal TagList GetTagList()
+ {
+ return _keyValuePairs;
+ }
+ }
+}
\ No newline at end of file
From e700a536a24153b4e8c5ff72de96a6a1f31bbcab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Silva?=
<2493377+askpt@users.noreply.github.com>
Date: Thu, 21 Dec 2023 17:59:22 +0000
Subject: [PATCH 2/8] Add support for custom dimensions in MetricsHook
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: André Silva <2493377+askpt@users.noreply.github.com>
---
src/OpenFeature.Contrib.Hooks.Otel/MetricsHook.cs | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/OpenFeature.Contrib.Hooks.Otel/MetricsHook.cs b/src/OpenFeature.Contrib.Hooks.Otel/MetricsHook.cs
index f5fd56f9..f9e77e8e 100644
--- a/src/OpenFeature.Contrib.Hooks.Otel/MetricsHook.cs
+++ b/src/OpenFeature.Contrib.Hooks.Otel/MetricsHook.cs
@@ -22,12 +22,16 @@ public class MetricsHook : Hook
private readonly Counter _evaluationRequestCounter;
private readonly Counter _evaluationSuccessCounter;
private readonly Counter _evaluationErrorCounter;
+ private readonly TagList _customDimensionsTagList;
///
/// Initializes a new instance of the class.
///
- public MetricsHook()
+ /// The optional custom dimensions.
+ public MetricsHook(MetricHookCustomDimensions customDimensions = null)
{
+ _customDimensionsTagList = customDimensions?.GetTagList() ?? new TagList();
+
var meter = new Meter(InstrumentationName, InstrumentationVersion);
_evaluationActiveUpDownCounter = meter.CreateUpDownCounter(MetricsConstants.ActiveCountName, description: MetricsConstants.ActiveDescription);
From e0be24ef2c37d3ace5d19b04f6c77890a4c9f0f6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Silva?=
<2493377+askpt@users.noreply.github.com>
Date: Thu, 21 Dec 2023 18:06:46 +0000
Subject: [PATCH 3/8] Refactor MetricHookCustomDimensions to use
List instead of TagList
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: André Silva <2493377+askpt@users.noreply.github.com>
---
.../MetricHookCustomDimensions.cs | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/src/OpenFeature.Contrib.Hooks.Otel/MetricHookCustomDimensions.cs b/src/OpenFeature.Contrib.Hooks.Otel/MetricHookCustomDimensions.cs
index 593186e5..70493a4c 100644
--- a/src/OpenFeature.Contrib.Hooks.Otel/MetricHookCustomDimensions.cs
+++ b/src/OpenFeature.Contrib.Hooks.Otel/MetricHookCustomDimensions.cs
@@ -1,3 +1,5 @@
+using System;
+using System.Collections.Generic;
using System.Diagnostics;
namespace OpenFeature.Contrib.Hooks.Otel
@@ -7,7 +9,7 @@ namespace OpenFeature.Contrib.Hooks.Otel
///
public class MetricHookCustomDimensions
{
- private readonly TagList _keyValuePairs = new TagList();
+ private readonly List> _keyValuePairs = new List>();
///
/// Adds a custom dimension to the list.
@@ -15,15 +17,15 @@ public class MetricHookCustomDimensions
/// The key of the custom dimension.
/// The value of the custom dimension.
/// The custom dimension list.
- public MetricHookCustomDimensions Add(string key, string value)
+ public MetricHookCustomDimensions Add(string key, object value)
{
- _keyValuePairs.Add(key, value);
+ _keyValuePairs.Add(new KeyValuePair(key, value));
return this;
}
- internal TagList GetTagList()
+ internal KeyValuePair[] GetTagList()
{
- return _keyValuePairs;
+ return _keyValuePairs.ToArray();
}
}
}
\ No newline at end of file
From 674b984af66c9c3743780160b679129e23811d22 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Silva?=
<2493377+askpt@users.noreply.github.com>
Date: Thu, 21 Dec 2023 18:08:28 +0000
Subject: [PATCH 4/8] Update MetricsHook to use KeyValuePair array for custom
dimensions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: André Silva <2493377+askpt@users.noreply.github.com>
---
src/OpenFeature.Contrib.Hooks.Otel/MetricsHook.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/OpenFeature.Contrib.Hooks.Otel/MetricsHook.cs b/src/OpenFeature.Contrib.Hooks.Otel/MetricsHook.cs
index f9e77e8e..1f512660 100644
--- a/src/OpenFeature.Contrib.Hooks.Otel/MetricsHook.cs
+++ b/src/OpenFeature.Contrib.Hooks.Otel/MetricsHook.cs
@@ -22,7 +22,7 @@ public class MetricsHook : Hook
private readonly Counter _evaluationRequestCounter;
private readonly Counter _evaluationSuccessCounter;
private readonly Counter _evaluationErrorCounter;
- private readonly TagList _customDimensionsTagList;
+ private readonly KeyValuePair[] _customDimensionsTagList;
///
/// Initializes a new instance of the class.
@@ -30,7 +30,7 @@ public class MetricsHook : Hook
/// The optional custom dimensions.
public MetricsHook(MetricHookCustomDimensions customDimensions = null)
{
- _customDimensionsTagList = customDimensions?.GetTagList() ?? new TagList();
+ _customDimensionsTagList = customDimensions?.GetTagList() ?? new KeyValuePair[] { };
var meter = new Meter(InstrumentationName, InstrumentationVersion);
@@ -77,7 +77,7 @@ public override Task Before(HookContext context, IReadO
/// The evaluation context.
public override Task After(HookContext context, FlagEvaluationDetails details, IReadOnlyDictionary hints = null)
{
- var tagList = new TagList
+ var tagList = new TagList(_customDimensionsTagList)
{
{ MetricsConstants.KeyAttr, context.FlagKey },
{ MetricsConstants.ProviderNameAttr, context.ProviderMetadata.Name },
From 145002210d0067ed8e98d73b8078b69e48d49764 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Silva?=
<2493377+askpt@users.noreply.github.com>
Date: Fri, 22 Dec 2023 08:20:46 +0000
Subject: [PATCH 5/8] Remove unused using statements and update MetricsHook
constructor
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: André Silva <2493377+askpt@users.noreply.github.com>
---
.../MetricHookCustomDimensions.cs | 2 --
src/OpenFeature.Contrib.Hooks.Otel/MetricsHook.cs | 2 +-
2 files changed, 1 insertion(+), 3 deletions(-)
diff --git a/src/OpenFeature.Contrib.Hooks.Otel/MetricHookCustomDimensions.cs b/src/OpenFeature.Contrib.Hooks.Otel/MetricHookCustomDimensions.cs
index 70493a4c..6e30a71b 100644
--- a/src/OpenFeature.Contrib.Hooks.Otel/MetricHookCustomDimensions.cs
+++ b/src/OpenFeature.Contrib.Hooks.Otel/MetricHookCustomDimensions.cs
@@ -1,6 +1,4 @@
-using System;
using System.Collections.Generic;
-using System.Diagnostics;
namespace OpenFeature.Contrib.Hooks.Otel
{
diff --git a/src/OpenFeature.Contrib.Hooks.Otel/MetricsHook.cs b/src/OpenFeature.Contrib.Hooks.Otel/MetricsHook.cs
index 1f512660..52ebb8c9 100644
--- a/src/OpenFeature.Contrib.Hooks.Otel/MetricsHook.cs
+++ b/src/OpenFeature.Contrib.Hooks.Otel/MetricsHook.cs
@@ -30,7 +30,7 @@ public class MetricsHook : Hook
/// The optional custom dimensions.
public MetricsHook(MetricHookCustomDimensions customDimensions = null)
{
- _customDimensionsTagList = customDimensions?.GetTagList() ?? new KeyValuePair[] { };
+ _customDimensionsTagList = customDimensions?.GetTagList() ?? Array.Empty>();
var meter = new Meter(InstrumentationName, InstrumentationVersion);
From 3edc4623311d4884c93daa2ed03602bef496e529 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Silva?=
<2493377+askpt@users.noreply.github.com>
Date: Fri, 22 Dec 2023 13:03:46 +0000
Subject: [PATCH 6/8] Adding documentation.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: André Silva <2493377+askpt@users.noreply.github.com>
---
src/OpenFeature.Contrib.Hooks.Otel/README.md | 60 ++++++++++++++++++--
1 file changed, 54 insertions(+), 6 deletions(-)
diff --git a/src/OpenFeature.Contrib.Hooks.Otel/README.md b/src/OpenFeature.Contrib.Hooks.Otel/README.md
index c15b5806..d8f91d43 100644
--- a/src/OpenFeature.Contrib.Hooks.Otel/README.md
+++ b/src/OpenFeature.Contrib.Hooks.Otel/README.md
@@ -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.
@@ -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.
From a62f7e8f1ffdad93f05e9a2540a823b7b496b34c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Silva?=
<2493377+askpt@users.noreply.github.com>
Date: Fri, 22 Dec 2023 13:12:37 +0000
Subject: [PATCH 7/8] Added unit tests.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: André Silva <2493377+askpt@users.noreply.github.com>
---
.../OpenFeature.Contrib.Hooks.Otel.csproj | 4 ++
.../MetricHookCustomDimensionsTest.cs | 38 +++++++++++++++++++
...OpenFeature.Contrib.Hooks.Otel.Test.csproj | 5 +--
3 files changed, 44 insertions(+), 3 deletions(-)
create mode 100644 test/OpenFeature.Contrib.Hooks.Otel.Test/MetricHookCustomDimensionsTest.cs
diff --git a/src/OpenFeature.Contrib.Hooks.Otel/OpenFeature.Contrib.Hooks.Otel.csproj b/src/OpenFeature.Contrib.Hooks.Otel/OpenFeature.Contrib.Hooks.Otel.csproj
index a4f54a75..005adbb9 100644
--- a/src/OpenFeature.Contrib.Hooks.Otel/OpenFeature.Contrib.Hooks.Otel.csproj
+++ b/src/OpenFeature.Contrib.Hooks.Otel/OpenFeature.Contrib.Hooks.Otel.csproj
@@ -17,4 +17,8 @@
+
+
+
+
diff --git a/test/OpenFeature.Contrib.Hooks.Otel.Test/MetricHookCustomDimensionsTest.cs b/test/OpenFeature.Contrib.Hooks.Otel.Test/MetricHookCustomDimensionsTest.cs
new file mode 100644
index 00000000..a3242f9a
--- /dev/null
+++ b/test/OpenFeature.Contrib.Hooks.Otel.Test/MetricHookCustomDimensionsTest.cs
@@ -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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/OpenFeature.Contrib.Hooks.Otel.Test/OpenFeature.Contrib.Hooks.Otel.Test.csproj b/test/OpenFeature.Contrib.Hooks.Otel.Test/OpenFeature.Contrib.Hooks.Otel.Test.csproj
index c396ebec..8cad51e0 100644
--- a/test/OpenFeature.Contrib.Hooks.Otel.Test/OpenFeature.Contrib.Hooks.Otel.Test.csproj
+++ b/test/OpenFeature.Contrib.Hooks.Otel.Test/OpenFeature.Contrib.Hooks.Otel.Test.csproj
@@ -4,9 +4,8 @@
-
-
-
+
+
From 37f8b44d9051dc5eadbbd5565309fa833ca9ae53 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Silva?=
<2493377+askpt@users.noreply.github.com>
Date: Wed, 10 Jan 2024 17:20:18 +0000
Subject: [PATCH 8/8] Initial attempt for custom function.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: André Silva <2493377+askpt@users.noreply.github.com>
---
src/OpenFeature.Contrib.Hooks.Otel/MetricsHook.cs | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/OpenFeature.Contrib.Hooks.Otel/MetricsHook.cs b/src/OpenFeature.Contrib.Hooks.Otel/MetricsHook.cs
index 52ebb8c9..466defa9 100644
--- a/src/OpenFeature.Contrib.Hooks.Otel/MetricsHook.cs
+++ b/src/OpenFeature.Contrib.Hooks.Otel/MetricsHook.cs
@@ -22,16 +22,15 @@ public class MetricsHook : Hook
private readonly Counter _evaluationRequestCounter;
private readonly Counter _evaluationSuccessCounter;
private readonly Counter _evaluationErrorCounter;
- private readonly KeyValuePair[] _customDimensionsTagList;
+ private readonly Func, KeyValuePair[]> _customDimensions;
///
/// Initializes a new instance of the class.
///
/// The optional custom dimensions.
- public MetricsHook(MetricHookCustomDimensions customDimensions = null)
+ public MetricsHook(Func, KeyValuePair[]> customDimensions = null)
{
- _customDimensionsTagList = customDimensions?.GetTagList() ?? Array.Empty>();
-
+ _customDimensions = customDimensions;
var meter = new Meter(InstrumentationName, InstrumentationVersion);
_evaluationActiveUpDownCounter = meter.CreateUpDownCounter(MetricsConstants.ActiveCountName, description: MetricsConstants.ActiveDescription);
@@ -77,7 +76,8 @@ public override Task Before(HookContext context, IReadO
/// The evaluation context.
public override Task After(HookContext context, FlagEvaluationDetails details, IReadOnlyDictionary hints = null)
{
- var tagList = new TagList(_customDimensionsTagList)
+ var initalTagList = _customDimensions != null ? _customDimensions(details) : Array.Empty>();
+ var tagList = new TagList(initalTagList)
{
{ MetricsConstants.KeyAttr, context.FlagKey },
{ MetricsConstants.ProviderNameAttr, context.ProviderMetadata.Name },