Skip to content
Merged
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
10 changes: 7 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

### ⚠️ Notice ⚠️

This update contains a breaking change of `RegisterDBStatsMetrics` now returning `(metric.Registration, error)` (previously returned only `error`) so callers can `Unregister()` the callback and avoid memory leaks.
This release contains breaking changes:

- If you need to unregister: `reg, err := RegisterDBStatsMetrics(...)` + `defer reg.Unregister()`.
- If you do not need to unregister: `_, err := RegisterDBStatsMetrics(...)`.
- `RegisterDBStatsMetrics` now returns `(metric.Registration, error)` (previously returned only `error`) so callers can `Unregister()` the callback and avoid memory leaks.
- If you need to unregister: `reg, err := RegisterDBStatsMetrics(...)` + `defer reg.Unregister()`.
- If you do not need to unregister: `_, err := RegisterDBStatsMetrics(...)`.
- `WithAttributes` now appends attributes when specified multiple times (previously overwrote existing attributes).
- If you relied on overwriting: consolidate your attributes into a single `WithAttributes(...)` call.

### Added

Expand All @@ -29,6 +32,7 @@ This update contains a breaking change of `RegisterDBStatsMetrics` now returning
- Reduce allocations and improve performance when creating spans. (#549)
- Reduce allocations when recording metrics. (#550)
- `RegisterDBStatsMetrics` now returns a `metric.Registration` so callbacks can be unregistered. (#580)
- `WithAttributes` now accumulates attributes across multiple calls instead of overwriting. (#576)
- Upgrade OTel to `v1.39.0`. (#583)

## [0.40.0] - 2025-09-08
Expand Down
5 changes: 3 additions & 2 deletions option.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@ func WithTracerProvider(provider trace.TracerProvider) Option {
})
}

// WithAttributes specifies attributes that will be set to each span and measurement.
// WithAttributes adds the attributes to each span and measurement.
// If multiple of WithAttributes are passed, the attributes will be extended instead of being overwriting.
func WithAttributes(attributes ...attribute.KeyValue) Option {
return OptionFunc(func(cfg *config) {
cfg.Attributes = attributes
cfg.Attributes = append(cfg.Attributes, attributes...)
})
}

Expand Down
107 changes: 90 additions & 17 deletions option_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,79 +41,152 @@ func TestOptions(t *testing.T) {

testCases := []struct {
name string
option Option
options []Option
expectedConfig config
}{
{
name: "WithTracerProvider",
option: WithTracerProvider(tracerProvider),
options: []Option{WithTracerProvider(tracerProvider)},
expectedConfig: config{TracerProvider: tracerProvider},
},
{
name: "WithAttributes",
option: WithAttributes(
attribute.String("foo", "bar"),
attribute.String("foo2", "bar2"),
),
options: []Option{
WithAttributes(
attribute.String("foo", "bar"),
attribute.String("foo2", "bar2"),
),
},
expectedConfig: config{Attributes: []attribute.KeyValue{
attribute.String("foo", "bar"),
attribute.String("foo2", "bar2"),
}},
},
{
name: "WithSpanNameFormatter",
option: WithSpanNameFormatter(nil),
options: []Option{WithSpanNameFormatter(nil)},
expectedConfig: config{SpanNameFormatter: nil},
},
{
name: "WithSpanOptions",
option: WithSpanOptions(SpanOptions{Ping: true}),
options: []Option{WithSpanOptions(SpanOptions{Ping: true})},
expectedConfig: config{SpanOptions: SpanOptions{Ping: true}},
},
{
name: "WithMeterProvider",
option: WithMeterProvider(meterProvider),
options: []Option{WithMeterProvider(meterProvider)},
expectedConfig: config{MeterProvider: meterProvider},
},
{
name: "WithSQLCommenter",
option: WithSQLCommenter(true),
options: []Option{WithSQLCommenter(true)},
expectedConfig: config{SQLCommenterEnabled: true},
},
{
name: "WithTextMapPropagator",
option: WithTextMapPropagator(textMapPropagator),
name: "WithTextMapPropagator",
options: []Option{WithTextMapPropagator(textMapPropagator)},
expectedConfig: config{
TextMapPropagator: textMapPropagator,
},
},
{
name: "WithAttributesGetter",
option: WithAttributesGetter(dummyAttributesGetter),
options: []Option{WithAttributesGetter(dummyAttributesGetter)},
expectedConfig: config{AttributesGetter: dummyAttributesGetter},
},
{
name: "WithInstrumentAttributesGetter",
option: WithInstrumentAttributesGetter(dummyAttributesGetter),
options: []Option{WithInstrumentAttributesGetter(dummyAttributesGetter)},
expectedConfig: config{InstrumentAttributesGetter: dummyAttributesGetter},
},
{
name: "WithDisableSkipErrMeasurement",
option: WithDisableSkipErrMeasurement(true),
options: []Option{WithDisableSkipErrMeasurement(true)},
expectedConfig: config{DisableSkipErrMeasurement: true},
},
{
name: "WithInstrumentErrorAttributesGetter",
option: WithInstrumentErrorAttributesGetter(dummyErrorAttributesGetter),
options: []Option{WithInstrumentErrorAttributesGetter(dummyErrorAttributesGetter)},
expectedConfig: config{InstrumentErrorAttributesGetter: dummyErrorAttributesGetter},
},
{
name: "WithAttributes multiple calls should accumulate",
options: []Option{
WithAttributes(attribute.String("key1", "value1")),
WithAttributes(attribute.String("key2", "value2")),
},
expectedConfig: config{Attributes: []attribute.KeyValue{
attribute.String("key1", "value1"),
attribute.String("key2", "value2"),
}},
},
{
name: "WithAttributes multiple calls with multiple attributes each",
options: []Option{
WithAttributes(
attribute.String("key1", "value1"),
attribute.String("key2", "value2"),
),
WithAttributes(
attribute.String("key3", "value3"),
attribute.String("key4", "value4"),
),
},
expectedConfig: config{Attributes: []attribute.KeyValue{
attribute.String("key1", "value1"),
attribute.String("key2", "value2"),
attribute.String("key3", "value3"),
attribute.String("key4", "value4"),
}},
},
{
name: "WithAttributes with empty attributes",
options: []Option{WithAttributes()},
expectedConfig: config{Attributes: nil},
},
{
name: "WithAttributes with empty followed by non-empty",
options: []Option{
WithAttributes(),
WithAttributes(attribute.String("key1", "value1")),
},
expectedConfig: config{Attributes: []attribute.KeyValue{
attribute.String("key1", "value1"),
}},
},
{
name: "WithAttributes three calls to verify order",
options: []Option{
WithAttributes(attribute.String("first", "1")),
WithAttributes(attribute.String("second", "2")),
WithAttributes(attribute.String("third", "3")),
},
expectedConfig: config{Attributes: []attribute.KeyValue{
attribute.String("first", "1"),
attribute.String("second", "2"),
attribute.String("third", "3"),
}},
},
{
name: "WithAttributes duplicate keys should be preserved",
options: []Option{
WithAttributes(attribute.String("key", "value1")),
WithAttributes(attribute.String("key", "value2")),
},
expectedConfig: config{Attributes: []attribute.KeyValue{
attribute.String("key", "value1"),
attribute.String("key", "value2"),
}},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var cfg config

tc.option.Apply(&cfg)
for _, opt := range tc.options {
opt.Apply(&cfg)
}

switch {
case tc.expectedConfig.AttributesGetter != nil:
Expand Down
Loading