Skip to content

Commit e8ff568

Browse files
committed
sdk/metric: Retain advisory attributes when View has no AttributeFilter
Fixes #8857. According to the OpenTelemetry Metrics SDK specification, if attribute_keys is not provided on a View, the SDK should use the 'Attributes' advisory parameter configured on the instrument. Previously, when a View matched an instrument without setting an AttributeFilter, the stream's AttributeFilter remained nil, causing the pipeline to drop the instrument's advisory default attributes. Centralize AttributeFilter defaulting inside cachedAggregator alongside aggregation and exemplar reservoir provider defaulting.
1 parent 3b8ef6f commit e8ff568

4 files changed

Lines changed: 44 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ The next release will require at least [Go 1.26].
2525

2626
### Fixed
2727

28+
- Retain instrument advisory attributes (`metric/x.WithDefaultAttributes`) when a matching View does not specify an attribute filter in `go.opentelemetry.io/otel/sdk/metric`.
2829
- Accept quoted finite double values in OTLP/HTTP JSON requests in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`. (#8831)
2930
- Export dropped attribute counts in OTLP log records from `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc` and `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#8829)
3031
- Name span events created from OpenTracing logs after the `event` log field, falling back to `log`, instead of always using an empty name in `go.opentelemetry.io/otel/bridge/opentracing`. (#8648)

sdk/metric/meter_test.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3000,9 +3000,37 @@ func TestMeterDefaultAttributes(t *testing.T) {
30003000
metricdatatest.AssertEqual(t, want, got, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
30013001
})
30023002

3003-
t.Run(tt.name+"_ViewOverride", func(t *testing.T) {
3003+
t.Run(tt.name+"_ViewWithoutFilter", func(t *testing.T) {
30043004
rdr := NewManualReader()
3005-
view := NewView(Instrument{Name: "*"}, Stream{}) // Match all instruments, override filter
3005+
view := NewView(
3006+
Instrument{Name: "*"},
3007+
Stream{Description: "updated"},
3008+
) // Match all instruments, no attribute filter
3009+
m := NewMeterProvider(WithReader(rdr), WithView(view)).Meter("test")
3010+
tt.record(t, m)
3011+
3012+
rm := metricdata.ResourceMetrics{}
3013+
err := rdr.Collect(t.Context(), &rm)
3014+
require.NoError(t, err)
3015+
3016+
require.Len(t, rm.ScopeMetrics, 1)
3017+
sm := rm.ScopeMetrics[0]
3018+
require.Len(t, sm.Metrics, 1)
3019+
got := sm.Metrics[0]
3020+
3021+
want := metricdata.Metrics{
3022+
Name: tt.instName,
3023+
Description: "updated",
3024+
Data: tt.wantData(alice),
3025+
}
3026+
metricdatatest.AssertEqual(t, want, got, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
3027+
})
3028+
3029+
t.Run(tt.name+"_ViewWithFilterOverride", func(t *testing.T) {
3030+
rdr := NewManualReader()
3031+
view := NewView(Instrument{Name: "*"}, Stream{
3032+
AttributeFilter: attribute.NewAllowKeysFilter(k1, k2),
3033+
}) // Match all instruments, override filter
30063034
m := NewMeterProvider(WithReader(rdr), WithView(view)).Meter("test")
30073035
tt.record(t, m)
30083036

sdk/metric/pipeline.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ func (i *inserter[N]) Instrument(
255255
continue
256256
}
257257
matched = true
258-
in, id, e := i.cachedAggregator(inst.Scope, inst.Kind, stream, readerAggregation)
258+
in, id, e := i.cachedAggregator(inst.Scope, inst.Kind, stream, allowedKeys, readerAggregation)
259259
if e != nil {
260260
err = errors.Join(err, e)
261261
}
@@ -284,13 +284,7 @@ func (i *inserter[N]) Instrument(
284284
Description: inst.Description,
285285
Unit: inst.Unit,
286286
}
287-
// allowedKeys == nil indicates that the WithDefaultAttributes option was not passed,
288-
// and all keys are allowed. An empty (non-nil) slice indicates that the option was passed
289-
// with an empty set of keys, and no keys are allowed.
290-
if allowedKeys != nil {
291-
stream.AttributeFilter = attribute.NewAllowKeysFilter(allowedKeys...)
292-
}
293-
in, _, e := i.cachedAggregator(inst.Scope, inst.Kind, stream, readerAggregation)
287+
in, _, e := i.cachedAggregator(inst.Scope, inst.Kind, stream, allowedKeys, readerAggregation)
294288
if e != nil {
295289
if err == nil {
296290
err = errCreatingAggregators
@@ -364,6 +358,7 @@ func (i *inserter[N]) cachedAggregator(
364358
scope instrumentation.Scope,
365359
kind InstrumentKind,
366360
stream Stream,
361+
allowedKeys []attribute.Key,
367362
readerAggregation Aggregation,
368363
) (meas aggregate.Measure[N], aggID uint64, err error) {
369364
switch stream.Aggregation.(type) {
@@ -378,6 +373,14 @@ func (i *inserter[N]) cachedAggregator(
378373
if stream.ExemplarReservoirProviderSelector == nil {
379374
stream.ExemplarReservoirProviderSelector = DefaultExemplarReservoirProviderSelector
380375
}
376+
// If attribute_keys is not provided on the view (AttributeFilter == nil),
377+
// use the advisory Attributes configured on the instrument (allowedKeys).
378+
// allowedKeys == nil indicates that the WithDefaultAttributes option was not passed,
379+
// and all keys are allowed. An empty (non-nil) slice indicates that the option was passed
380+
// with an empty set of keys, and no keys are allowed.
381+
if stream.AttributeFilter == nil && allowedKeys != nil {
382+
stream.AttributeFilter = attribute.NewAllowKeysFilter(allowedKeys...)
383+
}
381384

382385
if err := isAggregatorCompatible(kind, stream.Aggregation); err != nil {
383386
return nil, 0, fmt.Errorf(

sdk/metric/pipeline_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ func TestInserterCachedAggregatorNameConflict(t *testing.T) {
380380
i := newInserter[int64](pipe, &vc)
381381

382382
readerAggregation := i.readerDefaultAggregation(kind)
383-
_, origID, err := i.cachedAggregator(scope, kind, stream, readerAggregation)
383+
_, origID, err := i.cachedAggregator(scope, kind, stream, nil, readerAggregation)
384384
require.NoError(t, err)
385385

386386
require.Len(t, pipe.aggregations, 1)
@@ -390,7 +390,7 @@ func TestInserterCachedAggregatorNameConflict(t *testing.T) {
390390
require.Equal(t, name, iSync[0].name)
391391

392392
stream.Name = "RequestCount"
393-
_, id, err := i.cachedAggregator(scope, kind, stream, readerAggregation)
393+
_, id, err := i.cachedAggregator(scope, kind, stream, nil, readerAggregation)
394394
require.NoError(t, err)
395395
assert.Equal(t, origID, id, "multiple aggregators for equivalent name")
396396

0 commit comments

Comments
 (0)