Skip to content

Commit 5c69ff1

Browse files
authored
feat: update telemetry package to conform to latest spec (#371)
* docs: add Go doc comments Signed-off-by: Sahid Velji <sahidvelji@gmail.com> * feat: update telemetry package to conform to latest spec Signed-off-by: Sahid Velji <sahidvelji@gmail.com> * fix: fix the attribute key names Signed-off-by: Sahid Velji <sahidvelji@gmail.com> * fix: remove Body from the EvaluationEvent Signed-off-by: Sahid Velji <sahidvelji@gmail.com> --------- Signed-off-by: Sahid Velji <sahidvelji@gmail.com>
1 parent c38ab0d commit 5c69ff1

File tree

2 files changed

+94
-89
lines changed

2 files changed

+94
-89
lines changed

openfeature/telemetry/telemetry.go

Lines changed: 60 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// Package telemetry provides utilities for extracting data from the OpenFeature SDK for use in telemetry signals.
12
package telemetry
23

34
import (
@@ -6,91 +7,94 @@ import (
67
"github.com/open-feature/go-sdk/openfeature"
78
)
89

10+
// EvaluationEvent represents an event that is emitted when a flag is evaluated.
11+
// It is intended to be used to record flag evaluation events as OpenTelemetry log records.
12+
// See the OpenFeature specification [Appendix D: Observability] and
13+
// the OpenTelemetry [Semantic conventions for feature flags in logs] for more information.
14+
//
15+
// [Appendix D: Observability]: https://openfeature.dev/specification/appendix-d
16+
// [Semantic conventions for feature flags in logs]: https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/
917
type EvaluationEvent struct {
10-
Name string
18+
// Name is the name of the event.
19+
// It is always "feature_flag.evaluation".
20+
Name string
21+
// Attributes represents the event's attributes.
1122
Attributes map[string]any
12-
Body map[string]any
1323
}
1424

25+
// The OpenTelemetry compliant event attributes for flag evaluation.
1526
const (
16-
// The OpenTelemetry compliant event attributes for flag evaluation.
17-
// Specification: https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/
18-
19-
TelemetryKey string = "feature_flag.key"
20-
TelemetryErrorCode string = "error.type"
21-
TelemetryVariant string = "feature_flag.variant"
22-
TelemetryContextID string = "feature_flag.context.id"
23-
TelemetryErrorMsg string = "feature_flag.evaluation.error.message"
24-
TelemetryReason string = "feature_flag.evaluation.reason"
25-
TelemetryProvider string = "feature_flag.provider_name"
26-
TelemetryFlagSetID string = "feature_flag.set.id"
27-
TelemetryVersion string = "feature_flag.version"
28-
29-
// Well-known flag metadata attributes for telemetry events.
30-
// Specification: https://openfeature.dev/specification/appendix-d#flag-metadata
31-
TelemetryFlagMetaContextId string = "contextId"
32-
TelemetryFlagMetaFlagSetId string = "flagSetId"
33-
TelemetryFlagMetaVersion string = "version"
34-
35-
// OpenTelemetry event body.
36-
// Specification: https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/
37-
TelemetryBody string = "value"
38-
39-
FlagEvaluationEventName string = "feature_flag.evaluation"
27+
FlagKey string = "feature_flag.key"
28+
ErrorTypeKey string = "error.type"
29+
ResultValueKey string = "feature_flag.result.value"
30+
ResultVariantKey string = "feature_flag.result.variant"
31+
ErrorMessageKey string = "error.message"
32+
ContextIDKey string = "feature_flag.context.id"
33+
ProviderNameKey string = "feature_flag.provider.name"
34+
ResultReasonKey string = "feature_flag.result.reason"
35+
FlagSetIDKey string = "feature_flag.set.id"
36+
VersionKey string = "feature_flag.version"
4037
)
4138

39+
// FlagEvaluationKey is the name of the feature flag evaluation event.
40+
const FlagEvaluationKey string = "feature_flag.evaluation"
41+
42+
const (
43+
flagMetaContextIDKey string = "contextId"
44+
flagMetaFlagSetIDKey string = "flagSetId"
45+
flagMetaVersionKey string = "version"
46+
)
47+
48+
// CreateEvaluationEvent creates an [EvaluationEvent].
49+
// It is intended to be used in the `Finally` stage of a [openfeature.Hook].
4250
func CreateEvaluationEvent(hookContext openfeature.HookContext, details openfeature.InterfaceEvaluationDetails) EvaluationEvent {
4351
attributes := map[string]any{
44-
TelemetryKey: hookContext.FlagKey(),
45-
TelemetryProvider: hookContext.ProviderMetadata().Name,
52+
FlagKey: hookContext.FlagKey(),
53+
ProviderNameKey: hookContext.ProviderMetadata().Name,
4654
}
4755

56+
attributes[ResultReasonKey] = strings.ToLower(string(openfeature.UnknownReason))
4857
if details.Reason != "" {
49-
attributes[TelemetryReason] = strings.ToLower(string(details.Reason))
50-
} else {
51-
attributes[TelemetryReason] = strings.ToLower(string(openfeature.UnknownReason))
58+
attributes[ResultReasonKey] = strings.ToLower(string(details.Reason))
5259
}
5360

54-
body := map[string]any{}
55-
5661
if details.Variant != "" {
57-
attributes[TelemetryVariant] = details.Variant
62+
attributes[ResultVariantKey] = details.Variant
5863
} else {
59-
body[TelemetryBody] = details.Value
64+
attributes[ResultValueKey] = details.Value
6065
}
6166

62-
contextID, exists := details.FlagMetadata[TelemetryFlagMetaContextId]
63-
if !exists {
64-
contextID = hookContext.EvaluationContext().TargetingKey()
67+
attributes[ContextIDKey] = hookContext.EvaluationContext().TargetingKey()
68+
if contextID, ok := details.FlagMetadata[flagMetaContextIDKey]; ok {
69+
attributes[ContextIDKey] = contextID
6570
}
6671

67-
attributes[TelemetryContextID] = contextID
68-
69-
setID, exists := details.FlagMetadata[TelemetryFlagMetaFlagSetId]
70-
if exists {
71-
attributes[TelemetryFlagSetID] = setID
72+
if setID, ok := details.FlagMetadata[flagMetaFlagSetIDKey]; ok {
73+
attributes[FlagSetIDKey] = setID
7274
}
7375

74-
version, exists := details.FlagMetadata[TelemetryFlagMetaVersion]
75-
if exists {
76-
attributes[TelemetryVersion] = version
76+
if version, ok := details.FlagMetadata[flagMetaVersionKey]; ok {
77+
attributes[VersionKey] = version
7778
}
7879

79-
if details.Reason == openfeature.ErrorReason {
80-
if details.ErrorCode != "" {
81-
attributes[TelemetryErrorCode] = details.ErrorCode
82-
} else {
83-
attributes[TelemetryErrorCode] = openfeature.GeneralCode
80+
if details.Reason != openfeature.ErrorReason {
81+
return EvaluationEvent{
82+
Name: FlagEvaluationKey,
83+
Attributes: attributes,
8484
}
85+
}
8586

86-
if details.ErrorMessage != "" {
87-
attributes[TelemetryErrorMsg] = details.ErrorMessage
88-
}
87+
attributes[ErrorTypeKey] = strings.ToLower(string(openfeature.GeneralCode))
88+
if details.ErrorCode != "" {
89+
attributes[ErrorTypeKey] = strings.ToLower(string(details.ErrorCode))
90+
}
91+
92+
if details.ErrorMessage != "" {
93+
attributes[ErrorMessageKey] = details.ErrorMessage
8994
}
9095

9196
return EvaluationEvent{
92-
Name: FlagEvaluationEventName,
97+
Name: FlagEvaluationKey,
9398
Attributes: attributes,
94-
Body: body,
9599
}
96100
}

openfeature/telemetry/telemetry_test.go

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -41,25 +41,24 @@ func TestCreateEvaluationEvent_1_3_1_BasicEvent(t *testing.T) {
4141
t.Errorf("Expected event name to be 'feature_flag.evaluation', got '%s'", event.Name)
4242
}
4343

44-
if event.Attributes[TelemetryKey] != flagKey {
45-
t.Errorf("Expected event attribute 'KEY' to be '%s', got '%s'", flagKey, event.Attributes[TelemetryKey])
44+
if event.Attributes[FlagKey] != flagKey {
45+
t.Errorf("Expected event attribute 'KEY' to be '%s', got '%s'", flagKey, event.Attributes[FlagKey])
4646
}
4747

48-
if event.Attributes[TelemetryReason] != strings.ToLower(string(openfeature.StaticReason)) {
49-
t.Errorf("Expected evaluation reason to be '%s', got '%s'", strings.ToLower(string(openfeature.StaticReason)), event.Attributes[TelemetryReason])
48+
if event.Attributes[ResultReasonKey] != strings.ToLower(string(openfeature.StaticReason)) {
49+
t.Errorf("Expected evaluation reason to be '%s', got '%s'", strings.ToLower(string(openfeature.StaticReason)), event.Attributes[ResultReasonKey])
5050
}
5151

52-
if event.Attributes[TelemetryProvider] != "test-provider" {
53-
t.Errorf("Expected provider name to be 'test-provider', got '%s'", event.Attributes[TelemetryProvider])
52+
if event.Attributes[ProviderNameKey] != "test-provider" {
53+
t.Errorf("Expected provider name to be 'test-provider', got '%s'", event.Attributes[ProviderNameKey])
5454
}
5555

56-
if event.Body[TelemetryBody] != true {
57-
t.Errorf("Expected event body 'VALUE' to be 'true', got '%v'", event.Body[TelemetryBody])
56+
if event.Attributes[ResultValueKey] != true {
57+
t.Errorf("Expected event attribute 'VALUE' to be 'true', got '%v'", event.Attributes[ResultValueKey])
5858
}
5959
}
6060

6161
func TestCreateEvaluationEvent_1_4_6_WithVariant(t *testing.T) {
62-
6362
flagKey := "test-flag"
6463

6564
mockProviderMetadata := openfeature.Metadata{
@@ -92,15 +91,15 @@ func TestCreateEvaluationEvent_1_4_6_WithVariant(t *testing.T) {
9291
t.Errorf("Expected event name to be 'feature_flag.evaluation', got '%s'", event.Name)
9392
}
9493

95-
if event.Attributes[TelemetryKey] != flagKey {
96-
t.Errorf("Expected event attribute 'KEY' to be '%s', got '%s'", flagKey, event.Attributes[TelemetryKey])
94+
if event.Attributes[FlagKey] != flagKey {
95+
t.Errorf("Expected event attribute 'KEY' to be '%s', got '%s'", flagKey, event.Attributes[FlagKey])
9796
}
9897

99-
if event.Attributes[TelemetryVariant] != "true" {
100-
t.Errorf("Expected event attribute 'VARIANT' to be 'true', got '%s'", event.Attributes[TelemetryVariant])
98+
if event.Attributes[ResultVariantKey] != "true" {
99+
t.Errorf("Expected event attribute 'VARIANT' to be 'true', got '%s'", event.Attributes[ResultVariantKey])
101100
}
102-
103101
}
102+
104103
func TestCreateEvaluationEvent_1_4_14_WithFlagMetaData(t *testing.T) {
105104
flagKey := "test-flag"
106105

@@ -124,28 +123,29 @@ func TestCreateEvaluationEvent_1_4_14_WithFlagMetaData(t *testing.T) {
124123
FlagType: openfeature.Boolean,
125124
ResolutionDetail: openfeature.ResolutionDetail{
126125
FlagMetadata: openfeature.FlagMetadata{
127-
TelemetryFlagMetaFlagSetId: "test-set",
128-
TelemetryFlagMetaContextId: "metadata-context",
129-
TelemetryFlagMetaVersion: "v1.0",
126+
flagMetaFlagSetIDKey: "test-set",
127+
flagMetaContextIDKey: "metadata-context",
128+
flagMetaVersionKey: "v1.0",
130129
},
131130
},
132131
},
133132
}
134133

135134
event := CreateEvaluationEvent(mockHookContext, mockDetails)
136135

137-
if event.Attributes[TelemetryFlagSetID] != "test-set" {
138-
t.Errorf("Expected 'Flag SetID' in Flag Metadata name to be 'test-set', got '%s'", event.Attributes[TelemetryFlagMetaFlagSetId])
136+
if event.Attributes[FlagSetIDKey] != "test-set" {
137+
t.Errorf("Expected 'Flag SetID' in Flag Metadata name to be 'test-set', got '%s'", event.Attributes[flagMetaFlagSetIDKey])
139138
}
140139

141-
if event.Attributes[TelemetryContextID] != "metadata-context" {
142-
t.Errorf("Expected 'Flag ContextID' in Flag Metadata name to be 'metadata-context', got '%s'", event.Attributes[TelemetryFlagMetaContextId])
140+
if event.Attributes[ContextIDKey] != "metadata-context" {
141+
t.Errorf("Expected 'Flag ContextID' in Flag Metadata name to be 'metadata-context', got '%s'", event.Attributes[flagMetaContextIDKey])
143142
}
144143

145-
if event.Attributes[TelemetryVersion] != "v1.0" {
146-
t.Errorf("Expected 'Flag Version' in Flag Metadata name to be 'v1.0', got '%s'", event.Attributes[TelemetryFlagMetaVersion])
144+
if event.Attributes[VersionKey] != "v1.0" {
145+
t.Errorf("Expected 'Flag Version' in Flag Metadata name to be 'v1.0', got '%s'", event.Attributes[flagMetaVersionKey])
147146
}
148147
}
148+
149149
func TestCreateEvaluationEvent_1_4_8_WithErrors(t *testing.T) {
150150
flagKey := "test-flag"
151151

@@ -177,12 +177,12 @@ func TestCreateEvaluationEvent_1_4_8_WithErrors(t *testing.T) {
177177

178178
event := CreateEvaluationEvent(mockHookContext, mockDetails)
179179

180-
if event.Attributes[TelemetryErrorCode] != openfeature.FlagNotFoundCode {
181-
t.Errorf("Expected 'ERROR_CODE' to be 'GENERAL', got '%s'", event.Attributes[TelemetryErrorCode])
180+
if event.Attributes[ErrorTypeKey] != strings.ToLower(string(openfeature.FlagNotFoundCode)) {
181+
t.Errorf("Expected 'ERROR_CODE' to be 'GENERAL', got '%s'", event.Attributes[ErrorTypeKey])
182182
}
183183

184-
if event.Attributes[TelemetryErrorMsg] != "a test error" {
185-
t.Errorf("Expected 'ERROR_MESSAGE' to be 'a test error', got '%s'", event.Attributes[TelemetryErrorMsg])
184+
if event.Attributes[ErrorMessageKey] != "a test error" {
185+
t.Errorf("Expected 'ERROR_MESSAGE' to be 'a test error', got '%s'", event.Attributes[ErrorMessageKey])
186186
}
187187
}
188188

@@ -216,14 +216,15 @@ func TestCreateEvaluationEvent_1_4_8_WithGeneralErrors(t *testing.T) {
216216

217217
event := CreateEvaluationEvent(mockHookContext, mockDetails)
218218

219-
if event.Attributes[TelemetryErrorCode] != openfeature.GeneralCode {
220-
t.Errorf("Expected 'ERROR_CODE' to be 'GENERAL', got '%s'", event.Attributes[TelemetryErrorCode])
219+
if event.Attributes[ErrorTypeKey] != strings.ToLower(string(openfeature.GeneralCode)) {
220+
t.Errorf("Expected 'ERROR_CODE' to be 'GENERAL', got '%s'", event.Attributes[ErrorTypeKey])
221221
}
222222

223-
if event.Attributes[TelemetryErrorMsg] != "a test error" {
224-
t.Errorf("Expected 'ERROR_MESSAGE' to be 'a test error', got '%s'", event.Attributes[TelemetryErrorMsg])
223+
if event.Attributes[ErrorMessageKey] != "a test error" {
224+
t.Errorf("Expected 'ERROR_MESSAGE' to be 'a test error', got '%s'", event.Attributes[ErrorMessageKey])
225225
}
226226
}
227+
227228
func TestCreateEvaluationEvent_1_4_7_WithUnknownReason(t *testing.T) {
228229
flagKey := "test-flag"
229230

@@ -252,7 +253,7 @@ func TestCreateEvaluationEvent_1_4_7_WithUnknownReason(t *testing.T) {
252253

253254
event := CreateEvaluationEvent(mockHookContext, mockDetails)
254255

255-
if event.Attributes[TelemetryReason] != strings.ToLower(string(openfeature.UnknownReason)) {
256-
t.Errorf("Expected evaluation reason to be '%s', got '%s'", strings.ToLower(string(openfeature.UnknownReason)), event.Attributes[TelemetryReason])
256+
if event.Attributes[ResultReasonKey] != strings.ToLower(string(openfeature.UnknownReason)) {
257+
t.Errorf("Expected evaluation reason to be '%s', got '%s'", strings.ToLower(string(openfeature.UnknownReason)), event.Attributes[ResultReasonKey])
257258
}
258259
}

0 commit comments

Comments
 (0)