From 4e62d2b2c7a6bc8dbb97fd424e8864df9e1873f3 Mon Sep 17 00:00:00 2001 From: bbland1 <104288486+bbland1@users.noreply.github.com> Date: Tue, 18 Feb 2025 23:42:44 -0500 Subject: [PATCH 01/10] feat:a base idea of telemetry util after looking at js version Signed-off-by: bbland1 <104288486+bbland1@users.noreply.github.com> --- openfeature/telemetry.go | 158 ++++++++++++++++++++++++++++++++++ openfeature/telemetry_test.go | 19 ++++ 2 files changed, 177 insertions(+) create mode 100644 openfeature/telemetry.go create mode 100644 openfeature/telemetry_test.go diff --git a/openfeature/telemetry.go b/openfeature/telemetry.go new file mode 100644 index 00000000..a9be5a5d --- /dev/null +++ b/openfeature/telemetry.go @@ -0,0 +1,158 @@ +package openfeature + +import "strings" + +// // TelemetryAttribute defines an OpenTelemetry compliant event attributes for flag evaluation. +// // Specification: https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/ +// type TelemetryAttribute struct { +// Key string // The lookup key of the feature flag. +// ErrorCode string // Describes an error that the operation ended with. +// Variant string // A semantic identifier for an evaluated flag. +// ContextID string // The unique identifier for the the flag evaluation context. For example, the targeting key. +// ErrorMessage string // A message explaining the nature of an error occurring during flag evaluation. +// Reason string // The reason code which shows how a feature flag value was determined. +// Provider string // Describes an error the operation ended with. +// FlagSetID string // The identifier of the flag set to which the feature flag belongs. +// Version string // The version of the ruleset used during the evaluation. This may be any stable value which uniquely identifies the ruleset. +// } + +// TelemetryEvaluationData Event data, sometimes referred 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. +// Specification: https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/ +// type TelemetryEvaluationData struct { +// value string // The evaluated value of the feature flag. +// } + +// Well-known flag metadata attributes for telemetry events. +// Specification: https://openfeature.dev/specification/appendix-d#flag-metadata +// type TelemetryFlagMetadata struct { +// ContextId string +// FlagSetId string +// Version string +// } + +type EvaluationEvent struct { + Name string + Attributes map[string]interface{} + Data map[string]interface{} +} + +const ( + FlagEvaluationEventName string = "feature_flag.evaluation" + + // The OpenTelemetry compliant event attributes for flag evaluation. + // Specification: https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/ + + // The lookup key of the feature flag. + // requirement level:`required` + // example: `logo-color` + TelemetryKey string = "feature_flag.key" + + // Describes a category/type of error the operation ended with. + // requirement level: `conditionally + // required condition: `reason` is `error` + // example: `flag_not_found` + TelemetryErrorCode string = "error.type" + + // A semantic identifier for an evaluated flag value. + // requirement level: `conditionally required` + // condition: variant is defined on the evaluation details + // example: `blue`; `on`; `true` + TelemetryVariant string = "feature_flag.variant" + + // The unique identifier for the flag evaluation context. For example, the targeting key. + // requirement level: `recommended` + // example: `5157782b-2203-4c80-a857-dbbd5e7761db` + TelemetryContextID string = "feature_flag.context.id" + + // A message explaining the nature of an error occurring during flag evaluation. + // requirement level: `recommended` + // example: `Flag not found` + TelemetryErrorMsg string = "feature_flag.evaluation.error.message" + + // The reason code which shows how a feature flag value was determined. + // requirement level: `recommended` + // example: `targeting_match` + TelemetryReason string = "feature_flag.evaluation.reason" + + // Describes a category/type error the operation ended with. + // requirement level: `recommended` + // example: `flag_not_found` + TelemetryProvider string = "feature_flag.provider_name" + + // The identifier of the flag set to which the feature flag belongs. + // requirement level: `recommended` + // example: `proj-1`; `ab98sgs`; `service1/dev` + TelemetryFlagSetID string = "feature_flag.set.id" + + // The version of the ruleset used during the evaluation. + // This may be any stable value which uniquely identifies the ruleset. + // requirement level: `recommended` + // example: `1.0.0`; `2021-01-01` + TelemetryVersion string = "feature_flag.version" + + + // Well-known flag metadata attributes for telemetry events. + // Specification: https://openfeature.dev/specification/appendix-d#flag-metadata + + + // 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. + TelemetryFlagMetaContextId string = "contextId" + + // A logical identifier for the flag set. + TelemetryFlagMetaFlagSetId string = "flagSetId" + + // A version string (format unspecified) for the flag or flag set. + TelemetryFlagMetaVersion string = "version" + + // TelemetryEvaluationData Event data, sometimes referred 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. + // Specification: https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/ + + // The evaluated value of the feature flag. + // requirement level: `conditionally required` + // condition: variant is not defined on the evaluation details + // example: `#ff0000`; `1`; `true` + TelemetryEvalData string = "value" +) + +func CreateEvaluationEvent(hookContext HookContext, evaluationDetails InterfaceEvaluationDetails) EvaluationEvent { + attributes := map[string]interface{}{ + TelemetryKey: hookContext.flagKey, + TelemetryProvider: hookContext.providerMetadata.Name, + } + + if evaluationDetails.ResolutionDetail.Reason == "" { + attributes[TelemetryReason] = strings.ToLower(string(UnknownReason)) + } + + data := map[string]interface{}{} + + if evaluationDetails.Variant != "" { + attributes[TelemetryVariant] = evaluationDetails.Variant + } else { + data[TelemetryEvalData] = evaluationDetails.Value + } + + contextID, exists := evaluationDetails.FlagMetadata[TelemetryFlagMetaContextId] + if !exists || contextID == "" { + contextID = hookContext.evaluationContext.targetingKey + } + + if contextID != "" { + attributes[TelemetryContextID] = contextID + } + + setID, exists := evaluationDetails.FlagMetadata[TelemetryFlagMetaFlagSetId] + if exists && setID != "" { + attributes[TelemetryFlagSetID] = setID + } + + return EvaluationEvent{ + Name: FlagEvaluationEventName, + Attributes: attributes, + Data: data, + } +} diff --git a/openfeature/telemetry_test.go b/openfeature/telemetry_test.go new file mode 100644 index 00000000..2ebb5214 --- /dev/null +++ b/openfeature/telemetry_test.go @@ -0,0 +1,19 @@ +package openfeature + +import "testing" + +func TestCreateEvaluationEvent_1_3_1(t *testing.T){ + +} +func TestCreateEvaluationEvent_WithVariant(t *testing.T){ + +} +func TestCreateEvaluationEvent_WithFlagMetaData(t *testing.T){ + +} +func TestCreateEvaluationEvent_WithErrors(t *testing.T){ + +} +func TestCreateEvaluationEvent_withUnknownReason(t *testing.T){ + +} \ No newline at end of file From b567a3328802229e62e58302d8e87062035f951d Mon Sep 17 00:00:00 2001 From: bbland1 <104288486+bbland1@users.noreply.github.com> Date: Wed, 19 Feb 2025 00:16:07 -0500 Subject: [PATCH 02/10] style: cleaning up comments Signed-off-by: bbland1 <104288486+bbland1@users.noreply.github.com> --- openfeature/telemetry.go | 100 ++++----------------------------------- 1 file changed, 9 insertions(+), 91 deletions(-) diff --git a/openfeature/telemetry.go b/openfeature/telemetry.go index a9be5a5d..96f5c04a 100644 --- a/openfeature/telemetry.go +++ b/openfeature/telemetry.go @@ -2,34 +2,6 @@ package openfeature import "strings" -// // TelemetryAttribute defines an OpenTelemetry compliant event attributes for flag evaluation. -// // Specification: https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/ -// type TelemetryAttribute struct { -// Key string // The lookup key of the feature flag. -// ErrorCode string // Describes an error that the operation ended with. -// Variant string // A semantic identifier for an evaluated flag. -// ContextID string // The unique identifier for the the flag evaluation context. For example, the targeting key. -// ErrorMessage string // A message explaining the nature of an error occurring during flag evaluation. -// Reason string // The reason code which shows how a feature flag value was determined. -// Provider string // Describes an error the operation ended with. -// FlagSetID string // The identifier of the flag set to which the feature flag belongs. -// Version string // The version of the ruleset used during the evaluation. This may be any stable value which uniquely identifies the ruleset. -// } - -// TelemetryEvaluationData Event data, sometimes referred 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. -// Specification: https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/ -// type TelemetryEvaluationData struct { -// value string // The evaluated value of the feature flag. -// } - -// Well-known flag metadata attributes for telemetry events. -// Specification: https://openfeature.dev/specification/appendix-d#flag-metadata -// type TelemetryFlagMetadata struct { -// ContextId string -// FlagSetId string -// Version string -// } - type EvaluationEvent struct { Name string Attributes map[string]interface{} @@ -42,101 +14,47 @@ const ( // The OpenTelemetry compliant event attributes for flag evaluation. // Specification: https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/ - // The lookup key of the feature flag. - // requirement level:`required` - // example: `logo-color` TelemetryKey string = "feature_flag.key" - - // Describes a category/type of error the operation ended with. - // requirement level: `conditionally - // required condition: `reason` is `error` - // example: `flag_not_found` TelemetryErrorCode string = "error.type" - - // A semantic identifier for an evaluated flag value. - // requirement level: `conditionally required` - // condition: variant is defined on the evaluation details - // example: `blue`; `on`; `true` TelemetryVariant string = "feature_flag.variant" - - // The unique identifier for the flag evaluation context. For example, the targeting key. - // requirement level: `recommended` - // example: `5157782b-2203-4c80-a857-dbbd5e7761db` TelemetryContextID string = "feature_flag.context.id" - - // A message explaining the nature of an error occurring during flag evaluation. - // requirement level: `recommended` - // example: `Flag not found` TelemetryErrorMsg string = "feature_flag.evaluation.error.message" - - // The reason code which shows how a feature flag value was determined. - // requirement level: `recommended` - // example: `targeting_match` TelemetryReason string = "feature_flag.evaluation.reason" - - // Describes a category/type error the operation ended with. - // requirement level: `recommended` - // example: `flag_not_found` TelemetryProvider string = "feature_flag.provider_name" - - // The identifier of the flag set to which the feature flag belongs. - // requirement level: `recommended` - // example: `proj-1`; `ab98sgs`; `service1/dev` TelemetryFlagSetID string = "feature_flag.set.id" - - // The version of the ruleset used during the evaluation. - // This may be any stable value which uniquely identifies the ruleset. - // requirement level: `recommended` - // example: `1.0.0`; `2021-01-01` TelemetryVersion string = "feature_flag.version" // Well-known flag metadata attributes for telemetry events. // Specification: https://openfeature.dev/specification/appendix-d#flag-metadata - - - // 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. TelemetryFlagMetaContextId string = "contextId" - - // A logical identifier for the flag set. TelemetryFlagMetaFlagSetId string = "flagSetId" - - // A version string (format unspecified) for the flag or flag set. TelemetryFlagMetaVersion string = "version" - // TelemetryEvaluationData Event data, sometimes referred 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. + // OpenTelemetry event body. // Specification: https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/ - - // The evaluated value of the feature flag. - // requirement level: `conditionally required` - // condition: variant is not defined on the evaluation details - // example: `#ff0000`; `1`; `true` - TelemetryEvalData string = "value" + TelemetryBody string = "value" ) -func CreateEvaluationEvent(hookContext HookContext, evaluationDetails InterfaceEvaluationDetails) EvaluationEvent { +func CreateEvaluationEvent(hookContext HookContext, evalDetails InterfaceEvaluationDetails) EvaluationEvent { attributes := map[string]interface{}{ TelemetryKey: hookContext.flagKey, TelemetryProvider: hookContext.providerMetadata.Name, } - if evaluationDetails.ResolutionDetail.Reason == "" { + if evalDetails.ResolutionDetail.Reason == "" { attributes[TelemetryReason] = strings.ToLower(string(UnknownReason)) } data := map[string]interface{}{} - if evaluationDetails.Variant != "" { - attributes[TelemetryVariant] = evaluationDetails.Variant + if evalDetails.Variant != "" { + attributes[TelemetryVariant] = evalDetails.Variant } else { - data[TelemetryEvalData] = evaluationDetails.Value + data[TelemetryBody] = evalDetails.Value } - contextID, exists := evaluationDetails.FlagMetadata[TelemetryFlagMetaContextId] + contextID, exists := evalDetails.FlagMetadata[TelemetryFlagMetaContextId] if !exists || contextID == "" { contextID = hookContext.evaluationContext.targetingKey } @@ -145,7 +63,7 @@ func CreateEvaluationEvent(hookContext HookContext, evaluationDetails InterfaceE attributes[TelemetryContextID] = contextID } - setID, exists := evaluationDetails.FlagMetadata[TelemetryFlagMetaFlagSetId] + setID, exists := evalDetails.FlagMetadata[TelemetryFlagMetaFlagSetId] if exists && setID != "" { attributes[TelemetryFlagSetID] = setID } From cea1d82e6b6f857148b91c9c9e108e87bd3492a1 Mon Sep 17 00:00:00 2001 From: bbland1 <104288486+bbland1@users.noreply.github.com> Date: Wed, 19 Feb 2025 02:27:15 -0500 Subject: [PATCH 03/10] feat: version and reason ser for the eval event, passing first test Signed-off-by: bbland1 <104288486+bbland1@users.noreply.github.com> --- openfeature/telemetry.go | 43 +++++++++++++++++------ openfeature/telemetry_test.go | 64 +++++++++++++++++++++++++++++++---- 2 files changed, 89 insertions(+), 18 deletions(-) diff --git a/openfeature/telemetry.go b/openfeature/telemetry.go index 96f5c04a..159588a0 100644 --- a/openfeature/telemetry.go +++ b/openfeature/telemetry.go @@ -1,16 +1,16 @@ package openfeature -import "strings" +import ( + "strings" +) type EvaluationEvent struct { - Name string + Name string Attributes map[string]interface{} - Data map[string]interface{} + Body map[string]interface{} } -const ( - FlagEvaluationEventName string = "feature_flag.evaluation" - +const ( // The OpenTelemetry compliant event attributes for flag evaluation. // Specification: https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/ @@ -34,6 +34,8 @@ const ( // OpenTelemetry event body. // Specification: https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/ TelemetryBody string = "value" + + FlagEvaluationEventName string = "feature_flag.evaluation" ) func CreateEvaluationEvent(hookContext HookContext, evalDetails InterfaceEvaluationDetails) EvaluationEvent { @@ -42,16 +44,18 @@ func CreateEvaluationEvent(hookContext HookContext, evalDetails InterfaceEvaluat TelemetryProvider: hookContext.providerMetadata.Name, } - if evalDetails.ResolutionDetail.Reason == "" { + if evalDetails.ResolutionDetail.Reason != "" { + attributes[TelemetryReason] = strings.ToLower(string(evalDetails.ResolutionDetail.Reason)) + } else { attributes[TelemetryReason] = strings.ToLower(string(UnknownReason)) } - data := map[string]interface{}{} + body := map[string]interface{}{} if evalDetails.Variant != "" { attributes[TelemetryVariant] = evalDetails.Variant } else { - data[TelemetryBody] = evalDetails.Value + body[TelemetryBody] = evalDetails.Value } contextID, exists := evalDetails.FlagMetadata[TelemetryFlagMetaContextId] @@ -68,9 +72,26 @@ func CreateEvaluationEvent(hookContext HookContext, evalDetails InterfaceEvaluat attributes[TelemetryFlagSetID] = setID } + version, exists := evalDetails.FlagMetadata[TelemetryFlagMetaVersion] + if exists && version != "" { + attributes[TelemetryVersion] = version + } + + if evalDetails.ResolutionDetail.Reason == ErrorReason { + if evalDetails.ResolutionDetail.ErrorCode != "" { + attributes[TelemetryErrorCode] = evalDetails.ResolutionDetail.ErrorCode + } else { + attributes[TelemetryErrorCode] = GeneralCode + } + + if evalDetails.ResolutionDetail.ErrorMessage != "" { + attributes[TelemetryErrorMsg] = evalDetails.ResolutionDetail.ErrorMessage + } + } + return EvaluationEvent{ - Name: FlagEvaluationEventName, + Name: FlagEvaluationEventName, Attributes: attributes, - Data: data, + Body: body, } } diff --git a/openfeature/telemetry_test.go b/openfeature/telemetry_test.go index 2ebb5214..b548a925 100644 --- a/openfeature/telemetry_test.go +++ b/openfeature/telemetry_test.go @@ -1,19 +1,69 @@ package openfeature -import "testing" +import ( + "strings" + "testing" +) -func TestCreateEvaluationEvent_1_3_1(t *testing.T){ +func TestCreateEvaluationEvent_1_3_1(t *testing.T) { + flagKey := "test-flag" + mockProviderMetadata := Metadata{ + Name: "test-provider", + } + mockClientMetadata := ClientMetadata{ + domain: "test-client", + } + mockHookContext := HookContext{ + flagKey: flagKey, + flagType: Boolean, + defaultValue: true, + clientMetadata: mockClientMetadata, + providerMetadata: mockProviderMetadata, + } + mockDetails := InterfaceEvaluationDetails{ + Value: true, + EvaluationDetails: EvaluationDetails{ + FlagKey: flagKey, + FlagType: Boolean, + ResolutionDetail: ResolutionDetail{ + Reason: StaticReason, + FlagMetadata: FlagMetadata{}, + }, + }, + } + + event := CreateEvaluationEvent(mockHookContext, mockDetails) + + if event.Name != "feature_flag.evaluation" { + t.Errorf("Expected event name to be 'feature_flag.evaluation', got '%s'", event.Name) + } + + if event.Attributes[TelemetryKey] != flagKey { + t.Errorf("Expected event attribute 'KEY' to be '%s', got '%s'", flagKey, event.Attributes[TelemetryKey]) + } + + if event.Attributes[TelemetryReason] != strings.ToLower(string(StaticReason)) { + t.Errorf("Expected evaluation reason to be '%s', got '%s'", strings.ToLower(string(StaticReason)), event.Attributes[TelemetryReason]) + } + + if event.Attributes[TelemetryProvider] != "test-provider" { + t.Errorf("Expected provider name to be 'test-provider', got '%s'", event.Attributes[TelemetryProvider]) + } + + if event.Body[TelemetryBody] != true { + t.Errorf("Expected event body 'VALUE' to be 'true', got '%v'", event.Body[TelemetryBody]) + } } -func TestCreateEvaluationEvent_WithVariant(t *testing.T){ +func TestCreateEvaluationEvent_WithVariant(t *testing.T) { } -func TestCreateEvaluationEvent_WithFlagMetaData(t *testing.T){ +func TestCreateEvaluationEvent_WithFlagMetaData(t *testing.T) { } -func TestCreateEvaluationEvent_WithErrors(t *testing.T){ +func TestCreateEvaluationEvent_WithErrors(t *testing.T) { } -func TestCreateEvaluationEvent_withUnknownReason(t *testing.T){ +func TestCreateEvaluationEvent_withUnknownReason(t *testing.T) { -} \ No newline at end of file +} From ca366cbc1e023b685319034e46b92ea8b87f5986 Mon Sep 17 00:00:00 2001 From: bbland1 <104288486+bbland1@users.noreply.github.com> Date: Wed, 19 Feb 2025 14:29:58 -0500 Subject: [PATCH 04/10] test: writing unit tests for flag metadata and with variant Signed-off-by: bbland1 <104288486+bbland1@users.noreply.github.com> --- openfeature/telemetry_test.go | 90 +++++++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 3 deletions(-) diff --git a/openfeature/telemetry_test.go b/openfeature/telemetry_test.go index b548a925..99278f03 100644 --- a/openfeature/telemetry_test.go +++ b/openfeature/telemetry_test.go @@ -5,7 +5,7 @@ import ( "testing" ) -func TestCreateEvaluationEvent_1_3_1(t *testing.T) { +func TestCreateEvaluationEvent_1_3_1_BasicEvent(t *testing.T) { flagKey := "test-flag" mockProviderMetadata := Metadata{ Name: "test-provider", @@ -55,11 +55,95 @@ func TestCreateEvaluationEvent_1_3_1(t *testing.T) { t.Errorf("Expected event body 'VALUE' to be 'true', got '%v'", event.Body[TelemetryBody]) } } -func TestCreateEvaluationEvent_WithVariant(t *testing.T) { +func TestCreateEvaluationEvent_1_4_6_WithVariant(t *testing.T) { + flagKey := "test-flag" + mockProviderMetadata := Metadata{ + Name: "test-provider", + } + mockClientMetadata := ClientMetadata{ + domain: "test-client", + } + mockHookContext := HookContext{ + flagKey: flagKey, + flagType: Boolean, + defaultValue: true, + clientMetadata: mockClientMetadata, + providerMetadata: mockProviderMetadata, + } + + mockDetails := InterfaceEvaluationDetails{ + Value: true, + EvaluationDetails: EvaluationDetails{ + FlagKey: flagKey, + FlagType: Boolean, + ResolutionDetail: ResolutionDetail{ + Variant: "true", + }, + }, + } + + event := CreateEvaluationEvent(mockHookContext, mockDetails) + + if event.Name != "feature_flag.evaluation" { + t.Errorf("Expected event name to be 'feature_flag.evaluation', got '%s'", event.Name) + } + + if event.Attributes[TelemetryKey] != flagKey { + t.Errorf("Expected event attribute 'KEY' to be '%s', got '%s'", flagKey, event.Attributes[TelemetryKey]) + } + + if event.Attributes[TelemetryVariant] != "true" { + t.Errorf("Expected event attribute 'VARIANT' to be 'true', got '%s'", event.Attributes[TelemetryVariant]) + } + if event.Attributes[TelemetryReason] != strings.ToLower(string(UnknownReason)) { + t.Errorf("Expected evaluation reason to be '%s', got '%s'", strings.ToLower(string(UnknownReason)), event.Attributes[TelemetryReason]) + } } -func TestCreateEvaluationEvent_WithFlagMetaData(t *testing.T) { +func TestCreateEvaluationEvent_1_4_14_WithFlagMetaData(t *testing.T) { + flagKey := "test-flag" + mockProviderMetadata := Metadata{ + Name: "test-provider", + } + mockClientMetadata := ClientMetadata{ + domain: "test-client", + } + mockHookContext := HookContext{ + flagKey: flagKey, + flagType: Boolean, + defaultValue: true, + clientMetadata: mockClientMetadata, + providerMetadata: mockProviderMetadata, + } + + mockDetails := InterfaceEvaluationDetails{ + Value: false, + EvaluationDetails: EvaluationDetails{ + FlagKey: flagKey, + FlagType: Boolean, + ResolutionDetail: ResolutionDetail{ + FlagMetadata: FlagMetadata{ + TelemetryFlagMetaFlagSetId: "test-set", + TelemetryFlagMetaContextId: "metadata-context", + TelemetryFlagMetaVersion: "v1.0", + }, + }, + }, + } + + event := CreateEvaluationEvent(mockHookContext, mockDetails) + if event.Attributes[TelemetryFlagMetaFlagSetId] != "test-set" { + t.Errorf("Expected 'Flag SetID' in Flag Metadata name to be 'test-set', got '%s'", event.Attributes[TelemetryFlagMetaFlagSetId]) + } + + if event.Attributes[TelemetryFlagMetaContextId] != "metadata-context" { + t.Errorf("Expected 'Flag ContextID' in Flag Metadata name to be 'metadata-context', got '%s'", event.Attributes[TelemetryFlagMetaContextId]) + } + + if event.Attributes[TelemetryFlagMetaVersion] != "v1.0" { + t.Errorf("Expected 'Flag Version' in Flag Metadata name to be 'v1.0', got '%s'", event.Attributes[TelemetryFlagMetaVersion]) + } } func TestCreateEvaluationEvent_WithErrors(t *testing.T) { From 040e2abe193f7eeacabfa8015289f32e834ff244 Mon Sep 17 00:00:00 2001 From: bbland1 <104288486+bbland1@users.noreply.github.com> Date: Wed, 19 Feb 2025 14:31:05 -0500 Subject: [PATCH 05/10] fix: flag metadata not being properly set causing failing variant test Signed-off-by: bbland1 <104288486+bbland1@users.noreply.github.com> --- openfeature/telemetry.go | 44 +++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/openfeature/telemetry.go b/openfeature/telemetry.go index 159588a0..0326e4ea 100644 --- a/openfeature/telemetry.go +++ b/openfeature/telemetry.go @@ -38,54 +38,52 @@ const ( FlagEvaluationEventName string = "feature_flag.evaluation" ) -func CreateEvaluationEvent(hookContext HookContext, evalDetails InterfaceEvaluationDetails) EvaluationEvent { +func CreateEvaluationEvent(hookContext HookContext, details InterfaceEvaluationDetails) EvaluationEvent { attributes := map[string]interface{}{ TelemetryKey: hookContext.flagKey, TelemetryProvider: hookContext.providerMetadata.Name, } - if evalDetails.ResolutionDetail.Reason != "" { - attributes[TelemetryReason] = strings.ToLower(string(evalDetails.ResolutionDetail.Reason)) + if details.EvaluationDetails.ResolutionDetail.Reason != "" { + attributes[TelemetryReason] = strings.ToLower(string(details.ResolutionDetail.Reason)) } else { attributes[TelemetryReason] = strings.ToLower(string(UnknownReason)) } body := map[string]interface{}{} - if evalDetails.Variant != "" { - attributes[TelemetryVariant] = evalDetails.Variant + if details.Variant != "" { + attributes[TelemetryVariant] = details.EvaluationDetails.ResolutionDetail.Variant } else { - body[TelemetryBody] = evalDetails.Value + body[TelemetryBody] = details.Value } - contextID, exists := evalDetails.FlagMetadata[TelemetryFlagMetaContextId] - if !exists || contextID == "" { + contextID, exists := details.EvaluationDetails.ResolutionDetail.FlagMetadata[TelemetryFlagMetaContextId] + if exists && contextID != "" { + attributes[TelemetryFlagMetaContextId] = contextID + } else { contextID = hookContext.evaluationContext.targetingKey } - if contextID != "" { - attributes[TelemetryContextID] = contextID - } - - setID, exists := evalDetails.FlagMetadata[TelemetryFlagMetaFlagSetId] - if exists && setID != "" { - attributes[TelemetryFlagSetID] = setID + setID, exists := details.EvaluationDetails.ResolutionDetail.FlagMetadata[TelemetryFlagMetaFlagSetId] + if exists { + attributes[TelemetryFlagMetaFlagSetId] = setID } - version, exists := evalDetails.FlagMetadata[TelemetryFlagMetaVersion] - if exists && version != "" { - attributes[TelemetryVersion] = version + version, exists := details.EvaluationDetails.ResolutionDetail.FlagMetadata[TelemetryFlagMetaVersion] + if exists { + attributes[TelemetryFlagMetaVersion] = version } - if evalDetails.ResolutionDetail.Reason == ErrorReason { - if evalDetails.ResolutionDetail.ErrorCode != "" { - attributes[TelemetryErrorCode] = evalDetails.ResolutionDetail.ErrorCode + if details.EvaluationDetails.ResolutionDetail.Reason == ErrorReason { + if details.ResolutionDetail.ErrorCode != "" { + attributes[TelemetryErrorCode] = details.ResolutionDetail.ErrorCode } else { attributes[TelemetryErrorCode] = GeneralCode } - if evalDetails.ResolutionDetail.ErrorMessage != "" { - attributes[TelemetryErrorMsg] = evalDetails.ResolutionDetail.ErrorMessage + if details.ResolutionDetail.ErrorMessage != "" { + attributes[TelemetryErrorMsg] = details.ResolutionDetail.ErrorMessage } } From 91d8bde5a7be0d457625b96466697890070415f3 Mon Sep 17 00:00:00 2001 From: bbland1 <104288486+bbland1@users.noreply.github.com> Date: Wed, 19 Feb 2025 16:05:43 -0500 Subject: [PATCH 06/10] test: writing test for unknown reason check and with errors Signed-off-by: bbland1 <104288486+bbland1@users.noreply.github.com> --- openfeature/telemetry_test.go | 77 +++++++++++++++++++++++++++++++---- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/openfeature/telemetry_test.go b/openfeature/telemetry_test.go index 99278f03..759792e6 100644 --- a/openfeature/telemetry_test.go +++ b/openfeature/telemetry_test.go @@ -55,6 +55,7 @@ func TestCreateEvaluationEvent_1_3_1_BasicEvent(t *testing.T) { t.Errorf("Expected event body 'VALUE' to be 'true', got '%v'", event.Body[TelemetryBody]) } } + func TestCreateEvaluationEvent_1_4_6_WithVariant(t *testing.T) { flagKey := "test-flag" mockProviderMetadata := Metadata{ @@ -96,9 +97,6 @@ func TestCreateEvaluationEvent_1_4_6_WithVariant(t *testing.T) { t.Errorf("Expected event attribute 'VARIANT' to be 'true', got '%s'", event.Attributes[TelemetryVariant]) } - if event.Attributes[TelemetryReason] != strings.ToLower(string(UnknownReason)) { - t.Errorf("Expected evaluation reason to be '%s', got '%s'", strings.ToLower(string(UnknownReason)), event.Attributes[TelemetryReason]) - } } func TestCreateEvaluationEvent_1_4_14_WithFlagMetaData(t *testing.T) { flagKey := "test-flag" @@ -125,7 +123,7 @@ func TestCreateEvaluationEvent_1_4_14_WithFlagMetaData(t *testing.T) { FlagMetadata: FlagMetadata{ TelemetryFlagMetaFlagSetId: "test-set", TelemetryFlagMetaContextId: "metadata-context", - TelemetryFlagMetaVersion: "v1.0", + TelemetryFlagMetaVersion: "v1.0", }, }, }, @@ -136,7 +134,7 @@ func TestCreateEvaluationEvent_1_4_14_WithFlagMetaData(t *testing.T) { if event.Attributes[TelemetryFlagMetaFlagSetId] != "test-set" { t.Errorf("Expected 'Flag SetID' in Flag Metadata name to be 'test-set', got '%s'", event.Attributes[TelemetryFlagMetaFlagSetId]) } - + if event.Attributes[TelemetryFlagMetaContextId] != "metadata-context" { t.Errorf("Expected 'Flag ContextID' in Flag Metadata name to be 'metadata-context', got '%s'", event.Attributes[TelemetryFlagMetaContextId]) } @@ -145,9 +143,74 @@ func TestCreateEvaluationEvent_1_4_14_WithFlagMetaData(t *testing.T) { t.Errorf("Expected 'Flag Version' in Flag Metadata name to be 'v1.0', got '%s'", event.Attributes[TelemetryFlagMetaVersion]) } } -func TestCreateEvaluationEvent_WithErrors(t *testing.T) { +func TestCreateEvaluationEvent_1_4_8_WithErrors(t *testing.T) { + flagKey := "test-flag" + mockProviderMetadata := Metadata{ + Name: "test-provider", + } + mockClientMetadata := ClientMetadata{ + domain: "test-client", + } + mockHookContext := HookContext{ + flagKey: flagKey, + flagType: Boolean, + defaultValue: true, + clientMetadata: mockClientMetadata, + providerMetadata: mockProviderMetadata, + } + mockDetails := InterfaceEvaluationDetails{ + Value: false, + EvaluationDetails: EvaluationDetails{ + FlagKey: flagKey, + ResolutionDetail: ResolutionDetail{ + Reason: ErrorReason, + ErrorCode: GeneralCode, + ErrorMessage: "a test error", + FlagMetadata: FlagMetadata{}, + }, + }, + } + + event := CreateEvaluationEvent(mockHookContext, mockDetails) + + if event.Attributes[TelemetryErrorCode] != GeneralCode { + t.Errorf("Expected 'ERROR_CODE' to be 'GENERAL', got '%s'", event.Attributes[TelemetryErrorCode]) + } + + if event.Attributes[TelemetryErrorMsg] != "a test error" { + t.Errorf("Expected 'ERROR_MESSAGE' to be 'a test error', got '%s'", event.Attributes[TelemetryErrorMsg]) + } } -func TestCreateEvaluationEvent_withUnknownReason(t *testing.T) { +func TestCreateEvaluationEvent_1_4_7_WithUnknownReason(t *testing.T) { + flagKey := "test-flag" + mockProviderMetadata := Metadata{ + Name: "test-provider", + } + mockClientMetadata := ClientMetadata{ + domain: "test-client", + } + mockHookContext := HookContext{ + flagKey: flagKey, + flagType: Boolean, + defaultValue: true, + clientMetadata: mockClientMetadata, + providerMetadata: mockProviderMetadata, + } + mockDetails := InterfaceEvaluationDetails{ + Value: false, + EvaluationDetails: EvaluationDetails{ + FlagKey: flagKey, + ResolutionDetail: ResolutionDetail{ + FlagMetadata: FlagMetadata{}, + }, + }, + } + + event := CreateEvaluationEvent(mockHookContext, mockDetails) + + if event.Attributes[TelemetryReason] != strings.ToLower(string(UnknownReason)) { + t.Errorf("Expected evaluation reason to be '%s', got '%s'", strings.ToLower(string(UnknownReason)), event.Attributes[TelemetryReason]) + } } From c3d2da68c786eec34585ba5e50192778a984b29d Mon Sep 17 00:00:00 2001 From: bbland1 <104288486+bbland1@users.noreply.github.com> Date: Wed, 19 Feb 2025 16:43:24 -0500 Subject: [PATCH 07/10] test: adding tests for General error code and test specific error code based on codecov report Signed-off-by: bbland1 <104288486+bbland1@users.noreply.github.com> --- openfeature/telemetry_test.go | 41 ++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/openfeature/telemetry_test.go b/openfeature/telemetry_test.go index 759792e6..26ed55a0 100644 --- a/openfeature/telemetry_test.go +++ b/openfeature/telemetry_test.go @@ -165,7 +165,46 @@ func TestCreateEvaluationEvent_1_4_8_WithErrors(t *testing.T) { FlagKey: flagKey, ResolutionDetail: ResolutionDetail{ Reason: ErrorReason, - ErrorCode: GeneralCode, + ErrorCode: FlagNotFoundCode, + ErrorMessage: "a test error", + FlagMetadata: FlagMetadata{}, + }, + }, + } + + event := CreateEvaluationEvent(mockHookContext, mockDetails) + + if event.Attributes[TelemetryErrorCode] != FlagNotFoundCode { + t.Errorf("Expected 'ERROR_CODE' to be 'GENERAL', got '%s'", event.Attributes[TelemetryErrorCode]) + } + + if event.Attributes[TelemetryErrorMsg] != "a test error" { + t.Errorf("Expected 'ERROR_MESSAGE' to be 'a test error', got '%s'", event.Attributes[TelemetryErrorMsg]) + } +} + +func TestCreateEvaluationEvent_1_4_8_WithGeneralErrors(t *testing.T) { + flagKey := "test-flag" + mockProviderMetadata := Metadata{ + Name: "test-provider", + } + mockClientMetadata := ClientMetadata{ + domain: "test-client", + } + mockHookContext := HookContext{ + flagKey: flagKey, + flagType: Boolean, + defaultValue: true, + clientMetadata: mockClientMetadata, + providerMetadata: mockProviderMetadata, + } + + mockDetails := InterfaceEvaluationDetails{ + Value: false, + EvaluationDetails: EvaluationDetails{ + FlagKey: flagKey, + ResolutionDetail: ResolutionDetail{ + Reason: ErrorReason, ErrorMessage: "a test error", FlagMetadata: FlagMetadata{}, }, From 1f326b7e87867a21959aa28a89f5018f97046c11 Mon Sep 17 00:00:00 2001 From: bbland1 <104288486+bbland1@users.noreply.github.com> Date: Wed, 19 Feb 2025 22:16:25 -0500 Subject: [PATCH 08/10] refactor: changing variable that is never used caught by the linter to set what is being set by the if-else Signed-off-by: bbland1 <104288486+bbland1@users.noreply.github.com> --- openfeature/telemetry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openfeature/telemetry.go b/openfeature/telemetry.go index 0326e4ea..6fbe5814 100644 --- a/openfeature/telemetry.go +++ b/openfeature/telemetry.go @@ -62,7 +62,7 @@ func CreateEvaluationEvent(hookContext HookContext, details InterfaceEvaluationD if exists && contextID != "" { attributes[TelemetryFlagMetaContextId] = contextID } else { - contextID = hookContext.evaluationContext.targetingKey + attributes[TelemetryFlagMetaContextId] = hookContext.evaluationContext.targetingKey } setID, exists := details.EvaluationDetails.ResolutionDetail.FlagMetadata[TelemetryFlagMetaFlagSetId] From 78afe2b0a34701c83437009c0389418e4433e0bb Mon Sep 17 00:00:00 2001 From: bbland1 <104288486+bbland1@users.noreply.github.com> Date: Mon, 24 Feb 2025 19:02:59 -0500 Subject: [PATCH 09/10] refactor: moved telemetry to own package & updated some types for readability Signed-off-by: bbland1 <104288486+bbland1@users.noreply.github.com> --- openfeature/{ => telemetry}/telemetry.go | 26 ++- openfeature/{ => telemetry}/telemetry_test.go | 211 +++++++++--------- 2 files changed, 121 insertions(+), 116 deletions(-) rename openfeature/{ => telemetry}/telemetry.go (77%) rename openfeature/{ => telemetry}/telemetry_test.go (50%) diff --git a/openfeature/telemetry.go b/openfeature/telemetry/telemetry.go similarity index 77% rename from openfeature/telemetry.go rename to openfeature/telemetry/telemetry.go index 6fbe5814..b9345a00 100644 --- a/openfeature/telemetry.go +++ b/openfeature/telemetry/telemetry.go @@ -1,13 +1,15 @@ -package openfeature +package telemetry import ( "strings" + + "github.com/open-feature/go-sdk/openfeature" ) type EvaluationEvent struct { Name string - Attributes map[string]interface{} - Body map[string]interface{} + Attributes map[string]any + Body map[string]any } const ( @@ -38,19 +40,19 @@ const ( FlagEvaluationEventName string = "feature_flag.evaluation" ) -func CreateEvaluationEvent(hookContext HookContext, details InterfaceEvaluationDetails) EvaluationEvent { - attributes := map[string]interface{}{ - TelemetryKey: hookContext.flagKey, - TelemetryProvider: hookContext.providerMetadata.Name, +func CreateEvaluationEvent(hookContext openfeature.HookContext, details openfeature.InterfaceEvaluationDetails) EvaluationEvent { + attributes := map[string]any{ + TelemetryKey: hookContext.FlagKey(), + TelemetryProvider: hookContext.ProviderMetadata().Name, } if details.EvaluationDetails.ResolutionDetail.Reason != "" { attributes[TelemetryReason] = strings.ToLower(string(details.ResolutionDetail.Reason)) } else { - attributes[TelemetryReason] = strings.ToLower(string(UnknownReason)) + attributes[TelemetryReason] = strings.ToLower(string(openfeature.UnknownReason)) } - body := map[string]interface{}{} + body := map[string]any{} if details.Variant != "" { attributes[TelemetryVariant] = details.EvaluationDetails.ResolutionDetail.Variant @@ -62,7 +64,7 @@ func CreateEvaluationEvent(hookContext HookContext, details InterfaceEvaluationD if exists && contextID != "" { attributes[TelemetryFlagMetaContextId] = contextID } else { - attributes[TelemetryFlagMetaContextId] = hookContext.evaluationContext.targetingKey + attributes[TelemetryFlagMetaContextId] = hookContext.EvaluationContext().TargetingKey() } setID, exists := details.EvaluationDetails.ResolutionDetail.FlagMetadata[TelemetryFlagMetaFlagSetId] @@ -75,11 +77,11 @@ func CreateEvaluationEvent(hookContext HookContext, details InterfaceEvaluationD attributes[TelemetryFlagMetaVersion] = version } - if details.EvaluationDetails.ResolutionDetail.Reason == ErrorReason { + if details.EvaluationDetails.ResolutionDetail.Reason == openfeature.ErrorReason { if details.ResolutionDetail.ErrorCode != "" { attributes[TelemetryErrorCode] = details.ResolutionDetail.ErrorCode } else { - attributes[TelemetryErrorCode] = GeneralCode + attributes[TelemetryErrorCode] = openfeature.GeneralCode } if details.ResolutionDetail.ErrorMessage != "" { diff --git a/openfeature/telemetry_test.go b/openfeature/telemetry/telemetry_test.go similarity index 50% rename from openfeature/telemetry_test.go rename to openfeature/telemetry/telemetry_test.go index 26ed55a0..ec818087 100644 --- a/openfeature/telemetry_test.go +++ b/openfeature/telemetry/telemetry_test.go @@ -1,34 +1,36 @@ -package openfeature +package telemetry import ( "strings" "testing" + + "github.com/open-feature/go-sdk/openfeature" ) func TestCreateEvaluationEvent_1_3_1_BasicEvent(t *testing.T) { flagKey := "test-flag" - mockProviderMetadata := Metadata{ + + mockProviderMetadata := openfeature.Metadata{ Name: "test-provider", } - mockClientMetadata := ClientMetadata{ - domain: "test-client", - } - mockHookContext := HookContext{ - flagKey: flagKey, - flagType: Boolean, - defaultValue: true, - clientMetadata: mockClientMetadata, - providerMetadata: mockProviderMetadata, - } - mockDetails := InterfaceEvaluationDetails{ + mockClientMetadata := openfeature.NewClientMetadata("test-client") + + mockEvalCtx := openfeature.NewEvaluationContext( + "test-target-key", map[string]any{ + "is": "a test", + }) + + mockHookContext := openfeature.NewHookContext(flagKey, openfeature.Boolean, true, mockClientMetadata, mockProviderMetadata, mockEvalCtx) + + mockDetails := openfeature.InterfaceEvaluationDetails{ Value: true, - EvaluationDetails: EvaluationDetails{ + EvaluationDetails: openfeature.EvaluationDetails{ FlagKey: flagKey, - FlagType: Boolean, - ResolutionDetail: ResolutionDetail{ - Reason: StaticReason, - FlagMetadata: FlagMetadata{}, + FlagType: openfeature.Boolean, + ResolutionDetail: openfeature.ResolutionDetail{ + Reason: openfeature.StaticReason, + FlagMetadata: openfeature.FlagMetadata{}, }, }, } @@ -43,8 +45,8 @@ func TestCreateEvaluationEvent_1_3_1_BasicEvent(t *testing.T) { t.Errorf("Expected event attribute 'KEY' to be '%s', got '%s'", flagKey, event.Attributes[TelemetryKey]) } - if event.Attributes[TelemetryReason] != strings.ToLower(string(StaticReason)) { - t.Errorf("Expected evaluation reason to be '%s', got '%s'", strings.ToLower(string(StaticReason)), event.Attributes[TelemetryReason]) + if event.Attributes[TelemetryReason] != strings.ToLower(string(openfeature.StaticReason)) { + t.Errorf("Expected evaluation reason to be '%s', got '%s'", strings.ToLower(string(openfeature.StaticReason)), event.Attributes[TelemetryReason]) } if event.Attributes[TelemetryProvider] != "test-provider" { @@ -57,27 +59,28 @@ func TestCreateEvaluationEvent_1_3_1_BasicEvent(t *testing.T) { } func TestCreateEvaluationEvent_1_4_6_WithVariant(t *testing.T) { + flagKey := "test-flag" - mockProviderMetadata := Metadata{ + + mockProviderMetadata := openfeature.Metadata{ Name: "test-provider", } - mockClientMetadata := ClientMetadata{ - domain: "test-client", - } - mockHookContext := HookContext{ - flagKey: flagKey, - flagType: Boolean, - defaultValue: true, - clientMetadata: mockClientMetadata, - providerMetadata: mockProviderMetadata, - } - mockDetails := InterfaceEvaluationDetails{ + mockClientMetadata := openfeature.NewClientMetadata("test-client") + + mockEvalCtx := openfeature.NewEvaluationContext( + "test-target-key", map[string]any{ + "is": "a test", + }) + + mockHookContext := openfeature.NewHookContext(flagKey, openfeature.Boolean, true, mockClientMetadata, mockProviderMetadata, mockEvalCtx) + + mockDetails := openfeature.InterfaceEvaluationDetails{ Value: true, - EvaluationDetails: EvaluationDetails{ + EvaluationDetails: openfeature.EvaluationDetails{ FlagKey: flagKey, - FlagType: Boolean, - ResolutionDetail: ResolutionDetail{ + FlagType: openfeature.Boolean, + ResolutionDetail: openfeature.ResolutionDetail{ Variant: "true", }, }, @@ -100,27 +103,27 @@ func TestCreateEvaluationEvent_1_4_6_WithVariant(t *testing.T) { } func TestCreateEvaluationEvent_1_4_14_WithFlagMetaData(t *testing.T) { flagKey := "test-flag" - mockProviderMetadata := Metadata{ + + mockProviderMetadata := openfeature.Metadata{ Name: "test-provider", } - mockClientMetadata := ClientMetadata{ - domain: "test-client", - } - mockHookContext := HookContext{ - flagKey: flagKey, - flagType: Boolean, - defaultValue: true, - clientMetadata: mockClientMetadata, - providerMetadata: mockProviderMetadata, - } - mockDetails := InterfaceEvaluationDetails{ + mockClientMetadata := openfeature.NewClientMetadata("test-client") + + mockEvalCtx := openfeature.NewEvaluationContext( + "test-target-key", map[string]any{ + "is": "a test", + }) + + mockHookContext := openfeature.NewHookContext(flagKey, openfeature.Boolean, false, mockClientMetadata, mockProviderMetadata, mockEvalCtx) + + mockDetails := openfeature.InterfaceEvaluationDetails{ Value: false, - EvaluationDetails: EvaluationDetails{ + EvaluationDetails: openfeature.EvaluationDetails{ FlagKey: flagKey, - FlagType: Boolean, - ResolutionDetail: ResolutionDetail{ - FlagMetadata: FlagMetadata{ + FlagType: openfeature.Boolean, + ResolutionDetail: openfeature.ResolutionDetail{ + FlagMetadata: openfeature.FlagMetadata{ TelemetryFlagMetaFlagSetId: "test-set", TelemetryFlagMetaContextId: "metadata-context", TelemetryFlagMetaVersion: "v1.0", @@ -145,36 +148,36 @@ func TestCreateEvaluationEvent_1_4_14_WithFlagMetaData(t *testing.T) { } func TestCreateEvaluationEvent_1_4_8_WithErrors(t *testing.T) { flagKey := "test-flag" - mockProviderMetadata := Metadata{ + + mockProviderMetadata := openfeature.Metadata{ Name: "test-provider", } - mockClientMetadata := ClientMetadata{ - domain: "test-client", - } - mockHookContext := HookContext{ - flagKey: flagKey, - flagType: Boolean, - defaultValue: true, - clientMetadata: mockClientMetadata, - providerMetadata: mockProviderMetadata, - } - mockDetails := InterfaceEvaluationDetails{ + mockClientMetadata := openfeature.NewClientMetadata("test-client") + + mockEvalCtx := openfeature.NewEvaluationContext( + "test-target-key", map[string]any{ + "is": "a test", + }) + + mockHookContext := openfeature.NewHookContext(flagKey, openfeature.Boolean, false, mockClientMetadata, mockProviderMetadata, mockEvalCtx) + + mockDetails := openfeature.InterfaceEvaluationDetails{ Value: false, - EvaluationDetails: EvaluationDetails{ + EvaluationDetails: openfeature.EvaluationDetails{ FlagKey: flagKey, - ResolutionDetail: ResolutionDetail{ - Reason: ErrorReason, - ErrorCode: FlagNotFoundCode, + ResolutionDetail: openfeature.ResolutionDetail{ + Reason: openfeature.ErrorReason, + ErrorCode: openfeature.FlagNotFoundCode, ErrorMessage: "a test error", - FlagMetadata: FlagMetadata{}, + FlagMetadata: openfeature.FlagMetadata{}, }, }, } event := CreateEvaluationEvent(mockHookContext, mockDetails) - if event.Attributes[TelemetryErrorCode] != FlagNotFoundCode { + if event.Attributes[TelemetryErrorCode] != openfeature.FlagNotFoundCode { t.Errorf("Expected 'ERROR_CODE' to be 'GENERAL', got '%s'", event.Attributes[TelemetryErrorCode]) } @@ -185,35 +188,35 @@ func TestCreateEvaluationEvent_1_4_8_WithErrors(t *testing.T) { func TestCreateEvaluationEvent_1_4_8_WithGeneralErrors(t *testing.T) { flagKey := "test-flag" - mockProviderMetadata := Metadata{ + + mockProviderMetadata := openfeature.Metadata{ Name: "test-provider", } - mockClientMetadata := ClientMetadata{ - domain: "test-client", - } - mockHookContext := HookContext{ - flagKey: flagKey, - flagType: Boolean, - defaultValue: true, - clientMetadata: mockClientMetadata, - providerMetadata: mockProviderMetadata, - } - mockDetails := InterfaceEvaluationDetails{ + mockClientMetadata := openfeature.NewClientMetadata("test-client") + + mockEvalCtx := openfeature.NewEvaluationContext( + "test-target-key", map[string]any{ + "is": "a test", + }) + + mockHookContext := openfeature.NewHookContext(flagKey, openfeature.Boolean, false, mockClientMetadata, mockProviderMetadata, mockEvalCtx) + + mockDetails := openfeature.InterfaceEvaluationDetails{ Value: false, - EvaluationDetails: EvaluationDetails{ + EvaluationDetails: openfeature.EvaluationDetails{ FlagKey: flagKey, - ResolutionDetail: ResolutionDetail{ - Reason: ErrorReason, + ResolutionDetail: openfeature.ResolutionDetail{ + Reason: openfeature.ErrorReason, ErrorMessage: "a test error", - FlagMetadata: FlagMetadata{}, + FlagMetadata: openfeature.FlagMetadata{}, }, }, } event := CreateEvaluationEvent(mockHookContext, mockDetails) - if event.Attributes[TelemetryErrorCode] != GeneralCode { + if event.Attributes[TelemetryErrorCode] != openfeature.GeneralCode { t.Errorf("Expected 'ERROR_CODE' to be 'GENERAL', got '%s'", event.Attributes[TelemetryErrorCode]) } @@ -223,33 +226,33 @@ func TestCreateEvaluationEvent_1_4_8_WithGeneralErrors(t *testing.T) { } func TestCreateEvaluationEvent_1_4_7_WithUnknownReason(t *testing.T) { flagKey := "test-flag" - mockProviderMetadata := Metadata{ + + mockProviderMetadata := openfeature.Metadata{ Name: "test-provider", } - mockClientMetadata := ClientMetadata{ - domain: "test-client", - } - mockHookContext := HookContext{ - flagKey: flagKey, - flagType: Boolean, - defaultValue: true, - clientMetadata: mockClientMetadata, - providerMetadata: mockProviderMetadata, - } - mockDetails := InterfaceEvaluationDetails{ - Value: false, - EvaluationDetails: EvaluationDetails{ + mockClientMetadata := openfeature.NewClientMetadata("test-client") + + mockEvalCtx := openfeature.NewEvaluationContext( + "test-target-key", map[string]any{ + "is": "a test", + }) + + mockHookContext := openfeature.NewHookContext(flagKey, openfeature.Boolean, true, mockClientMetadata, mockProviderMetadata, mockEvalCtx) + + mockDetails := openfeature.InterfaceEvaluationDetails{ + Value: true, + EvaluationDetails: openfeature.EvaluationDetails{ FlagKey: flagKey, - ResolutionDetail: ResolutionDetail{ - FlagMetadata: FlagMetadata{}, + ResolutionDetail: openfeature.ResolutionDetail{ + FlagMetadata: openfeature.FlagMetadata{}, }, }, } event := CreateEvaluationEvent(mockHookContext, mockDetails) - if event.Attributes[TelemetryReason] != strings.ToLower(string(UnknownReason)) { - t.Errorf("Expected evaluation reason to be '%s', got '%s'", strings.ToLower(string(UnknownReason)), event.Attributes[TelemetryReason]) + if event.Attributes[TelemetryReason] != strings.ToLower(string(openfeature.UnknownReason)) { + t.Errorf("Expected evaluation reason to be '%s', got '%s'", strings.ToLower(string(openfeature.UnknownReason)), event.Attributes[TelemetryReason]) } } From 2e8a4be7c11dcf17e4564a911d6f8f56b59dc774 Mon Sep 17 00:00:00 2001 From: bbland1 <104288486+bbland1@users.noreply.github.com> Date: Tue, 25 Feb 2025 21:45:26 -0500 Subject: [PATCH 10/10] fix: put the unused const values in the proper attributes Signed-off-by: bbland1 <104288486+bbland1@users.noreply.github.com> --- openfeature/telemetry/telemetry.go | 16 ++++++++-------- openfeature/telemetry/telemetry_test.go | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/openfeature/telemetry/telemetry.go b/openfeature/telemetry/telemetry.go index b9345a00..82ff67ce 100644 --- a/openfeature/telemetry/telemetry.go +++ b/openfeature/telemetry/telemetry.go @@ -61,20 +61,20 @@ func CreateEvaluationEvent(hookContext openfeature.HookContext, details openfeat } contextID, exists := details.EvaluationDetails.ResolutionDetail.FlagMetadata[TelemetryFlagMetaContextId] - if exists && contextID != "" { - attributes[TelemetryFlagMetaContextId] = contextID - } else { - attributes[TelemetryFlagMetaContextId] = hookContext.EvaluationContext().TargetingKey() - } - + if !exists { + contextID = hookContext.EvaluationContext().TargetingKey() + } + + attributes[TelemetryContextID] = contextID + setID, exists := details.EvaluationDetails.ResolutionDetail.FlagMetadata[TelemetryFlagMetaFlagSetId] if exists { - attributes[TelemetryFlagMetaFlagSetId] = setID + attributes[TelemetryFlagSetID] = setID } version, exists := details.EvaluationDetails.ResolutionDetail.FlagMetadata[TelemetryFlagMetaVersion] if exists { - attributes[TelemetryFlagMetaVersion] = version + attributes[TelemetryVersion] = version } if details.EvaluationDetails.ResolutionDetail.Reason == openfeature.ErrorReason { diff --git a/openfeature/telemetry/telemetry_test.go b/openfeature/telemetry/telemetry_test.go index ec818087..a0c7321f 100644 --- a/openfeature/telemetry/telemetry_test.go +++ b/openfeature/telemetry/telemetry_test.go @@ -134,15 +134,15 @@ func TestCreateEvaluationEvent_1_4_14_WithFlagMetaData(t *testing.T) { event := CreateEvaluationEvent(mockHookContext, mockDetails) - if event.Attributes[TelemetryFlagMetaFlagSetId] != "test-set" { + if event.Attributes[TelemetryFlagSetID] != "test-set" { t.Errorf("Expected 'Flag SetID' in Flag Metadata name to be 'test-set', got '%s'", event.Attributes[TelemetryFlagMetaFlagSetId]) } - if event.Attributes[TelemetryFlagMetaContextId] != "metadata-context" { + if event.Attributes[TelemetryContextID] != "metadata-context" { t.Errorf("Expected 'Flag ContextID' in Flag Metadata name to be 'metadata-context', got '%s'", event.Attributes[TelemetryFlagMetaContextId]) } - if event.Attributes[TelemetryFlagMetaVersion] != "v1.0" { + if event.Attributes[TelemetryVersion] != "v1.0" { t.Errorf("Expected 'Flag Version' in Flag Metadata name to be 'v1.0', got '%s'", event.Attributes[TelemetryFlagMetaVersion]) } }