diff --git a/go.mod b/go.mod index 542d35ac..af7f8db3 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.23.0 require ( github.com/cucumber/godog v0.15.0 github.com/go-logr/logr v1.4.3 + go.opentelemetry.io/otel v1.37.0 go.uber.org/mock v0.5.2 golang.org/x/text v0.26.0 ) diff --git a/go.sum b/go.sum index a0dc7573..db5bfc4b 100644 --- a/go.sum +++ b/go.sum @@ -9,14 +9,14 @@ github.com/cucumber/messages/go/v22 v22.0.0/go.mod h1:aZipXTKc0JnjCsXrJnuZpWhtay github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.3.1+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -45,16 +45,13 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= -go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/openfeature/telemetry/telemetry.go b/openfeature/telemetry/telemetry.go index 14f363be..a97d8f2a 100644 --- a/openfeature/telemetry/telemetry.go +++ b/openfeature/telemetry/telemetry.go @@ -5,39 +5,12 @@ import ( "strings" "github.com/open-feature/go-sdk/openfeature" + "go.opentelemetry.io/otel/attribute" + semconv "go.opentelemetry.io/otel/semconv/v1.34.0" ) -// EvaluationEvent represents an event that is emitted when a flag is evaluated. -// It is intended to be used to record flag evaluation events as OpenTelemetry log records. -// See the OpenFeature specification [Appendix D: Observability] and -// the OpenTelemetry [Semantic conventions for feature flags in logs] for more information. -// -// [Appendix D: Observability]: https://openfeature.dev/specification/appendix-d -// [Semantic conventions for feature flags in logs]: https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/ -type EvaluationEvent struct { - // Name is the name of the event. - // It is always "feature_flag.evaluation". - Name string - // Attributes represents the event's attributes. - Attributes map[string]any -} - -// The OpenTelemetry compliant event attributes for flag evaluation. -const ( - FlagKey string = "feature_flag.key" - ErrorTypeKey string = "error.type" - ResultValueKey string = "feature_flag.result.value" - ResultVariantKey string = "feature_flag.result.variant" - ErrorMessageKey string = "error.message" - ContextIDKey string = "feature_flag.context.id" - ProviderNameKey string = "feature_flag.provider.name" - ResultReasonKey string = "feature_flag.result.reason" - FlagSetIDKey string = "feature_flag.set.id" - VersionKey string = "feature_flag.version" -) - -// FlagEvaluationKey is the name of the feature flag evaluation event. -const FlagEvaluationKey string = "feature_flag.evaluation" +// EventName is the name of the feature flag evaluation event. +const EventName string = "feature_flag.evaluation" const ( flagMetaContextIDKey string = "contextId" @@ -45,56 +18,51 @@ const ( flagMetaVersionKey string = "version" ) -// CreateEvaluationEvent creates an [EvaluationEvent]. +// EventAttributes returns a slice of OpenTelemetry attributes that can be used to create an event for a feature flag evaluation. // It is intended to be used in the `Finally` stage of a [openfeature.Hook]. -func CreateEvaluationEvent(hookContext openfeature.HookContext, details openfeature.InterfaceEvaluationDetails) EvaluationEvent { - attributes := map[string]any{ - FlagKey: hookContext.FlagKey(), - ProviderNameKey: hookContext.ProviderMetadata().Name, +func EventAttributes(hookContext openfeature.HookContext, details openfeature.InterfaceEvaluationDetails) []attribute.KeyValue { + attributes := []attribute.KeyValue{ + semconv.FeatureFlagKey(hookContext.FlagKey()), + semconv.FeatureFlagProviderName(hookContext.ProviderMetadata().Name), } - attributes[ResultReasonKey] = strings.ToLower(string(openfeature.UnknownReason)) + reason := strings.ToLower(string(openfeature.UnknownReason)) if details.Reason != "" { - attributes[ResultReasonKey] = strings.ToLower(string(details.Reason)) + reason = strings.ToLower(string(details.Reason)) } + attributes = append(attributes, semconv.FeatureFlagResultReasonKey.String(reason)) if details.Variant != "" { - attributes[ResultVariantKey] = details.Variant - } else { - attributes[ResultValueKey] = details.Value + attributes = append(attributes, semconv.FeatureFlagResultVariant(details.Variant)) } - attributes[ContextIDKey] = hookContext.EvaluationContext().TargetingKey() - if contextID, ok := details.FlagMetadata[flagMetaContextIDKey]; ok { - attributes[ContextIDKey] = contextID + contextID := hookContext.EvaluationContext().TargetingKey() + if flagMetaContextID, ok := details.FlagMetadata[flagMetaContextIDKey].(string); ok { + contextID = flagMetaContextID } + attributes = append(attributes, semconv.FeatureFlagContextID(contextID)) - if setID, ok := details.FlagMetadata[flagMetaFlagSetIDKey]; ok { - attributes[FlagSetIDKey] = setID + if setID, ok := details.FlagMetadata[flagMetaFlagSetIDKey].(string); ok { + attributes = append(attributes, semconv.FeatureFlagSetID(setID)) } - if version, ok := details.FlagMetadata[flagMetaVersionKey]; ok { - attributes[VersionKey] = version + if version, ok := details.FlagMetadata[flagMetaVersionKey].(string); ok { + attributes = append(attributes, semconv.FeatureFlagVersion(version)) } if details.Reason != openfeature.ErrorReason { - return EvaluationEvent{ - Name: FlagEvaluationKey, - Attributes: attributes, - } + return attributes } - attributes[ErrorTypeKey] = strings.ToLower(string(openfeature.GeneralCode)) + errorType := strings.ToLower(string(openfeature.GeneralCode)) if details.ErrorCode != "" { - attributes[ErrorTypeKey] = strings.ToLower(string(details.ErrorCode)) + errorType = strings.ToLower(string(details.ErrorCode)) } + attributes = append(attributes, semconv.ErrorTypeKey.String(errorType)) if details.ErrorMessage != "" { - attributes[ErrorMessageKey] = details.ErrorMessage + attributes = append(attributes, semconv.ErrorMessage(details.ErrorMessage)) } - return EvaluationEvent{ - Name: FlagEvaluationKey, - Attributes: attributes, - } + return attributes }