Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally we like to keep our SDK free of non-test dependencies. Of course, we can make exceptions for some cases, and OTel could be such an exception, but I think I'd advocate for implementing this in the contrib as you alluded to in slack.

I think if we are going to implement it in the SDK directly, we should avoid this dependency. The only SDK that includes OTel bits directly is .NET, but that's a bit of a different case since recent .NET runtimes actually include OTel.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@beeme1mr @sahidvelji my main question is, how realistic is it for an org to reject the use of OpenFeature because of this dep? Is OTel "controversial" for some orgs? Might a big org that offers telemetry products, which would otherwise adopt OpenFeature, decide not to just because of this dep?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So Go dependency "tree shaking" happens at the package level. So unless a user imports the telemetry package, they will not get the OTel dependency. But again, given that the OTel hooks live in contrib instead of the SDK, I think it might make sense for this package to also live in contrib instead.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be fine given how it's used.

go.uber.org/mock v0.5.2
golang.org/x/text v0.26.0
)
Expand Down
15 changes: 6 additions & 9 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
86 changes: 27 additions & 59 deletions openfeature/telemetry/telemetry.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,96 +5,64 @@ 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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is what you & Michael were asking when it would be added to semconv in #407 correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it doesn't currently exist in the latest semconv 1.34.0, which is why this const is needed for now
https://pkg.go.dev/go.opentelemetry.io/[email protected]/semconv/v1.34.0#section-readme


const (
flagMetaContextIDKey string = "contextId"
flagMetaFlagSetIDKey string = "flagSetId"
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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is a new function, should we keep the old one with a deprecation warning? I don't want technical debt to pile up, but this seems like an easy way to avoid abruptly adding a breaking change.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could branch our v1 now and this could be a breaking change, but I think we should consider this.

If we want to implement this in contribs instead, to avoid the OTel dep (which would be my preference) I'd recommend we just cut the v1 maintenance branch now, and then completely remove this function here and implement it in contribs.

WDYT @beeme1mr @sahidvelji ?

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
}
Loading