Skip to content

Commit 4d0a91c

Browse files
authored
feat: update OpenTelemetry hook to use the latest semconv (#713)
Signed-off-by: Sahid Velji <[email protected]>
1 parent 374cd5d commit 4d0a91c

File tree

8 files changed

+107
-97
lines changed

8 files changed

+107
-97
lines changed

hooks/open-telemetry/go.mod

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ go 1.23.0
44

55
require (
66
github.com/open-feature/go-sdk v1.15.1
7-
go.opentelemetry.io/otel v1.36.0
8-
go.opentelemetry.io/otel/metric v1.36.0
9-
go.opentelemetry.io/otel/sdk v1.36.0
10-
go.opentelemetry.io/otel/sdk/metric v1.36.0
11-
go.opentelemetry.io/otel/trace v1.36.0
7+
go.opentelemetry.io/otel v1.37.0
8+
go.opentelemetry.io/otel/metric v1.37.0
9+
go.opentelemetry.io/otel/sdk v1.37.0
10+
go.opentelemetry.io/otel/sdk/metric v1.37.0
11+
go.opentelemetry.io/otel/trace v1.37.0
1212
)
1313

1414
require (

hooks/open-telemetry/go.sum

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,16 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
1717
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
1818
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
1919
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
20-
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
21-
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
22-
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
23-
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
24-
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
25-
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
26-
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
27-
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
28-
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
29-
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
20+
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
21+
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
22+
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
23+
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
24+
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
25+
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
26+
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
27+
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
28+
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
29+
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
3030
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
3131
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
3232
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=

hooks/open-telemetry/pkg/common_test.go

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,43 @@ import (
77

88
// test commons
99

10-
var scopeKey = "scope"
11-
var scopeValue = "7c34165e-fbef-11ed-be56-0242ac120002"
12-
var scopeDescription = DimensionDescription{
13-
Key: scopeKey,
14-
Type: String,
15-
}
10+
var (
11+
scopeKey = "scope"
12+
scopeValue = "7c34165e-fbef-11ed-be56-0242ac120002"
13+
scopeDescription = DimensionDescription{
14+
Key: scopeKey,
15+
Type: String,
16+
}
17+
)
1618

17-
var stageKey = "stage"
18-
var stageValue = 1
19-
var stageDescription = DimensionDescription{
20-
Key: stageKey,
21-
Type: Int,
22-
}
19+
var (
20+
stageKey = "stage"
21+
stageValue = 1
22+
stageDescription = DimensionDescription{
23+
Key: stageKey,
24+
Type: Int,
25+
}
26+
)
2327

24-
var scoreKey = "score"
25-
var scoreValue = 4.5
26-
var scoreDescription = DimensionDescription{
27-
Key: scoreKey,
28-
Type: Float,
29-
}
28+
var (
29+
scoreKey = "score"
30+
scoreValue = 4.5
31+
scoreDescription = DimensionDescription{
32+
Key: scoreKey,
33+
Type: Float,
34+
}
35+
)
3036

31-
var cachedKey = "cached"
32-
var cacheValue = false
33-
var cachedDescription = DimensionDescription{
34-
Key: cachedKey,
35-
Type: Bool,
36-
}
37+
var (
38+
cachedKey = "cached"
39+
cacheValue = false
40+
cachedDescription = DimensionDescription{
41+
Key: cachedKey,
42+
Type: Bool,
43+
}
44+
)
3745

38-
var evalMetadata = map[string]interface{}{
46+
var evalMetadata = map[string]any{
3947
scopeKey: scopeValue,
4048
stageKey: stageValue,
4149
scoreKey: scoreValue,

hooks/open-telemetry/pkg/metrics.go

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,57 +6,55 @@ import (
66
"github.com/open-feature/go-sdk/openfeature"
77
"go.opentelemetry.io/otel"
88
"go.opentelemetry.io/otel/attribute"
9-
api "go.opentelemetry.io/otel/metric"
10-
semconv "go.opentelemetry.io/otel/semconv/v1.18.0"
9+
"go.opentelemetry.io/otel/metric"
10+
semconv "go.opentelemetry.io/otel/semconv/v1.34.0"
1111
)
1212

1313
const (
14-
meterName = "go.openfeature.dev"
15-
1614
evaluationActive = "feature_flag.evaluation_active_count"
1715
evaluationRequests = "feature_flag.evaluation_requests_total"
1816
evaluationSuccess = "feature_flag.evaluation_success_total"
1917
evaluationErrors = "feature_flag.evaluation_error_total"
2018
)
2119

2220
type MetricsHook struct {
23-
activeCounter api.Int64UpDownCounter
24-
requestCounter api.Int64Counter
25-
successCounter api.Int64Counter
26-
errorCounter api.Int64Counter
21+
activeCounter metric.Int64UpDownCounter
22+
requestCounter metric.Int64Counter
23+
successCounter metric.Int64Counter
24+
errorCounter metric.Int64Counter
2725

2826
flagEvalMetadataDimensions []DimensionDescription
2927
attributeMapperCallback func(openfeature.FlagMetadata) []attribute.KeyValue
3028
}
3129

3230
var _ openfeature.Hook = &MetricsHook{}
3331

34-
// NewMetricsHook builds a metric hook backed by a globally set metric.MeterProvider.
35-
// Use otel.SetMeterProvider to set the global provider or use NewMetricsHookForProvider.
32+
// NewMetricsHook builds a metric hook backed by a globally set [metric.MeterProvider].
33+
// Use [otel.SetMeterProvider] to set the global provider or use [NewMetricsHookForProvider].
3634
func NewMetricsHook(opts ...MetricOptions) (*MetricsHook, error) {
3735
return NewMetricsHookForProvider(otel.GetMeterProvider(), opts...)
3836
}
3937

40-
// NewMetricsHookForProvider builds a metric hook backed by metric.MeterProvider.
41-
func NewMetricsHookForProvider(provider api.MeterProvider, opts ...MetricOptions) (*MetricsHook, error) {
42-
meter := provider.Meter(meterName)
38+
// NewMetricsHookForProvider builds a metric hook backed by [metric.MeterProvider].
39+
func NewMetricsHookForProvider(provider metric.MeterProvider, opts ...MetricOptions) (*MetricsHook, error) {
40+
meter := provider.Meter(ScopeName)
4341

44-
activeCounter, err := meter.Int64UpDownCounter(evaluationActive, api.WithDescription("active flag evaluations counter"))
42+
activeCounter, err := meter.Int64UpDownCounter(evaluationActive, metric.WithDescription("active flag evaluations counter"))
4543
if err != nil {
4644
return nil, err
4745
}
4846

49-
evalCounter, err := meter.Int64Counter(evaluationRequests, api.WithDescription("feature flag evaluation request counter"))
47+
evalCounter, err := meter.Int64Counter(evaluationRequests, metric.WithDescription("feature flag evaluation request counter"))
5048
if err != nil {
5149
return nil, err
5250
}
5351

54-
successCounter, err := meter.Int64Counter(evaluationSuccess, api.WithDescription("feature flag evaluation success counter"))
52+
successCounter, err := meter.Int64Counter(evaluationSuccess, metric.WithDescription("feature flag evaluation success counter"))
5553
if err != nil {
5654
return nil, err
5755
}
5856

59-
errorCounter, err := meter.Int64Counter(evaluationErrors, api.WithDescription("feature flag evaluation error counter"))
57+
errorCounter, err := meter.Int64Counter(evaluationErrors, metric.WithDescription("feature flag evaluation error counter"))
6058
if err != nil {
6159
return nil, err
6260
}
@@ -78,10 +76,10 @@ func NewMetricsHookForProvider(provider api.MeterProvider, opts ...MetricOptions
7876
func (h *MetricsHook) Before(ctx context.Context, hCtx openfeature.HookContext,
7977
hint openfeature.HookHints,
8078
) (*openfeature.EvaluationContext, error) {
81-
h.activeCounter.Add(ctx, +1, api.WithAttributes(semconv.FeatureFlagKey(hCtx.FlagKey())))
79+
h.activeCounter.Add(ctx, 1, metric.WithAttributes(semconv.FeatureFlagKey(hCtx.FlagKey())))
8280

8381
h.requestCounter.Add(ctx, 1,
84-
api.WithAttributes(
82+
metric.WithAttributes(
8583
semconv.FeatureFlagKey(hCtx.FlagKey()),
8684
semconv.FeatureFlagProviderName(hCtx.ProviderMetadata().Name)))
8785

@@ -97,7 +95,7 @@ func (h *MetricsHook) After(ctx context.Context, hCtx openfeature.HookContext,
9795
}
9896

9997
if details.Variant != "" {
100-
attribs = append(attribs, semconv.FeatureFlagVariant(details.Variant))
98+
attribs = append(attribs, semconv.FeatureFlagResultVariant(details.Variant))
10199
}
102100

103101
if details.Reason != "" {
@@ -110,21 +108,23 @@ func (h *MetricsHook) After(ctx context.Context, hCtx openfeature.HookContext,
110108
attribs = append(attribs, h.attributeMapperCallback(details.FlagMetadata)...)
111109
}
112110

113-
h.successCounter.Add(ctx, 1, api.WithAttributes(attribs...))
111+
h.successCounter.Add(ctx, 1, metric.WithAttributes(attribs...))
114112

115113
return nil
116114
}
117115

118116
func (h *MetricsHook) Error(ctx context.Context, hCtx openfeature.HookContext, err error, hint openfeature.HookHints) {
119117
h.errorCounter.Add(ctx, 1,
120-
api.WithAttributes(
118+
metric.WithAttributes(
121119
semconv.FeatureFlagKey(hCtx.FlagKey()),
122120
semconv.FeatureFlagProviderName(hCtx.ProviderMetadata().Name),
123-
attribute.String(semconv.ExceptionEventName, err.Error())))
121+
semconv.ErrorMessage(err.Error()),
122+
),
123+
)
124124
}
125125

126126
func (h *MetricsHook) Finally(ctx context.Context, hCtx openfeature.HookContext, flagEvaluationDetails openfeature.InterfaceEvaluationDetails, hint openfeature.HookHints) {
127-
h.activeCounter.Add(ctx, -1, api.WithAttributes(semconv.FeatureFlagKey(hCtx.FlagKey())))
127+
h.activeCounter.Add(ctx, -1, metric.WithAttributes(semconv.FeatureFlagKey(hCtx.FlagKey())))
128128
}
129129

130130
// Extra options for metrics hook
@@ -150,23 +150,23 @@ type DimensionDescription struct {
150150
}
151151

152152
// WithFlagMetadataDimensions allows configuring extra dimensions for feature_flag.evaluation_success_total metric.
153-
// If provided, dimensions will be extracted from openfeature.FlagMetadata & added to the metric with the same key
153+
// If provided, dimensions will be extracted from [openfeature.FlagMetadata] & added to the metric with the same key.
154154
func WithFlagMetadataDimensions(descriptions ...DimensionDescription) MetricOptions {
155155
return func(metricsHook *MetricsHook) {
156156
metricsHook.flagEvalMetadataDimensions = descriptions
157157
}
158158
}
159159

160-
// WithMetricsAttributeSetter allows to set a extractionCallback which accept openfeature.FlagMetadata and returns
160+
// WithMetricsAttributeSetter allows to set a extractionCallback which accepts a [openfeature.FlagMetadata] and returns
161161
// []attribute.KeyValue derived from those metadata.
162162
func WithMetricsAttributeSetter(callback func(openfeature.FlagMetadata) []attribute.KeyValue) MetricOptions {
163163
return func(metricsHook *MetricsHook) {
164164
metricsHook.attributeMapperCallback = callback
165165
}
166166
}
167167

168-
// descriptionsToAttributes is a helper to extract dimensions from openfeature.FlagMetadata. Missing metadata
169-
// dimensions are ignore.
168+
// descriptionsToAttributes is a helper to extract dimensions from [openfeature.FlagMetadata]. Missing metadata
169+
// dimensions are ignored.
170170
func descriptionsToAttributes(metadata openfeature.FlagMetadata, descriptions []DimensionDescription) []attribute.KeyValue {
171171
attribs := []attribute.KeyValue{}
172172
for _, dimension := range descriptions {

hooks/open-telemetry/pkg/metrics_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func TestMetricsHook_BeforeStage(t *testing.T) {
2020

2121
ctx := context.Background()
2222

23-
hookHints := openfeature.NewHookHints(map[string]interface{}{})
23+
hookHints := openfeature.NewHookHints(map[string]any{})
2424

2525
metricsHook, err := NewMetricsHookForProvider(metric.NewMeterProvider(metric.WithReader(manualReader)))
2626
if err != nil {
@@ -71,7 +71,7 @@ func TestMetricsHook_AfterStage(t *testing.T) {
7171
ResolutionDetail: openfeature.ResolutionDetail{},
7272
},
7373
}
74-
hookHints := openfeature.NewHookHints(map[string]interface{}{})
74+
hookHints := openfeature.NewHookHints(map[string]any{})
7575

7676
metricsHook, err := NewMetricsHookForProvider(metric.NewMeterProvider(metric.WithReader(manualReader)))
7777
if err != nil {
@@ -119,7 +119,7 @@ func TestMetricsHook_ErrorStage(t *testing.T) {
119119
ctx := context.Background()
120120

121121
evalError := errors.New("some eval error")
122-
hookHints := openfeature.NewHookHints(map[string]interface{}{})
122+
hookHints := openfeature.NewHookHints(map[string]any{})
123123

124124
metricsHook, err := NewMetricsHookForProvider(metric.NewMeterProvider(metric.WithReader(manualReader)))
125125
if err != nil {
@@ -181,7 +181,7 @@ func TestMetricsHook_FinallyStage(t *testing.T) {
181181
}
182182

183183
hookContext := hookContext()
184-
hookHints := openfeature.NewHookHints(map[string]interface{}{})
184+
hookHints := openfeature.NewHookHints(map[string]any{})
185185

186186
metricsHook, err := NewMetricsHookForProvider(metric.NewMeterProvider(metric.WithReader(manualReader)))
187187
if err != nil {
@@ -307,7 +307,7 @@ func TestMetricHook_MetadataExtractionOptions(t *testing.T) {
307307
},
308308
},
309309
}
310-
hookHints := openfeature.NewHookHints(map[string]interface{}{})
310+
hookHints := openfeature.NewHookHints(map[string]any{})
311311

312312
t.Run("from dimensionDescriptions", func(t *testing.T) {
313313
// when

hooks/open-telemetry/pkg/otel.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Package otel provides OpenTelemetry instrumentation for the OpenFeature Go SDK.
2+
package otel
3+
4+
// ScopeName is the instrumentation scope name.
5+
const ScopeName = "github.com/open-feature/go-sdk-contrib/hooks/open-telemetry"

hooks/open-telemetry/pkg/traces.go

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,18 @@ package otel
33
import (
44
"context"
55
"fmt"
6+
67
"github.com/open-feature/go-sdk/openfeature"
78
"go.opentelemetry.io/otel/attribute"
89
"go.opentelemetry.io/otel/codes"
9-
semconv "go.opentelemetry.io/otel/semconv/v1.18.0"
10+
semconv "go.opentelemetry.io/otel/semconv/v1.34.0"
1011
"go.opentelemetry.io/otel/trace"
1112
)
1213

13-
const (
14-
EventName = "feature_flag"
15-
EventPropertyFlagKey = "feature_flag.key"
16-
EventPropertyProviderName = "feature_flag.provider_name"
17-
EventPropertyVariant = "feature_flag.variant"
18-
)
14+
// EventName is the name of the span event.
15+
const EventName = "feature_flag.evaluation"
1916

20-
// traceHook is the hook implementation for OTel traces
17+
// traceHook is the hook implementation for OTel traces.
2118
type traceHook struct {
2219
setErrorStatus bool
2320
attributeMapperCallback func(openfeature.FlagMetadata) []attribute.KeyValue
@@ -27,7 +24,7 @@ type traceHook struct {
2724

2825
var _ openfeature.Hook = &traceHook{}
2926

30-
// NewTracesHook return a reference to a new instance of the OpenTelemetry Hook
27+
// NewTracesHook return a reference to a new instance of the OpenTelemetry Hook.
3128
func NewTracesHook(opts ...Options) *traceHook {
3229
h := &traceHook{}
3330

@@ -38,14 +35,14 @@ func NewTracesHook(opts ...Options) *traceHook {
3835
return h
3936
}
4037

41-
// After sets the feature_flag event and associated attributes on the span stored in the context
38+
// After sets the feature_flag event and associated attributes on the span stored in the context.
4239
func (h *traceHook) After(ctx context.Context, hookContext openfeature.HookContext, flagEvaluationDetails openfeature.InterfaceEvaluationDetails, hookHints openfeature.HookHints) error {
4340
attribs := []attribute.KeyValue{
4441
semconv.FeatureFlagKey(hookContext.FlagKey()),
4542
semconv.FeatureFlagProviderName(hookContext.ProviderMetadata().Name),
4643
}
4744
if flagEvaluationDetails.Variant != "" {
48-
attribs = append(attribs, semconv.FeatureFlagVariant(flagEvaluationDetails.Variant))
45+
attribs = append(attribs, semconv.FeatureFlagResultVariant(flagEvaluationDetails.Variant))
4946
}
5047

5148
if h.attributeMapperCallback != nil {
@@ -57,7 +54,7 @@ func (h *traceHook) After(ctx context.Context, hookContext openfeature.HookConte
5754
return nil
5855
}
5956

60-
// Error records the given error against the span and sets the span to an error status
57+
// Error records the given error against the span and sets the span to an error status.
6158
func (h *traceHook) Error(ctx context.Context, hookContext openfeature.HookContext, err error, hookHints openfeature.HookHints) {
6259
span := trace.SpanFromContext(ctx)
6360

@@ -76,16 +73,16 @@ func (h *traceHook) Error(ctx context.Context, hookContext openfeature.HookConte
7673

7774
type Options func(*traceHook)
7875

79-
// WithErrorStatusEnabled enable setting span status to codes.Error in case of an error. Default behavior is disabled
76+
// WithErrorStatusEnabled enable setting span status to codes.Error in case of an error. Default behavior is disabled.
8077
func WithErrorStatusEnabled() Options {
8178
return func(h *traceHook) {
8279
h.setErrorStatus = true
8380
}
8481
}
8582

86-
// WithTracesAttributeSetter allows to set a extractionCallback which accept openfeature.FlagMetadata and returns
83+
// WithTracesAttributeSetter allows to set a extractionCallback which accept [openfeature.FlagMetadata] and returns
8784
// []attribute.KeyValue derived from those metadata.
88-
// If present, returned attributes will be added to successful evaluation traces
85+
// If present, returned attributes will be added to successful evaluation traces.
8986
func WithTracesAttributeSetter(callback func(openfeature.FlagMetadata) []attribute.KeyValue) Options {
9087
return func(tracesHook *traceHook) {
9188
tracesHook.attributeMapperCallback = callback

0 commit comments

Comments
 (0)