-
Notifications
You must be signed in to change notification settings - Fork 47
refactor: use attribute.KeyValue
instead of map
#418
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
|
||
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could branch our 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 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 | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.There was a problem hiding this comment.
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.