From 00f75fcd9ec00b39f9c75ba8178dbaa688a12ebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <2493377+askpt@users.noreply.github.com> Date: Thu, 6 Mar 2025 16:37:19 +0000 Subject: [PATCH 01/20] feat: Add TelemetryAttributes for OpenTelemetry compliant event logging 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> --- .../Telemetry/TelemetryAttributes.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/OpenFeature/Telemetry/TelemetryAttributes.cs diff --git a/src/OpenFeature/Telemetry/TelemetryAttributes.cs b/src/OpenFeature/Telemetry/TelemetryAttributes.cs new file mode 100644 index 00000000..fa60cf15 --- /dev/null +++ b/src/OpenFeature/Telemetry/TelemetryAttributes.cs @@ -0,0 +1,53 @@ +namespace OpenFeature.Telemetry; + +/// +/// The attributes of an OpenTelemetry compliant event for flag evaluation. +/// +/// +public static class TelemetryAttributes +{ + /// + /// The lookup key of the feature flag. + /// + public const string Key = "feature_flag.key"; + + /// + /// Describes a class of error the operation ended with. + /// + public const string ErrorCode = "error.type"; + + /// + /// A semantic identifier for an evaluated flag value. + /// + public const string Variant = "feature_flag.variant"; + + /// + /// The unique identifier for the flag evaluation context. For example, the targeting key. + /// + public const string ContextId = "feature_flag.context.id"; + + /// + /// A message explaining the nature of an error occurring during flag evaluation. + /// + public const string ErrorMessage = "feature_flag.evaluation.error.message"; + + /// + /// The reason code which shows how a feature flag value was determined. + /// + public const string Reason = "feature_flag.evaluation.reason"; + + /// + /// Describes a class of error the operation ended with. + /// + public const string Provider = "feature_flag.provider_name"; + + /// + /// The identifier of the flag set to which the feature flag belongs. + /// + public const string FlagSetId = "feature_flag.set.id"; + + /// + /// The version of the ruleset used during the evaluation. This may be any stable value which uniquely identifies the ruleset. + /// + public const string Version = "feature_flag.version"; +} From c6287eb54030903a4d3b67f5c302a26f8da7f2ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <2493377+askpt@users.noreply.github.com> Date: Thu, 6 Mar 2025 16:38:56 +0000 Subject: [PATCH 02/20] feat: Add TelemetryEvaluationData for feature flag event logging 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> --- .../Telemetry/TelemetryEvaluationData.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/OpenFeature/Telemetry/TelemetryEvaluationData.cs diff --git a/src/OpenFeature/Telemetry/TelemetryEvaluationData.cs b/src/OpenFeature/Telemetry/TelemetryEvaluationData.cs new file mode 100644 index 00000000..f143dc02 --- /dev/null +++ b/src/OpenFeature/Telemetry/TelemetryEvaluationData.cs @@ -0,0 +1,20 @@ +namespace OpenFeature.Telemetry; + +/** +* Event data, sometimes referred to as "body", is specific to a specific event. +* In this case, the event is `feature_flag.evaluation`. That's why the prefix +* is omitted from the values. +* @see https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/ +*/ +public static class TelemetryEvaluationData +{ + /** + * The evaluated value of the feature flag. + * + * - type: `undefined` + * - requirement level: `conditionally required` + * - condition: variant is not defined on the evaluation details + * - example: `#ff0000`; `1`; `true` + */ + public const string Value = "value"; +} From 38a110a2954431c2d8e6f268a97ff8e6263faa28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <2493377+askpt@users.noreply.github.com> Date: Thu, 6 Mar 2025 16:40:32 +0000 Subject: [PATCH 03/20] feat: Add TelemetryFlagMetadata for flag metadata attributes 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> --- .../Telemetry/TelemetryFlagMetadata.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/OpenFeature/Telemetry/TelemetryFlagMetadata.cs diff --git a/src/OpenFeature/Telemetry/TelemetryFlagMetadata.cs b/src/OpenFeature/Telemetry/TelemetryFlagMetadata.cs new file mode 100644 index 00000000..3bd0a258 --- /dev/null +++ b/src/OpenFeature/Telemetry/TelemetryFlagMetadata.cs @@ -0,0 +1,25 @@ +namespace OpenFeature.Telemetry; + +/** + * Well-known flag metadata attributes for telemetry events. + * @see https://openfeature.dev/specification/appendix-d#flag-metadata + */ +public static class TelemetryFlagMetadata +{ + /** + * The context identifier returned in the flag metadata uniquely identifies + * the subject of the flag evaluation. If not available, the targeting key + * should be used. + */ + public const string ContextId = "contextId"; + + /** + * A logical identifier for the flag set. + */ + public const string FlagSetId = "flagSetId"; + + /** + * A version string (format unspecified) for the flag or flag set. + */ + public const string Version = "version"; +} From 75330fe69e5e28b0140386247ffc8425be97148f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <2493377+askpt@users.noreply.github.com> Date: Thu, 6 Mar 2025 17:08:43 +0000 Subject: [PATCH 04/20] feat: Rename TelemetryAttributes to TelemetryConstants and add EvaluationEventFactory for event creation 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> --- .../Telemetry/EvaluationEventFactory.cs | 57 +++++++++++++++++++ ...tryAttributes.cs => TelemetryConstants.cs} | 2 +- 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 src/OpenFeature/Telemetry/EvaluationEventFactory.cs rename src/OpenFeature/Telemetry/{TelemetryAttributes.cs => TelemetryConstants.cs} (97%) diff --git a/src/OpenFeature/Telemetry/EvaluationEventFactory.cs b/src/OpenFeature/Telemetry/EvaluationEventFactory.cs new file mode 100644 index 00000000..01965d12 --- /dev/null +++ b/src/OpenFeature/Telemetry/EvaluationEventFactory.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using OpenFeature.Constant; +using OpenFeature.Model; + +namespace OpenFeature.Telemetry; + +public class EvaluationEvent +{ + public string Name { get; set; } + public Dictionary Attributes { get; set; } + public Dictionary Body { get; set; } +} + +public static class EvaluationEventFactory +{ + + private const string EventName = "feature_flag.evaluation"; + + public static EvaluationEvent CreateEvaluationEvent(HookContext hookContext, FlagEvaluationDetails details) + { + var attributes = new Dictionary + { + { TelemetryConstants.Key, hookContext.FlagKey }, + { TelemetryConstants.Provider, hookContext.ProviderMetadata.Name } + }; + + attributes[TelemetryConstants.Reason] = !string.IsNullOrWhiteSpace(details.Reason) ? details.Reason?.ToLowerInvariant() : "unknown"; + + var body = new Dictionary(); + + if (!string.IsNullOrWhiteSpace(details.Variant)) + { + attributes[TelemetryConstants.Variant] = details.Variant ?? details.Value.ToString(); + } + + attributes[TelemetryFlagMetadata.ContextId] = details.FlagMetadata?.GetString(TelemetryFlagMetadata.ContextId); + attributes[TelemetryFlagMetadata.FlagSetId] = details.FlagMetadata?.GetString(TelemetryFlagMetadata.FlagSetId); + attributes[TelemetryFlagMetadata.Version] = details.FlagMetadata?.GetString(TelemetryFlagMetadata.Version); + + if (details.ErrorType != ErrorType.None) + { + attributes[TelemetryConstants.ErrorCode] = details.ErrorType.ToString(); + + if (!string.IsNullOrWhiteSpace(details.ErrorMessage)) + { + attributes[TelemetryConstants.ErrorMessage] = details.ErrorMessage ?? "N/A"; + } + } + + return new EvaluationEvent + { + Name = EventName, + Attributes = attributes, + Body = body + }; + } +} diff --git a/src/OpenFeature/Telemetry/TelemetryAttributes.cs b/src/OpenFeature/Telemetry/TelemetryConstants.cs similarity index 97% rename from src/OpenFeature/Telemetry/TelemetryAttributes.cs rename to src/OpenFeature/Telemetry/TelemetryConstants.cs index fa60cf15..9cbd3db4 100644 --- a/src/OpenFeature/Telemetry/TelemetryAttributes.cs +++ b/src/OpenFeature/Telemetry/TelemetryConstants.cs @@ -4,7 +4,7 @@ namespace OpenFeature.Telemetry; /// The attributes of an OpenTelemetry compliant event for flag evaluation. /// /// -public static class TelemetryAttributes +public static class TelemetryConstants { /// /// The lookup key of the feature flag. From 783df4f652430bc369bf32a4bbde1547236252d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <2493377+askpt@users.noreply.github.com> Date: Thu, 6 Mar 2025 17:12:17 +0000 Subject: [PATCH 05/20] feat: Introduce EvaluationEvent class and refactor EvaluationEventFactory for event creation 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/Telemetry/EvaluationEvent.cs | 37 +++++++++++++++++++ .../Telemetry/EvaluationEventFactory.cs | 26 ++++++------- 2 files changed, 48 insertions(+), 15 deletions(-) create mode 100644 src/OpenFeature/Telemetry/EvaluationEvent.cs diff --git a/src/OpenFeature/Telemetry/EvaluationEvent.cs b/src/OpenFeature/Telemetry/EvaluationEvent.cs new file mode 100644 index 00000000..666f7b4c --- /dev/null +++ b/src/OpenFeature/Telemetry/EvaluationEvent.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; + +namespace OpenFeature.Telemetry; + +/// +/// Represents an evaluation event for feature flags. +/// +public class EvaluationEvent +{ + /// + /// Initializes a new instance of the class. + /// + /// The name of the event. + /// The attributes of the event. + /// The body of the event. + public EvaluationEvent(string name, Dictionary attributes, Dictionary body) + { + this.Name = name; + this.Attributes = attributes; + this.Body = body; + } + + /// + /// Gets or sets the name of the event. + /// + public string Name { get; set; } + + /// + /// Gets or sets the attributes of the event. + /// + public Dictionary Attributes { get; set; } + + /// + /// Gets or sets the body of the event. + /// + public Dictionary Body { get; set; } +} diff --git a/src/OpenFeature/Telemetry/EvaluationEventFactory.cs b/src/OpenFeature/Telemetry/EvaluationEventFactory.cs index 01965d12..e84928fd 100644 --- a/src/OpenFeature/Telemetry/EvaluationEventFactory.cs +++ b/src/OpenFeature/Telemetry/EvaluationEventFactory.cs @@ -4,18 +4,19 @@ namespace OpenFeature.Telemetry; -public class EvaluationEvent -{ - public string Name { get; set; } - public Dictionary Attributes { get; set; } - public Dictionary Body { get; set; } -} - +/// +/// Factory class for creating evaluation events for feature flags. +/// public static class EvaluationEventFactory { - private const string EventName = "feature_flag.evaluation"; + /// + /// Creates an evaluation event based on the provided hook context and flag evaluation details. + /// + /// The context of the hook containing flag key and provider metadata. + /// The details of the flag evaluation including reason, variant, and metadata. + /// An instance of containing the event name, attributes, and body. public static EvaluationEvent CreateEvaluationEvent(HookContext hookContext, FlagEvaluationDetails details) { var attributes = new Dictionary @@ -24,7 +25,7 @@ public static EvaluationEvent CreateEvaluationEvent(HookContext hookConte { TelemetryConstants.Provider, hookContext.ProviderMetadata.Name } }; - attributes[TelemetryConstants.Reason] = !string.IsNullOrWhiteSpace(details.Reason) ? details.Reason?.ToLowerInvariant() : "unknown"; + attributes[TelemetryConstants.Reason] = !string.IsNullOrWhiteSpace(details.Reason) ? details.Reason?.ToLowerInvariant() : Reason.Unknown; var body = new Dictionary(); @@ -47,11 +48,6 @@ public static EvaluationEvent CreateEvaluationEvent(HookContext hookConte } } - return new EvaluationEvent - { - Name = EventName, - Attributes = attributes, - Body = body - }; + return new EvaluationEvent(EventName, attributes, body); } } From f94d83aa120404d1e0c603b580609c5903f08c0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <2493377+askpt@users.noreply.github.com> Date: Fri, 7 Mar 2025 13:38:53 +0000 Subject: [PATCH 06/20] feat: Add EvaluationEventBuilder for creating evaluation events for feature flags 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> --- ...{EvaluationEventFactory.cs => EvaluationEventBuilder.cs} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename src/OpenFeature/Telemetry/{EvaluationEventFactory.cs => EvaluationEventBuilder.cs} (89%) diff --git a/src/OpenFeature/Telemetry/EvaluationEventFactory.cs b/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs similarity index 89% rename from src/OpenFeature/Telemetry/EvaluationEventFactory.cs rename to src/OpenFeature/Telemetry/EvaluationEventBuilder.cs index e84928fd..e982587b 100644 --- a/src/OpenFeature/Telemetry/EvaluationEventFactory.cs +++ b/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs @@ -5,9 +5,9 @@ namespace OpenFeature.Telemetry; /// -/// Factory class for creating evaluation events for feature flags. +/// Class for creating evaluation events for feature flags. /// -public static class EvaluationEventFactory +public static class EvaluationEventBuilder { private const string EventName = "feature_flag.evaluation"; @@ -17,7 +17,7 @@ public static class EvaluationEventFactory /// The context of the hook containing flag key and provider metadata. /// The details of the flag evaluation including reason, variant, and metadata. /// An instance of containing the event name, attributes, and body. - public static EvaluationEvent CreateEvaluationEvent(HookContext hookContext, FlagEvaluationDetails details) + public static EvaluationEvent Build(HookContext hookContext, FlagEvaluationDetails details) { var attributes = new Dictionary { From f6f3d0f53c9943ad5f3fc220cc5a46b5bb852ba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <2493377+askpt@users.noreply.github.com> Date: Fri, 7 Mar 2025 13:57:40 +0000 Subject: [PATCH 07/20] test: Add unit tests for EvaluationEventBuilder to validate event creation 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> --- .../Telemetry/EvaluationEventBuilderTests.cs | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs diff --git a/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs b/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs new file mode 100644 index 00000000..1d65fc2d --- /dev/null +++ b/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs @@ -0,0 +1,88 @@ +using System.Collections.Generic; +using OpenFeature.Constant; +using OpenFeature.Model; +using OpenFeature.Telemetry; +using Xunit; + +namespace OpenFeature.Tests.Telemetry +{ + public class EvaluationEventBuilderTests + { + [Fact] + public void Build_ShouldReturnEventWithCorrectAttributes() + { + // Arrange + var clientMetadata = new ClientMetadata("client", "1.0.0"); + var providerMetadata = new Metadata("provider"); + var hookContext = new HookContext("flagKey", new Value(), FlagValueType.Object, clientMetadata, + providerMetadata, EvaluationContext.Empty); + var metadata = new Dictionary + { + { "flagSetId", "flagSetId" }, { "contextId", "contextId" }, { "version", "version" } + }; + var flagMetadata = new ImmutableMetadata(metadata); + var details = new FlagEvaluationDetails("flagKey", new Value("value"), ErrorType.None, + reason: "reason", variant: "variant", flagMetadata: flagMetadata); + + // Act + var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); + + // Assert + Assert.Equal("feature_flag.evaluation", evaluationEvent.Name); + Assert.Equal("flagKey", evaluationEvent.Attributes[TelemetryConstants.Key]); + Assert.Equal("provider", evaluationEvent.Attributes[TelemetryConstants.Provider]); + Assert.Equal("reason", evaluationEvent.Attributes[TelemetryConstants.Reason]); + Assert.Equal("variant", evaluationEvent.Attributes[TelemetryConstants.Variant]); + Assert.Equal("contextId", evaluationEvent.Attributes[TelemetryFlagMetadata.ContextId]); + Assert.Equal("flagSetId", evaluationEvent.Attributes[TelemetryFlagMetadata.FlagSetId]); + Assert.Equal("version", evaluationEvent.Attributes[TelemetryFlagMetadata.Version]); + } + + [Fact] + public void Build_ShouldHandleErrorDetails() + { + // Arrange + var clientMetadata = new ClientMetadata("client", "1.0.0"); + var providerMetadata = new Metadata("provider"); + var hookContext = new HookContext("flagKey", new Value(), FlagValueType.Object, clientMetadata, + providerMetadata, EvaluationContext.Empty); + var metadata = new Dictionary + { + { "flagSetId", "flagSetId" }, { "contextId", "contextId" }, { "version", "version" } + }; + var flagMetadata = new ImmutableMetadata(metadata); + var details = new FlagEvaluationDetails("flagKey", new Value("value"), ErrorType.General, + errorMessage: "errorMessage", reason: "reason", variant: "variant", flagMetadata: flagMetadata); + + // Act + var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); + + // Assert + Assert.Equal("General", evaluationEvent.Attributes[TelemetryConstants.ErrorCode]); + Assert.Equal("errorMessage", evaluationEvent.Attributes[TelemetryConstants.ErrorMessage]); + } + + [Fact] + public void Build_ShouldHandleMissingVariant() + { + // Arrange + var clientMetadata = new ClientMetadata("client", "1.0.0"); + var providerMetadata = new Metadata("provider"); + var hookContext = new HookContext("flagKey", new Value("value"), FlagValueType.Object, clientMetadata, + providerMetadata, EvaluationContext.Empty); + var metadata = new Dictionary + { + { "flagSetId", "flagSetId" }, { "contextId", "contextId" }, { "version", "version" } + }; + var flagMetadata = new ImmutableMetadata(metadata); + var details = new FlagEvaluationDetails("flagKey", new Value("value"), ErrorType.None, + reason: "reason", variant: "", flagMetadata: flagMetadata); + + // Act + var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); + + // Assert + Assert.Throws(() => evaluationEvent.Attributes[TelemetryConstants.Variant]); + } + } +} From 53693481df6ff54b16e0878f6bda14d3e781bf6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <2493377+askpt@users.noreply.github.com> Date: Fri, 7 Mar 2025 14:30:32 +0000 Subject: [PATCH 08/20] fix: Simplify variant assignment in EvaluationEventBuilder and enhance tests for missing flag metadata 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> --- .../Telemetry/EvaluationEventBuilder.cs | 6 +---- .../Telemetry/EvaluationEventBuilderTests.cs | 23 ++++++++++++++++++- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs b/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs index e982587b..6b67bc65 100644 --- a/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs +++ b/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs @@ -29,11 +29,7 @@ public static EvaluationEvent Build(HookContext hookContext, FlagEvaluati var body = new Dictionary(); - if (!string.IsNullOrWhiteSpace(details.Variant)) - { - attributes[TelemetryConstants.Variant] = details.Variant ?? details.Value.ToString(); - } - + attributes[TelemetryConstants.Variant] = details.Variant; attributes[TelemetryFlagMetadata.ContextId] = details.FlagMetadata?.GetString(TelemetryFlagMetadata.ContextId); attributes[TelemetryFlagMetadata.FlagSetId] = details.FlagMetadata?.GetString(TelemetryFlagMetadata.FlagSetId); attributes[TelemetryFlagMetadata.Version] = details.FlagMetadata?.GetString(TelemetryFlagMetadata.Version); diff --git a/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs b/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs index 1d65fc2d..1965e415 100644 --- a/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs +++ b/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs @@ -75,6 +75,25 @@ public void Build_ShouldHandleMissingVariant() { "flagSetId", "flagSetId" }, { "contextId", "contextId" }, { "version", "version" } }; var flagMetadata = new ImmutableMetadata(metadata); + var details = new FlagEvaluationDetails("flagKey", new Value("value"), ErrorType.None, + reason: "reason", variant: null, flagMetadata: flagMetadata); + + // Act + var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); + + // Assert + Assert.Null(evaluationEvent.Attributes[TelemetryConstants.Variant]); + } + + [Fact] + public void Build_ShouldHandleMissingFlagMetadata() + { + // Arrange + var clientMetadata = new ClientMetadata("client", "1.0.0"); + var providerMetadata = new Metadata("provider"); + var hookContext = new HookContext("flagKey", new Value("value"), FlagValueType.Object, clientMetadata, + providerMetadata, EvaluationContext.Empty); + var flagMetadata = new ImmutableMetadata(); var details = new FlagEvaluationDetails("flagKey", new Value("value"), ErrorType.None, reason: "reason", variant: "", flagMetadata: flagMetadata); @@ -82,7 +101,9 @@ public void Build_ShouldHandleMissingVariant() var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); // Assert - Assert.Throws(() => evaluationEvent.Attributes[TelemetryConstants.Variant]); + Assert.Null(evaluationEvent.Attributes[TelemetryFlagMetadata.ContextId]); + Assert.Null(evaluationEvent.Attributes[TelemetryFlagMetadata.FlagSetId]); + Assert.Null(evaluationEvent.Attributes[TelemetryFlagMetadata.Version]); } } } From 2d81ada7a1b2e5c8058493e923e94ef63c36661a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <2493377+askpt@users.noreply.github.com> Date: Fri, 7 Mar 2025 14:32:18 +0000 Subject: [PATCH 09/20] fix: Ensure proper handling of missing reason in EvaluationEventBuilder and add corresponding 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> --- .../Telemetry/EvaluationEventBuilder.cs | 2 +- .../Telemetry/EvaluationEventBuilderTests.cs | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs b/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs index 6b67bc65..865045d3 100644 --- a/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs +++ b/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs @@ -25,10 +25,10 @@ public static EvaluationEvent Build(HookContext hookContext, FlagEvaluati { TelemetryConstants.Provider, hookContext.ProviderMetadata.Name } }; - attributes[TelemetryConstants.Reason] = !string.IsNullOrWhiteSpace(details.Reason) ? details.Reason?.ToLowerInvariant() : Reason.Unknown; var body = new Dictionary(); + attributes[TelemetryConstants.Reason] = !string.IsNullOrWhiteSpace(details.Reason) ? details.Reason?.ToLowerInvariant() : Reason.Unknown; attributes[TelemetryConstants.Variant] = details.Variant; attributes[TelemetryFlagMetadata.ContextId] = details.FlagMetadata?.GetString(TelemetryFlagMetadata.ContextId); attributes[TelemetryFlagMetadata.FlagSetId] = details.FlagMetadata?.GetString(TelemetryFlagMetadata.FlagSetId); diff --git a/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs b/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs index 1965e415..0d08cad1 100644 --- a/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs +++ b/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs @@ -105,5 +105,27 @@ public void Build_ShouldHandleMissingFlagMetadata() Assert.Null(evaluationEvent.Attributes[TelemetryFlagMetadata.FlagSetId]); Assert.Null(evaluationEvent.Attributes[TelemetryFlagMetadata.Version]); } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void Build_ShouldHandleMissingReason(string? reason) + { + // Arrange + var clientMetadata = new ClientMetadata("client", "1.0.0"); + var providerMetadata = new Metadata("provider"); + var hookContext = new HookContext("flagKey", new Value("value"), FlagValueType.Object, clientMetadata, + providerMetadata, EvaluationContext.Empty); + var flagMetadata = new ImmutableMetadata(); + var details = new FlagEvaluationDetails("flagKey", new Value("value"), ErrorType.None, + reason: reason, variant: "", flagMetadata: flagMetadata); + + // Act + var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); + + // Assert + Assert.Equal(Reason.Unknown, evaluationEvent.Attributes[TelemetryConstants.Reason]); + } } } From 0d1a869424d98202f6f9ac5d76d0e7609e732967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <2493377+askpt@users.noreply.github.com> Date: Tue, 11 Mar 2025 16:26:35 +0000 Subject: [PATCH 10/20] fix: Normalize error code to lowercase in EvaluationEventBuilder 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/Telemetry/EvaluationEventBuilder.cs | 2 +- test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs b/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs index 865045d3..4dc17aab 100644 --- a/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs +++ b/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs @@ -36,7 +36,7 @@ public static EvaluationEvent Build(HookContext hookContext, FlagEvaluati if (details.ErrorType != ErrorType.None) { - attributes[TelemetryConstants.ErrorCode] = details.ErrorType.ToString(); + attributes[TelemetryConstants.ErrorCode] = details.ErrorType.ToString()?.ToLowerInvariant(); if (!string.IsNullOrWhiteSpace(details.ErrorMessage)) { diff --git a/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs b/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs index 0d08cad1..2c56c360 100644 --- a/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs +++ b/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs @@ -58,7 +58,7 @@ public void Build_ShouldHandleErrorDetails() var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); // Assert - Assert.Equal("General", evaluationEvent.Attributes[TelemetryConstants.ErrorCode]); + Assert.Equal("general", evaluationEvent.Attributes[TelemetryConstants.ErrorCode]); Assert.Equal("errorMessage", evaluationEvent.Attributes[TelemetryConstants.ErrorMessage]); } From 26d7dd5930c5d719bad250387240fbb44b9b8d71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <2493377+askpt@users.noreply.github.com> Date: Thu, 17 Apr 2025 16:45:13 +0100 Subject: [PATCH 11/20] fix: Correct telemetry constant values for feature flag attributes 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/Telemetry/TelemetryConstants.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/OpenFeature/Telemetry/TelemetryConstants.cs b/src/OpenFeature/Telemetry/TelemetryConstants.cs index 9cbd3db4..3660c82d 100644 --- a/src/OpenFeature/Telemetry/TelemetryConstants.cs +++ b/src/OpenFeature/Telemetry/TelemetryConstants.cs @@ -19,7 +19,7 @@ public static class TelemetryConstants /// /// A semantic identifier for an evaluated flag value. /// - public const string Variant = "feature_flag.variant"; + public const string Variant = "feature_flag.result.variant"; /// /// The unique identifier for the flag evaluation context. For example, the targeting key. @@ -29,17 +29,17 @@ public static class TelemetryConstants /// /// A message explaining the nature of an error occurring during flag evaluation. /// - public const string ErrorMessage = "feature_flag.evaluation.error.message"; + public const string ErrorMessage = "error.message"; /// /// The reason code which shows how a feature flag value was determined. /// - public const string Reason = "feature_flag.evaluation.reason"; + public const string Reason = "feature_flag.result.reason"; /// /// Describes a class of error the operation ended with. /// - public const string Provider = "feature_flag.provider_name"; + public const string Provider = "feature_flag.provider.name"; /// /// The identifier of the flag set to which the feature flag belongs. From 6b484877a4807f7e6363f47e38f6caf69b53045f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <2493377+askpt@users.noreply.github.com> Date: Mon, 28 Apr 2025 16:34:53 +0100 Subject: [PATCH 12/20] refactor(tests): streamline EvaluationEventBuilderTests for clarity and consistency 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> --- .../Telemetry/EvaluationEventBuilderTests.cs | 235 +++++++++--------- 1 file changed, 117 insertions(+), 118 deletions(-) diff --git a/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs b/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs index 2c56c360..99805f2b 100644 --- a/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs +++ b/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs @@ -4,128 +4,127 @@ using OpenFeature.Telemetry; using Xunit; -namespace OpenFeature.Tests.Telemetry +namespace OpenFeature.Tests.Telemetry; + +public class EvaluationEventBuilderTests { - public class EvaluationEventBuilderTests + [Fact] + public void Build_ShouldReturnEventWithCorrectAttributes() { - [Fact] - public void Build_ShouldReturnEventWithCorrectAttributes() - { - // Arrange - var clientMetadata = new ClientMetadata("client", "1.0.0"); - var providerMetadata = new Metadata("provider"); - var hookContext = new HookContext("flagKey", new Value(), FlagValueType.Object, clientMetadata, - providerMetadata, EvaluationContext.Empty); - var metadata = new Dictionary - { - { "flagSetId", "flagSetId" }, { "contextId", "contextId" }, { "version", "version" } - }; - var flagMetadata = new ImmutableMetadata(metadata); - var details = new FlagEvaluationDetails("flagKey", new Value("value"), ErrorType.None, - reason: "reason", variant: "variant", flagMetadata: flagMetadata); - - // Act - var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); - - // Assert - Assert.Equal("feature_flag.evaluation", evaluationEvent.Name); - Assert.Equal("flagKey", evaluationEvent.Attributes[TelemetryConstants.Key]); - Assert.Equal("provider", evaluationEvent.Attributes[TelemetryConstants.Provider]); - Assert.Equal("reason", evaluationEvent.Attributes[TelemetryConstants.Reason]); - Assert.Equal("variant", evaluationEvent.Attributes[TelemetryConstants.Variant]); - Assert.Equal("contextId", evaluationEvent.Attributes[TelemetryFlagMetadata.ContextId]); - Assert.Equal("flagSetId", evaluationEvent.Attributes[TelemetryFlagMetadata.FlagSetId]); - Assert.Equal("version", evaluationEvent.Attributes[TelemetryFlagMetadata.Version]); - } - - [Fact] - public void Build_ShouldHandleErrorDetails() + // Arrange + var clientMetadata = new ClientMetadata("client", "1.0.0"); + var providerMetadata = new Metadata("provider"); + var hookContext = new HookContext("flagKey", new Value(), FlagValueType.Object, clientMetadata, + providerMetadata, EvaluationContext.Empty); + var metadata = new Dictionary { - // Arrange - var clientMetadata = new ClientMetadata("client", "1.0.0"); - var providerMetadata = new Metadata("provider"); - var hookContext = new HookContext("flagKey", new Value(), FlagValueType.Object, clientMetadata, - providerMetadata, EvaluationContext.Empty); - var metadata = new Dictionary - { - { "flagSetId", "flagSetId" }, { "contextId", "contextId" }, { "version", "version" } - }; - var flagMetadata = new ImmutableMetadata(metadata); - var details = new FlagEvaluationDetails("flagKey", new Value("value"), ErrorType.General, - errorMessage: "errorMessage", reason: "reason", variant: "variant", flagMetadata: flagMetadata); - - // Act - var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); - - // Assert - Assert.Equal("general", evaluationEvent.Attributes[TelemetryConstants.ErrorCode]); - Assert.Equal("errorMessage", evaluationEvent.Attributes[TelemetryConstants.ErrorMessage]); - } - - [Fact] - public void Build_ShouldHandleMissingVariant() - { - // Arrange - var clientMetadata = new ClientMetadata("client", "1.0.0"); - var providerMetadata = new Metadata("provider"); - var hookContext = new HookContext("flagKey", new Value("value"), FlagValueType.Object, clientMetadata, - providerMetadata, EvaluationContext.Empty); - var metadata = new Dictionary - { - { "flagSetId", "flagSetId" }, { "contextId", "contextId" }, { "version", "version" } - }; - var flagMetadata = new ImmutableMetadata(metadata); - var details = new FlagEvaluationDetails("flagKey", new Value("value"), ErrorType.None, - reason: "reason", variant: null, flagMetadata: flagMetadata); - - // Act - var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); - - // Assert - Assert.Null(evaluationEvent.Attributes[TelemetryConstants.Variant]); - } - - [Fact] - public void Build_ShouldHandleMissingFlagMetadata() + { "flagSetId", "flagSetId" }, { "contextId", "contextId" }, { "version", "version" } + }; + var flagMetadata = new ImmutableMetadata(metadata); + var details = new FlagEvaluationDetails("flagKey", new Value("value"), ErrorType.None, + reason: "reason", variant: "variant", flagMetadata: flagMetadata); + + // Act + var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); + + // Assert + Assert.Equal("feature_flag.evaluation", evaluationEvent.Name); + Assert.Equal("flagKey", evaluationEvent.Attributes[TelemetryConstants.Key]); + Assert.Equal("provider", evaluationEvent.Attributes[TelemetryConstants.Provider]); + Assert.Equal("reason", evaluationEvent.Attributes[TelemetryConstants.Reason]); + Assert.Equal("variant", evaluationEvent.Attributes[TelemetryConstants.Variant]); + Assert.Equal("contextId", evaluationEvent.Attributes[TelemetryFlagMetadata.ContextId]); + Assert.Equal("flagSetId", evaluationEvent.Attributes[TelemetryFlagMetadata.FlagSetId]); + Assert.Equal("version", evaluationEvent.Attributes[TelemetryFlagMetadata.Version]); + } + + [Fact] + public void Build_ShouldHandleErrorDetails() + { + // Arrange + var clientMetadata = new ClientMetadata("client", "1.0.0"); + var providerMetadata = new Metadata("provider"); + var hookContext = new HookContext("flagKey", new Value(), FlagValueType.Object, clientMetadata, + providerMetadata, EvaluationContext.Empty); + var metadata = new Dictionary { - // Arrange - var clientMetadata = new ClientMetadata("client", "1.0.0"); - var providerMetadata = new Metadata("provider"); - var hookContext = new HookContext("flagKey", new Value("value"), FlagValueType.Object, clientMetadata, - providerMetadata, EvaluationContext.Empty); - var flagMetadata = new ImmutableMetadata(); - var details = new FlagEvaluationDetails("flagKey", new Value("value"), ErrorType.None, - reason: "reason", variant: "", flagMetadata: flagMetadata); - - // Act - var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); - - // Assert - Assert.Null(evaluationEvent.Attributes[TelemetryFlagMetadata.ContextId]); - Assert.Null(evaluationEvent.Attributes[TelemetryFlagMetadata.FlagSetId]); - Assert.Null(evaluationEvent.Attributes[TelemetryFlagMetadata.Version]); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void Build_ShouldHandleMissingReason(string? reason) + { "flagSetId", "flagSetId" }, { "contextId", "contextId" }, { "version", "version" } + }; + var flagMetadata = new ImmutableMetadata(metadata); + var details = new FlagEvaluationDetails("flagKey", new Value("value"), ErrorType.General, + errorMessage: "errorMessage", reason: "reason", variant: "variant", flagMetadata: flagMetadata); + + // Act + var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); + + // Assert + Assert.Equal("general", evaluationEvent.Attributes[TelemetryConstants.ErrorCode]); + Assert.Equal("errorMessage", evaluationEvent.Attributes[TelemetryConstants.ErrorMessage]); + } + + [Fact] + public void Build_ShouldHandleMissingVariant() + { + // Arrange + var clientMetadata = new ClientMetadata("client", "1.0.0"); + var providerMetadata = new Metadata("provider"); + var hookContext = new HookContext("flagKey", new Value("value"), FlagValueType.Object, clientMetadata, + providerMetadata, EvaluationContext.Empty); + var metadata = new Dictionary { - // Arrange - var clientMetadata = new ClientMetadata("client", "1.0.0"); - var providerMetadata = new Metadata("provider"); - var hookContext = new HookContext("flagKey", new Value("value"), FlagValueType.Object, clientMetadata, - providerMetadata, EvaluationContext.Empty); - var flagMetadata = new ImmutableMetadata(); - var details = new FlagEvaluationDetails("flagKey", new Value("value"), ErrorType.None, - reason: reason, variant: "", flagMetadata: flagMetadata); - - // Act - var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); - - // Assert - Assert.Equal(Reason.Unknown, evaluationEvent.Attributes[TelemetryConstants.Reason]); - } + { "flagSetId", "flagSetId" }, { "contextId", "contextId" }, { "version", "version" } + }; + var flagMetadata = new ImmutableMetadata(metadata); + var details = new FlagEvaluationDetails("flagKey", new Value("value"), ErrorType.None, + reason: "reason", variant: null, flagMetadata: flagMetadata); + + // Act + var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); + + // Assert + Assert.Null(evaluationEvent.Attributes[TelemetryConstants.Variant]); + } + + [Fact] + public void Build_ShouldHandleMissingFlagMetadata() + { + // Arrange + var clientMetadata = new ClientMetadata("client", "1.0.0"); + var providerMetadata = new Metadata("provider"); + var hookContext = new HookContext("flagKey", new Value("value"), FlagValueType.Object, clientMetadata, + providerMetadata, EvaluationContext.Empty); + var flagMetadata = new ImmutableMetadata(); + var details = new FlagEvaluationDetails("flagKey", new Value("value"), ErrorType.None, + reason: "reason", variant: "", flagMetadata: flagMetadata); + + // Act + var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); + + // Assert + Assert.Null(evaluationEvent.Attributes[TelemetryFlagMetadata.ContextId]); + Assert.Null(evaluationEvent.Attributes[TelemetryFlagMetadata.FlagSetId]); + Assert.Null(evaluationEvent.Attributes[TelemetryFlagMetadata.Version]); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void Build_ShouldHandleMissingReason(string? reason) + { + // Arrange + var clientMetadata = new ClientMetadata("client", "1.0.0"); + var providerMetadata = new Metadata("provider"); + var hookContext = new HookContext("flagKey", new Value("value"), FlagValueType.Object, clientMetadata, + providerMetadata, EvaluationContext.Empty); + var flagMetadata = new ImmutableMetadata(); + var details = new FlagEvaluationDetails("flagKey", new Value("value"), ErrorType.None, + reason: reason, variant: "", flagMetadata: flagMetadata); + + // Act + var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); + + // Assert + Assert.Equal(Reason.Unknown, evaluationEvent.Attributes[TelemetryConstants.Reason]); } } From 1b984c6ada975849d994628c587c2298cc1fb005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <2493377+askpt@users.noreply.github.com> Date: Wed, 14 May 2025 16:16:11 +0100 Subject: [PATCH 13/20] Removed Body. 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/Telemetry/EvaluationEvent.cs | 9 +-------- src/OpenFeature/Telemetry/EvaluationEventBuilder.cs | 6 ++---- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/OpenFeature/Telemetry/EvaluationEvent.cs b/src/OpenFeature/Telemetry/EvaluationEvent.cs index 666f7b4c..c0cb1bbe 100644 --- a/src/OpenFeature/Telemetry/EvaluationEvent.cs +++ b/src/OpenFeature/Telemetry/EvaluationEvent.cs @@ -12,12 +12,10 @@ public class EvaluationEvent /// /// The name of the event. /// The attributes of the event. - /// The body of the event. - public EvaluationEvent(string name, Dictionary attributes, Dictionary body) + public EvaluationEvent(string name, Dictionary attributes) { this.Name = name; this.Attributes = attributes; - this.Body = body; } /// @@ -29,9 +27,4 @@ public EvaluationEvent(string name, Dictionary attributes, Dict /// Gets or sets the attributes of the event. /// public Dictionary Attributes { get; set; } - - /// - /// Gets or sets the body of the event. - /// - public Dictionary Body { get; set; } } diff --git a/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs b/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs index 4dc17aab..72da1fa5 100644 --- a/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs +++ b/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs @@ -26,8 +26,6 @@ public static EvaluationEvent Build(HookContext hookContext, FlagEvaluati }; - var body = new Dictionary(); - attributes[TelemetryConstants.Reason] = !string.IsNullOrWhiteSpace(details.Reason) ? details.Reason?.ToLowerInvariant() : Reason.Unknown; attributes[TelemetryConstants.Variant] = details.Variant; attributes[TelemetryFlagMetadata.ContextId] = details.FlagMetadata?.GetString(TelemetryFlagMetadata.ContextId); @@ -36,7 +34,7 @@ public static EvaluationEvent Build(HookContext hookContext, FlagEvaluati if (details.ErrorType != ErrorType.None) { - attributes[TelemetryConstants.ErrorCode] = details.ErrorType.ToString()?.ToLowerInvariant(); + attributes[TelemetryConstants.ErrorCode] = details.ErrorType.ToString().ToLowerInvariant(); if (!string.IsNullOrWhiteSpace(details.ErrorMessage)) { @@ -44,6 +42,6 @@ public static EvaluationEvent Build(HookContext hookContext, FlagEvaluati } } - return new EvaluationEvent(EventName, attributes, body); + return new EvaluationEvent(EventName, attributes); } } From 5c9768786a0b57ce8d5dd144baae2e0f6bb14b6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <2493377+askpt@users.noreply.github.com> Date: Wed, 14 May 2025 16:33:33 +0100 Subject: [PATCH 14/20] docs: update XML documentation for TelemetryFlagMetadata class 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> --- .../Telemetry/TelemetryEvaluationData.cs | 20 ----------- .../Telemetry/TelemetryFlagMetadata.cs | 35 +++++++++++-------- 2 files changed, 20 insertions(+), 35 deletions(-) delete mode 100644 src/OpenFeature/Telemetry/TelemetryEvaluationData.cs diff --git a/src/OpenFeature/Telemetry/TelemetryEvaluationData.cs b/src/OpenFeature/Telemetry/TelemetryEvaluationData.cs deleted file mode 100644 index f143dc02..00000000 --- a/src/OpenFeature/Telemetry/TelemetryEvaluationData.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace OpenFeature.Telemetry; - -/** -* Event data, sometimes referred to as "body", is specific to a specific event. -* In this case, the event is `feature_flag.evaluation`. That's why the prefix -* is omitted from the values. -* @see https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/ -*/ -public static class TelemetryEvaluationData -{ - /** - * The evaluated value of the feature flag. - * - * - type: `undefined` - * - requirement level: `conditionally required` - * - condition: variant is not defined on the evaluation details - * - example: `#ff0000`; `1`; `true` - */ - public const string Value = "value"; -} diff --git a/src/OpenFeature/Telemetry/TelemetryFlagMetadata.cs b/src/OpenFeature/Telemetry/TelemetryFlagMetadata.cs index 3bd0a258..eaa6d3f6 100644 --- a/src/OpenFeature/Telemetry/TelemetryFlagMetadata.cs +++ b/src/OpenFeature/Telemetry/TelemetryFlagMetadata.cs @@ -1,25 +1,30 @@ namespace OpenFeature.Telemetry; -/** - * Well-known flag metadata attributes for telemetry events. - * @see https://openfeature.dev/specification/appendix-d#flag-metadata - */ +/// +/// Well-known flag metadata attributes for telemetry events. +/// See also: https://openfeature.dev/specification/appendix-d#flag-metadata +/// public static class TelemetryFlagMetadata { - /** - * The context identifier returned in the flag metadata uniquely identifies - * the subject of the flag evaluation. If not available, the targeting key - * should be used. - */ + /// + /// The context identifier returned in the flag metadata uniquely identifies + /// the subject of the flag evaluation. If not available, the targeting key + /// should be used. + /// public const string ContextId = "contextId"; - /** - * A logical identifier for the flag set. - */ + /// + /// ///A logical identifier for the flag set. + /// public const string FlagSetId = "flagSetId"; - /** - * A version string (format unspecified) for the flag or flag set. - */ + /// + /// A version string (format unspecified) for the flag or flag set. + /// public const string Version = "version"; + + /// + /// The evaluated value of the feature flag. + /// + public const string Value = "value"; } From ac2d968cfb0e5838df11b5ce9346f104f12a529e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <2493377+askpt@users.noreply.github.com> Date: Thu, 15 May 2025 16:35:16 +0100 Subject: [PATCH 15/20] feat: update telemetry attributes to include evaluated value and refactor constants 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> --- .../Telemetry/EvaluationEventBuilder.cs | 7 ++++--- src/OpenFeature/Telemetry/TelemetryConstants.cs | 14 ++++++++++---- src/OpenFeature/Telemetry/TelemetryFlagMetadata.cs | 5 ----- .../Telemetry/EvaluationEventBuilderTests.cs | 12 ++++++------ 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs b/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs index 72da1fa5..6572885a 100644 --- a/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs +++ b/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs @@ -28,9 +28,10 @@ public static EvaluationEvent Build(HookContext hookContext, FlagEvaluati attributes[TelemetryConstants.Reason] = !string.IsNullOrWhiteSpace(details.Reason) ? details.Reason?.ToLowerInvariant() : Reason.Unknown; attributes[TelemetryConstants.Variant] = details.Variant; - attributes[TelemetryFlagMetadata.ContextId] = details.FlagMetadata?.GetString(TelemetryFlagMetadata.ContextId); - attributes[TelemetryFlagMetadata.FlagSetId] = details.FlagMetadata?.GetString(TelemetryFlagMetadata.FlagSetId); - attributes[TelemetryFlagMetadata.Version] = details.FlagMetadata?.GetString(TelemetryFlagMetadata.Version); + attributes[TelemetryConstants.Value] = details.Value; + attributes[TelemetryConstants.ContextId] = details.FlagMetadata?.GetString(TelemetryFlagMetadata.ContextId); + attributes[TelemetryConstants.FlagSetId] = details.FlagMetadata?.GetString(TelemetryFlagMetadata.FlagSetId); + attributes[TelemetryConstants.Version] = details.FlagMetadata?.GetString(TelemetryFlagMetadata.Version); if (details.ErrorType != ErrorType.None) { diff --git a/src/OpenFeature/Telemetry/TelemetryConstants.cs b/src/OpenFeature/Telemetry/TelemetryConstants.cs index 3660c82d..62730666 100644 --- a/src/OpenFeature/Telemetry/TelemetryConstants.cs +++ b/src/OpenFeature/Telemetry/TelemetryConstants.cs @@ -16,20 +16,25 @@ public static class TelemetryConstants /// public const string ErrorCode = "error.type"; + /// + /// A message explaining the nature of an error occurring during flag evaluation. + /// + public const string ErrorMessage = "error.message"; + /// /// A semantic identifier for an evaluated flag value. /// public const string Variant = "feature_flag.result.variant"; /// - /// The unique identifier for the flag evaluation context. For example, the targeting key. + /// The evaluated value of the feature flag. /// - public const string ContextId = "feature_flag.context.id"; + public const string Value = "feature_flag.result.value"; /// - /// A message explaining the nature of an error occurring during flag evaluation. + /// The unique identifier for the flag evaluation context. For example, the targeting key. /// - public const string ErrorMessage = "error.message"; + public const string ContextId = "feature_flag.context.id"; /// /// The reason code which shows how a feature flag value was determined. @@ -50,4 +55,5 @@ public static class TelemetryConstants /// The version of the ruleset used during the evaluation. This may be any stable value which uniquely identifies the ruleset. /// public const string Version = "feature_flag.version"; + } diff --git a/src/OpenFeature/Telemetry/TelemetryFlagMetadata.cs b/src/OpenFeature/Telemetry/TelemetryFlagMetadata.cs index eaa6d3f6..40b58f04 100644 --- a/src/OpenFeature/Telemetry/TelemetryFlagMetadata.cs +++ b/src/OpenFeature/Telemetry/TelemetryFlagMetadata.cs @@ -22,9 +22,4 @@ public static class TelemetryFlagMetadata /// A version string (format unspecified) for the flag or flag set. /// public const string Version = "version"; - - /// - /// The evaluated value of the feature flag. - /// - public const string Value = "value"; } diff --git a/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs b/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs index 99805f2b..b5af1770 100644 --- a/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs +++ b/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs @@ -33,9 +33,9 @@ public void Build_ShouldReturnEventWithCorrectAttributes() Assert.Equal("provider", evaluationEvent.Attributes[TelemetryConstants.Provider]); Assert.Equal("reason", evaluationEvent.Attributes[TelemetryConstants.Reason]); Assert.Equal("variant", evaluationEvent.Attributes[TelemetryConstants.Variant]); - Assert.Equal("contextId", evaluationEvent.Attributes[TelemetryFlagMetadata.ContextId]); - Assert.Equal("flagSetId", evaluationEvent.Attributes[TelemetryFlagMetadata.FlagSetId]); - Assert.Equal("version", evaluationEvent.Attributes[TelemetryFlagMetadata.Version]); + Assert.Equal("contextId", evaluationEvent.Attributes[TelemetryConstants.ContextId]); + Assert.Equal("flagSetId", evaluationEvent.Attributes[TelemetryConstants.FlagSetId]); + Assert.Equal("version", evaluationEvent.Attributes[TelemetryConstants.Version]); } [Fact] @@ -101,9 +101,9 @@ public void Build_ShouldHandleMissingFlagMetadata() var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); // Assert - Assert.Null(evaluationEvent.Attributes[TelemetryFlagMetadata.ContextId]); - Assert.Null(evaluationEvent.Attributes[TelemetryFlagMetadata.FlagSetId]); - Assert.Null(evaluationEvent.Attributes[TelemetryFlagMetadata.Version]); + Assert.Null(evaluationEvent.Attributes[TelemetryConstants.ContextId]); + Assert.Null(evaluationEvent.Attributes[TelemetryConstants.FlagSetId]); + Assert.Null(evaluationEvent.Attributes[TelemetryConstants.Version]); } [Theory] From 789312e3117869406b38648a2d3d8d525bfe57fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <2493377+askpt@users.noreply.github.com> Date: Thu, 15 May 2025 16:47:13 +0100 Subject: [PATCH 16/20] feat: enhance EvaluationEventBuilder to handle empty error messages and include value attribute 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> --- .../Telemetry/EvaluationEventBuilder.cs | 2 +- .../Telemetry/EvaluationEventBuilderTests.cs | 42 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs b/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs index 6572885a..e7253a29 100644 --- a/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs +++ b/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs @@ -39,7 +39,7 @@ public static EvaluationEvent Build(HookContext hookContext, FlagEvaluati if (!string.IsNullOrWhiteSpace(details.ErrorMessage)) { - attributes[TelemetryConstants.ErrorMessage] = details.ErrorMessage ?? "N/A"; + attributes[TelemetryConstants.ErrorMessage] = details.ErrorMessage; } } diff --git a/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs b/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs index b5af1770..ecab3386 100644 --- a/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs +++ b/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs @@ -127,4 +127,46 @@ public void Build_ShouldHandleMissingReason(string? reason) // Assert Assert.Equal(Reason.Unknown, evaluationEvent.Attributes[TelemetryConstants.Reason]); } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void Build_ShouldHandleErrorWithEmptyErrorMessage(string? errorMessage) + { + // Arrange + var clientMetadata = new ClientMetadata("client", "1.0.0"); + var providerMetadata = new Metadata("provider"); + var hookContext = new HookContext("flagKey", new Value("value"), FlagValueType.Object, clientMetadata, + providerMetadata, EvaluationContext.Empty); + var flagMetadata = new ImmutableMetadata(); + var details = new FlagEvaluationDetails("flagKey", new Value("value"), ErrorType.General, + errorMessage: errorMessage, reason: "reason", variant: "", flagMetadata: flagMetadata); + + // Act + var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); + + // Assert + Assert.Equal("general", evaluationEvent.Attributes[TelemetryConstants.ErrorCode]); + Assert.False(evaluationEvent.Attributes.ContainsKey(TelemetryConstants.ErrorMessage)); + } + + [Fact] + public void Build_ShouldIncludeValueAttributeInEvent() + { + // Arrange + var clientMetadata = new ClientMetadata("client", "1.0.0"); + var providerMetadata = new Metadata("provider"); + var hookContext = new HookContext("flagKey", new Value(), FlagValueType.Object, clientMetadata, + providerMetadata, EvaluationContext.Empty); + var testValue = new Value("test-value"); + var details = new FlagEvaluationDetails("flagKey", testValue, ErrorType.None, + reason: "reason", variant: "variant", flagMetadata: new ImmutableMetadata()); + + // Act + var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); + + // Assert + Assert.Equal(testValue, evaluationEvent.Attributes[TelemetryConstants.Value]); + } } From 62d8b5e61980590a2f7be37631d54002079fba11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <2493377+askpt@users.noreply.github.com> Date: Thu, 15 May 2025 16:50:46 +0100 Subject: [PATCH 17/20] feat: refactor EvaluationEventBuilder to improve handling of flag metadata and streamline reason assignment 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> --- .../Telemetry/EvaluationEventBuilder.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs b/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs index e7253a29..39468aa5 100644 --- a/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs +++ b/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs @@ -25,13 +25,17 @@ public static EvaluationEvent Build(HookContext hookContext, FlagEvaluati { TelemetryConstants.Provider, hookContext.ProviderMetadata.Name } }; - - attributes[TelemetryConstants.Reason] = !string.IsNullOrWhiteSpace(details.Reason) ? details.Reason?.ToLowerInvariant() : Reason.Unknown; + attributes[TelemetryConstants.Reason] = !string.IsNullOrWhiteSpace(details.Reason) + ? details.Reason?.ToLowerInvariant() + : Reason.Unknown; attributes[TelemetryConstants.Variant] = details.Variant; attributes[TelemetryConstants.Value] = details.Value; - attributes[TelemetryConstants.ContextId] = details.FlagMetadata?.GetString(TelemetryFlagMetadata.ContextId); - attributes[TelemetryConstants.FlagSetId] = details.FlagMetadata?.GetString(TelemetryFlagMetadata.FlagSetId); - attributes[TelemetryConstants.Version] = details.FlagMetadata?.GetString(TelemetryFlagMetadata.Version); + if (details.FlagMetadata != null) + { + attributes[TelemetryConstants.ContextId] = details.FlagMetadata.GetString(TelemetryFlagMetadata.ContextId); + attributes[TelemetryConstants.FlagSetId] = details.FlagMetadata.GetString(TelemetryFlagMetadata.FlagSetId); + attributes[TelemetryConstants.Version] = details.FlagMetadata.GetString(TelemetryFlagMetadata.Version); + } if (details.ErrorType != ErrorType.None) { From 0cf35e248f0691ed11d8c39eb45bff9773c6e92f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <2493377+askpt@users.noreply.github.com> Date: Thu, 15 May 2025 16:52:19 +0100 Subject: [PATCH 18/20] feat: normalize Reason attribute to lowercase in EvaluationEventBuilder 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/Telemetry/EvaluationEventBuilder.cs | 3 ++- .../OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs b/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs index 39468aa5..e79bbda4 100644 --- a/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs +++ b/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs @@ -27,9 +27,10 @@ public static EvaluationEvent Build(HookContext hookContext, FlagEvaluati attributes[TelemetryConstants.Reason] = !string.IsNullOrWhiteSpace(details.Reason) ? details.Reason?.ToLowerInvariant() - : Reason.Unknown; + : Reason.Unknown.ToLowerInvariant(); attributes[TelemetryConstants.Variant] = details.Variant; attributes[TelemetryConstants.Value] = details.Value; + if (details.FlagMetadata != null) { attributes[TelemetryConstants.ContextId] = details.FlagMetadata.GetString(TelemetryFlagMetadata.ContextId); diff --git a/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs b/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs index ecab3386..88f79c1a 100644 --- a/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs +++ b/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs @@ -125,7 +125,7 @@ public void Build_ShouldHandleMissingReason(string? reason) var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); // Assert - Assert.Equal(Reason.Unknown, evaluationEvent.Attributes[TelemetryConstants.Reason]); + Assert.Equal(Reason.Unknown.ToLowerInvariant(), evaluationEvent.Attributes[TelemetryConstants.Reason]); } [Theory] From f91851d1adc5313b5208a8246a4b6f5f5e7d37dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <2493377+askpt@users.noreply.github.com> Date: Mon, 19 May 2025 07:58:31 +0100 Subject: [PATCH 19/20] refactor: change attributes type to IDictionary and make properties read-only in EvaluationEvent 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/Telemetry/EvaluationEvent.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/OpenFeature/Telemetry/EvaluationEvent.cs b/src/OpenFeature/Telemetry/EvaluationEvent.cs index c0cb1bbe..51506ad7 100644 --- a/src/OpenFeature/Telemetry/EvaluationEvent.cs +++ b/src/OpenFeature/Telemetry/EvaluationEvent.cs @@ -12,19 +12,19 @@ public class EvaluationEvent /// /// The name of the event. /// The attributes of the event. - public EvaluationEvent(string name, Dictionary attributes) + public EvaluationEvent(string name, IDictionary attributes) { - this.Name = name; - this.Attributes = attributes; + Name = name; + Attributes = new Dictionary(attributes); } /// - /// Gets or sets the name of the event. + /// Gets the name of the event. /// - public string Name { get; set; } + public string Name { get; } /// - /// Gets or sets the attributes of the event. + /// Gets the attributes of the event. /// - public Dictionary Attributes { get; set; } + public IReadOnlyDictionary Attributes { get; } } From b7d34fb01e5bd3149139960aea3a19a8ae0a3b6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <2493377+askpt@users.noreply.github.com> Date: Mon, 19 May 2025 08:03:25 +0100 Subject: [PATCH 20/20] refactor: change EvaluationEventBuilder to sealed and update Build method calls in 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> --- .../Telemetry/EvaluationEventBuilder.cs | 9 +++++++-- .../Telemetry/EvaluationEventBuilderTests.cs | 16 +++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs b/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs index e79bbda4..2f73224f 100644 --- a/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs +++ b/src/OpenFeature/Telemetry/EvaluationEventBuilder.cs @@ -7,17 +7,22 @@ namespace OpenFeature.Telemetry; /// /// Class for creating evaluation events for feature flags. /// -public static class EvaluationEventBuilder +public sealed class EvaluationEventBuilder { private const string EventName = "feature_flag.evaluation"; + /// + /// Gets the default instance of the . + /// + public static EvaluationEventBuilder Default { get; } = new(); + /// /// Creates an evaluation event based on the provided hook context and flag evaluation details. /// /// The context of the hook containing flag key and provider metadata. /// The details of the flag evaluation including reason, variant, and metadata. /// An instance of containing the event name, attributes, and body. - public static EvaluationEvent Build(HookContext hookContext, FlagEvaluationDetails details) + public EvaluationEvent Build(HookContext hookContext, FlagEvaluationDetails details) { var attributes = new Dictionary { diff --git a/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs b/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs index 88f79c1a..3b02a8ee 100644 --- a/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs +++ b/test/OpenFeature.Tests/Telemetry/EvaluationEventBuilderTests.cs @@ -8,6 +8,8 @@ namespace OpenFeature.Tests.Telemetry; public class EvaluationEventBuilderTests { + private readonly EvaluationEventBuilder _builder = EvaluationEventBuilder.Default; + [Fact] public void Build_ShouldReturnEventWithCorrectAttributes() { @@ -25,7 +27,7 @@ public void Build_ShouldReturnEventWithCorrectAttributes() reason: "reason", variant: "variant", flagMetadata: flagMetadata); // Act - var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); + var evaluationEvent = _builder.Build(hookContext, details); // Assert Assert.Equal("feature_flag.evaluation", evaluationEvent.Name); @@ -55,7 +57,7 @@ public void Build_ShouldHandleErrorDetails() errorMessage: "errorMessage", reason: "reason", variant: "variant", flagMetadata: flagMetadata); // Act - var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); + var evaluationEvent = _builder.Build(hookContext, details); // Assert Assert.Equal("general", evaluationEvent.Attributes[TelemetryConstants.ErrorCode]); @@ -79,7 +81,7 @@ public void Build_ShouldHandleMissingVariant() reason: "reason", variant: null, flagMetadata: flagMetadata); // Act - var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); + var evaluationEvent = _builder.Build(hookContext, details); // Assert Assert.Null(evaluationEvent.Attributes[TelemetryConstants.Variant]); @@ -98,7 +100,7 @@ public void Build_ShouldHandleMissingFlagMetadata() reason: "reason", variant: "", flagMetadata: flagMetadata); // Act - var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); + var evaluationEvent = _builder.Build(hookContext, details); // Assert Assert.Null(evaluationEvent.Attributes[TelemetryConstants.ContextId]); @@ -122,7 +124,7 @@ public void Build_ShouldHandleMissingReason(string? reason) reason: reason, variant: "", flagMetadata: flagMetadata); // Act - var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); + var evaluationEvent = _builder.Build(hookContext, details); // Assert Assert.Equal(Reason.Unknown.ToLowerInvariant(), evaluationEvent.Attributes[TelemetryConstants.Reason]); @@ -144,7 +146,7 @@ public void Build_ShouldHandleErrorWithEmptyErrorMessage(string? errorMessage) errorMessage: errorMessage, reason: "reason", variant: "", flagMetadata: flagMetadata); // Act - var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); + var evaluationEvent = _builder.Build(hookContext, details); // Assert Assert.Equal("general", evaluationEvent.Attributes[TelemetryConstants.ErrorCode]); @@ -164,7 +166,7 @@ public void Build_ShouldIncludeValueAttributeInEvent() reason: "reason", variant: "variant", flagMetadata: new ImmutableMetadata()); // Act - var evaluationEvent = EvaluationEventBuilder.Build(hookContext, details); + var evaluationEvent = _builder.Build(hookContext, details); // Assert Assert.Equal(testValue, evaluationEvent.Attributes[TelemetryConstants.Value]);