From a9cee84fceb8b443aeb45996e1c8cf5115f7a823 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Thu, 10 Aug 2023 09:32:46 -0700 Subject: [PATCH 1/9] Add allow/deny attr filters --- attribute/filter.go | 60 +++++++++++++++++++++++++++++++++++++++++++++ attribute/set.go | 7 ------ 2 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 attribute/filter.go diff --git a/attribute/filter.go b/attribute/filter.go new file mode 100644 index 00000000000..4c569b03e1a --- /dev/null +++ b/attribute/filter.go @@ -0,0 +1,60 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package attribute // import "go.opentelemetry.io/otel/attribute" + +// Filter supports removing certain attributes from attribute sets. When +// the filter returns true, the attribute will be kept in the filtered +// attribute set. When the filter returns false, the attribute is excluded +// from the filtered attribute set, and the attribute instead appears in +// the removed list of excluded attributes. +type Filter func(KeyValue) bool + +// NewAllowAttributeKeysFilter returns a Filter that only allows attributes +// with one of the provided keys. +// +// If keys is empty a deny-all filter is returned. +func NewAllowAttributeKeysFilter(keys ...Key) Filter { + if len(keys) <= 0 { + return func(kv KeyValue) bool { return false } + } + + allowed := make(map[Key]struct{}) + for _, k := range keys { + allowed[k] = struct{}{} + } + return func(kv KeyValue) bool { + _, ok := allowed[kv.Key] + return ok + } +} + +// NewDenyAttributeKeysFilter returns a Filter that only allows attributes +// that do not have one of the provided keys. +// +// If keys is empty an allow-all filter is returned. +func NewDenyAttributeKeysFilter(keys ...Key) Filter { + if len(keys) <= 0 { + return func(kv KeyValue) bool { return true } + } + + forbid := make(map[Key]struct{}) + for _, k := range keys { + forbid[k] = struct{}{} + } + return func(kv KeyValue) bool { + _, ok := forbid[kv.Key] + return !ok + } +} diff --git a/attribute/set.go b/attribute/set.go index b976367e46d..9f9303d4f15 100644 --- a/attribute/set.go +++ b/attribute/set.go @@ -39,13 +39,6 @@ type ( iface interface{} } - // Filter supports removing certain attributes from attribute sets. When - // the filter returns true, the attribute will be kept in the filtered - // attribute set. When the filter returns false, the attribute is excluded - // from the filtered attribute set, and the attribute instead appears in - // the removed list of excluded attributes. - Filter func(KeyValue) bool - // Sortable implements sort.Interface, used for sorting KeyValue. This is // an exported type to support a memory optimization. A pointer to one of // these is needed for the call to sort.Stable(), which the caller may From 36cbc9f056609398340f871a195994c68fea3076 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Mon, 14 Aug 2023 14:08:35 -0700 Subject: [PATCH 2/9] Revert "Replace `Stream.AttributeFilter` with `AllowAttributeKeys` (#4288)" This reverts commit 1633c74aea5f5b9f05083d255d3c37e2b1412e79. --- CHANGELOG.md | 3 --- sdk/metric/benchmark_test.go | 4 +++- sdk/metric/instrument.go | 27 ++------------------------- sdk/metric/meter_test.go | 11 ++++++++--- sdk/metric/pipeline.go | 4 ++-- sdk/metric/view.go | 10 +++++----- sdk/metric/view_test.go | 29 +++++++++++++++++------------ 7 files changed, 37 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11549cd77a4..49b01ff4080 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,9 +37,6 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Count the Collect time in the PeriodicReader timeout. (#4221) - `New` in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc` returns `*Exporter` instead of `"go.opentelemetry.io/otel/sdk/metric".Exporter`. (#4272) - `New` in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp` returns `*Exporter` instead of `"go.opentelemetry.io/otel/sdk/metric".Exporter`. (#4272) -- ⚠️ Metrics SDK Breaking ⚠️ : the `AttributeFilter` fields of the `Stream` from `go.opentelemetry.io/otel/sdk/metric` is replaced by the `AttributeKeys` field. - The `AttributeKeys` fields allows users to specify an allow-list of attributes allowed to be recorded for a view. - This change is made to ensure compatibility with the OpenTelemetry specification. (#4288) - If an attribute set is omitted from an async callback, the previous value will no longer be exported. (#4290) - If an attribute set is Observed multiple times in an async callback, the values will be summed instead of the last observation winning. (#4289) - Allow the explicit bucket histogram aggregation to be used for the up-down counter, observable counter, observable up-down counter, and observable gauge in the `go.opentelemetry.io/otel/sdk/metric` package. (#4332) diff --git a/sdk/metric/benchmark_test.go b/sdk/metric/benchmark_test.go index dd75de3cd63..270f2e0a7e4 100644 --- a/sdk/metric/benchmark_test.go +++ b/sdk/metric/benchmark_test.go @@ -42,7 +42,9 @@ var viewBenchmarks = []struct { "AttrFilterView", []View{NewView( Instrument{Name: "*"}, - Stream{AllowAttributeKeys: []attribute.Key{"K"}}, + Stream{AttributeFilter: func(kv attribute.KeyValue) bool { + return kv.Key == attribute.Key("K") + }}, )}, }, } diff --git a/sdk/metric/instrument.go b/sdk/metric/instrument.go index c09c89361c6..04b4cf51185 100644 --- a/sdk/metric/instrument.go +++ b/sdk/metric/instrument.go @@ -146,31 +146,8 @@ type Stream struct { Unit string // Aggregation the stream uses for an instrument. Aggregation Aggregation - // AllowAttributeKeys are an allow-list of attribute keys that will be - // preserved for the stream. Any attribute recorded for the stream with a - // key not in this slice will be dropped. - // - // If this slice is empty, all attributes will be kept. - AllowAttributeKeys []attribute.Key -} - -// attributeFilter returns an attribute.Filter that only allows attributes -// with keys in s.AttributeKeys. -// -// If s.AttributeKeys is empty an accept-all filter is returned. -func (s Stream) attributeFilter() attribute.Filter { - if len(s.AllowAttributeKeys) <= 0 { - return func(kv attribute.KeyValue) bool { return true } - } - - allowed := make(map[attribute.Key]struct{}) - for _, k := range s.AllowAttributeKeys { - allowed[k] = struct{}{} - } - return func(kv attribute.KeyValue) bool { - _, ok := allowed[kv.Key] - return ok - } + // AttributeFilter applied to all attributes recorded for an instrument. + AttributeFilter attribute.Filter } // instID are the identifying properties of a instrument. diff --git a/sdk/metric/meter_test.go b/sdk/metric/meter_test.go index edb1a400b2d..7001676ceff 100644 --- a/sdk/metric/meter_test.go +++ b/sdk/metric/meter_test.go @@ -1518,7 +1518,9 @@ func testAttributeFilter(temporality metricdata.Temporality) func(*testing.T) { WithReader(rdr), WithView(NewView( Instrument{Name: "*"}, - Stream{AllowAttributeKeys: []attribute.Key{"foo"}}, + Stream{AttributeFilter: func(kv attribute.KeyValue) bool { + return kv.Key == attribute.Key("foo") + }}, )), ).Meter("TestAttributeFilter") require.NoError(t, tt.register(t, mtr)) @@ -1565,8 +1567,11 @@ func TestObservableExample(t *testing.T) { selector := func(InstrumentKind) metricdata.Temporality { return temp } reader := NewManualReader(WithTemporalitySelector(selector)) - noFiltered := NewView(Instrument{Name: instName}, Stream{Name: instName}) - filtered := NewView(Instrument{Name: instName}, Stream{Name: filteredStream, AllowAttributeKeys: []attribute.Key{"pid"}}) + noopFilter := func(kv attribute.KeyValue) bool { return true } + noFiltered := NewView(Instrument{Name: instName}, Stream{Name: instName, AttributeFilter: noopFilter}) + + filter := func(kv attribute.KeyValue) bool { return kv.Key != "tid" } + filtered := NewView(Instrument{Name: instName}, Stream{Name: filteredStream, AttributeFilter: filter}) mp := NewMeterProvider(WithReader(reader), WithView(noFiltered, filtered)) meter := mp.Meter(scopeName) diff --git a/sdk/metric/pipeline.go b/sdk/metric/pipeline.go index d76231cff7f..19a0df5be7c 100644 --- a/sdk/metric/pipeline.go +++ b/sdk/metric/pipeline.go @@ -351,8 +351,8 @@ func (i *inserter[N]) cachedAggregator(scope instrumentation.Scope, kind Instrum b := aggregate.Builder[N]{ Temporality: i.pipeline.reader.temporality(kind), } - if len(stream.AllowAttributeKeys) > 0 { - b.Filter = stream.attributeFilter() + if stream.AttributeFilter != nil { + b.Filter = stream.AttributeFilter } in, out, err := i.aggregateFunc(b, stream.Aggregation, kind) if err != nil { diff --git a/sdk/metric/view.go b/sdk/metric/view.go index 2d0fe18d7e9..e4f350e1912 100644 --- a/sdk/metric/view.go +++ b/sdk/metric/view.go @@ -107,11 +107,11 @@ func NewView(criteria Instrument, mask Stream) View { return func(i Instrument) (Stream, bool) { if matchFunc(i) { return Stream{ - Name: nonZero(mask.Name, i.Name), - Description: nonZero(mask.Description, i.Description), - Unit: nonZero(mask.Unit, i.Unit), - Aggregation: agg, - AllowAttributeKeys: mask.AllowAttributeKeys, + Name: nonZero(mask.Name, i.Name), + Description: nonZero(mask.Description, i.Description), + Unit: nonZero(mask.Unit, i.Unit), + Aggregation: agg, + AttributeFilter: mask.AttributeFilter, }, true } return Stream{}, false diff --git a/sdk/metric/view_test.go b/sdk/metric/view_test.go index 07f0c906cb8..d9e3f9b6c81 100644 --- a/sdk/metric/view_test.go +++ b/sdk/metric/view_test.go @@ -404,18 +404,6 @@ func TestNewViewReplace(t *testing.T) { } }, }, - { - name: "AttributeKeys", - mask: Stream{AllowAttributeKeys: []attribute.Key{"test"}}, - want: func(i Instrument) Stream { - return Stream{ - Name: i.Name, - Description: i.Description, - Unit: i.Unit, - AllowAttributeKeys: []attribute.Key{"test"}, - } - }, - }, { name: "Complete", mask: Stream{ @@ -442,6 +430,23 @@ func TestNewViewReplace(t *testing.T) { assert.Equal(t, test.want(completeIP), got) }) } + + // Go does not allow for the comparison of function values, even their + // addresses. Therefore, the AttributeFilter field needs an alternative + // testing strategy. + t.Run("AttributeFilter", func(t *testing.T) { + allowed := attribute.String("key", "val") + filter := func(kv attribute.KeyValue) bool { + return kv == allowed + } + mask := Stream{AttributeFilter: filter} + got, match := NewView(completeIP, mask)(completeIP) + require.True(t, match, "view did not match exact criteria") + require.NotNil(t, got.AttributeFilter, "AttributeFilter not set") + assert.True(t, got.AttributeFilter(allowed), "wrong AttributeFilter") + other := attribute.String("key", "other val") + assert.False(t, got.AttributeFilter(other), "wrong AttributeFilter") + }) } type badAgg struct { From 551b028816c1117d048ccf218568f33dbbc59f83 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Mon, 14 Aug 2023 14:21:26 -0700 Subject: [PATCH 3/9] Rename new attr filter funcs Do not include the term "Attribute" in a creation function of the "attribute" pkg. --- attribute/filter.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/attribute/filter.go b/attribute/filter.go index 4c569b03e1a..638c213d59a 100644 --- a/attribute/filter.go +++ b/attribute/filter.go @@ -21,11 +21,11 @@ package attribute // import "go.opentelemetry.io/otel/attribute" // the removed list of excluded attributes. type Filter func(KeyValue) bool -// NewAllowAttributeKeysFilter returns a Filter that only allows attributes -// with one of the provided keys. +// NewAllowKeysFilter returns a Filter that only allows attributes with one of +// the provided keys. // // If keys is empty a deny-all filter is returned. -func NewAllowAttributeKeysFilter(keys ...Key) Filter { +func NewAllowKeysFilter(keys ...Key) Filter { if len(keys) <= 0 { return func(kv KeyValue) bool { return false } } @@ -40,11 +40,11 @@ func NewAllowAttributeKeysFilter(keys ...Key) Filter { } } -// NewDenyAttributeKeysFilter returns a Filter that only allows attributes +// NewDenyKeysFilter returns a Filter that only allows attributes // that do not have one of the provided keys. // // If keys is empty an allow-all filter is returned. -func NewDenyAttributeKeysFilter(keys ...Key) Filter { +func NewDenyKeysFilter(keys ...Key) Filter { if len(keys) <= 0 { return func(kv KeyValue) bool { return true } } From a1f4d433a879bb3df782f0ba205487b7e0d0e749 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Mon, 14 Aug 2023 14:22:07 -0700 Subject: [PATCH 4/9] Update the AttributeFilter field documentation --- sdk/metric/instrument.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sdk/metric/instrument.go b/sdk/metric/instrument.go index 04b4cf51185..bf6e753a2f1 100644 --- a/sdk/metric/instrument.go +++ b/sdk/metric/instrument.go @@ -146,7 +146,13 @@ type Stream struct { Unit string // Aggregation the stream uses for an instrument. Aggregation Aggregation - // AttributeFilter applied to all attributes recorded for an instrument. + // AttributeFilter is an [attribute.Filter] applied to the attributes + // recorded for an instrument's measurement. If the filter returns false + // the attribute will not be recorded, otherwise, if it returns true, it + // will record the attribute. + // + // Use [attribute.NewAllowKeysFilter] to provide an allow-list of + // attribute keys here. AttributeFilter attribute.Filter } From 824f3ff15e3599964f35a2400abcd6b7324a593d Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Mon, 14 Aug 2023 14:32:36 -0700 Subject: [PATCH 5/9] Add tests for filter creation funcs --- attribute/filter_test.go | 87 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 attribute/filter_test.go diff --git a/attribute/filter_test.go b/attribute/filter_test.go new file mode 100644 index 00000000000..c668e260b83 --- /dev/null +++ b/attribute/filter_test.go @@ -0,0 +1,87 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package attribute + +import "testing" + +func TestNewAllowKeysFilter(t *testing.T) { + keys := []string{"zero", "one", "two"} + attrs := []KeyValue{Int(keys[0], 0), Int(keys[1], 1), Int(keys[2], 2)} + + t.Run("Empty", func(t *testing.T) { + empty := NewAllowKeysFilter() + for _, kv := range attrs { + if empty(kv) { + t.Errorf("empty NewAllowKeysFilter filter accepted %v", kv) + } + } + }) + + t.Run("Partial", func(t *testing.T) { + partial := NewAllowKeysFilter(Key(keys[0]), Key(keys[1])) + for _, kv := range attrs[:2] { + if !partial(kv) { + t.Errorf("partial NewAllowKeysFilter filter denied %v", kv) + } + } + if partial(attrs[2]) { + t.Errorf("partial NewAllowKeysFilter filter accepted %v", attrs[2]) + } + }) + + t.Run("Full", func(t *testing.T) { + full := NewAllowKeysFilter(Key(keys[0]), Key(keys[1]), Key(keys[2])) + for _, kv := range attrs { + if !full(kv) { + t.Errorf("full NewAllowKeysFilter filter denied %v", kv) + } + } + }) +} + +func TestNewDenyKeysFilter(t *testing.T) { + keys := []string{"zero", "one", "two"} + attrs := []KeyValue{Int(keys[0], 0), Int(keys[1], 1), Int(keys[2], 2)} + + t.Run("Empty", func(t *testing.T) { + empty := NewDenyKeysFilter() + for _, kv := range attrs { + if !empty(kv) { + t.Errorf("empty NewDenyKeysFilter filter denied %v", kv) + } + } + }) + + t.Run("Partial", func(t *testing.T) { + partial := NewDenyKeysFilter(Key(keys[0]), Key(keys[1])) + for _, kv := range attrs[:2] { + if partial(kv) { + t.Errorf("partial NewDenyKeysFilter filter accepted %v", kv) + } + } + if !partial(attrs[2]) { + t.Errorf("partial NewDenyKeysFilter filter denied %v", attrs[2]) + } + }) + + t.Run("Full", func(t *testing.T) { + full := NewDenyKeysFilter(Key(keys[0]), Key(keys[1]), Key(keys[2])) + for _, kv := range attrs { + if full(kv) { + t.Errorf("full NewDenyKeysFilter filter accepted %v", kv) + } + } + }) +} From e03e18ab0ab26b8846468f26d8ed102193a556ed Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Mon, 14 Aug 2023 14:43:43 -0700 Subject: [PATCH 6/9] Add change to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49b01ff4080..76fa560c46d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Expand the set of units supported by the prometheus exporter, and don't add unit suffixes if they are already present in `go.opentelemetry.op/otel/exporters/prometheus` (#4374) - Move the `Aggregation` interface and its implementations from `go.opentelemetry.io/otel/sdk/metric/aggregation` to `go.opentelemetry.io/otel/sdk/metric`. (#4435) - The exporters in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc` and `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp` support the `OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION` environment variable. (#4437) +- Add the `NewAllowKeysFilter` and `NewDenyKeysFilter` functions to `go.opentelemetry.io/otel/attribute` to allow convenient creation of allow-keys and deny-keys filters. (#4444) ### Changed From 9df6aaeff682af22f05a58ecd81de361d0d64f4f Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 15 Aug 2023 08:24:35 -0700 Subject: [PATCH 7/9] Apply feedback --- sdk/metric/benchmark_test.go | 4 +--- sdk/metric/meter_test.go | 4 +--- sdk/metric/pipeline.go | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/sdk/metric/benchmark_test.go b/sdk/metric/benchmark_test.go index 270f2e0a7e4..90f88088630 100644 --- a/sdk/metric/benchmark_test.go +++ b/sdk/metric/benchmark_test.go @@ -42,9 +42,7 @@ var viewBenchmarks = []struct { "AttrFilterView", []View{NewView( Instrument{Name: "*"}, - Stream{AttributeFilter: func(kv attribute.KeyValue) bool { - return kv.Key == attribute.Key("K") - }}, + Stream{AttributeFilter: attribute.NewAllowKeysFilter("K")}, )}, }, } diff --git a/sdk/metric/meter_test.go b/sdk/metric/meter_test.go index 7001676ceff..dfd85c21532 100644 --- a/sdk/metric/meter_test.go +++ b/sdk/metric/meter_test.go @@ -1518,9 +1518,7 @@ func testAttributeFilter(temporality metricdata.Temporality) func(*testing.T) { WithReader(rdr), WithView(NewView( Instrument{Name: "*"}, - Stream{AttributeFilter: func(kv attribute.KeyValue) bool { - return kv.Key == attribute.Key("foo") - }}, + Stream{AttributeFilter: attribute.NewAllowKeysFilter("foo")}, )), ).Meter("TestAttributeFilter") require.NoError(t, tt.register(t, mtr)) diff --git a/sdk/metric/pipeline.go b/sdk/metric/pipeline.go index 19a0df5be7c..c1597a75597 100644 --- a/sdk/metric/pipeline.go +++ b/sdk/metric/pipeline.go @@ -351,9 +351,7 @@ func (i *inserter[N]) cachedAggregator(scope instrumentation.Scope, kind Instrum b := aggregate.Builder[N]{ Temporality: i.pipeline.reader.temporality(kind), } - if stream.AttributeFilter != nil { - b.Filter = stream.AttributeFilter - } + b.Filter = stream.AttributeFilter in, out, err := i.aggregateFunc(b, stream.Aggregation, kind) if err != nil { return aggVal[N]{0, nil, err} From f335d1573d77f83af70b400d66781fd0450ee7e7 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 15 Aug 2023 08:29:24 -0700 Subject: [PATCH 8/9] Use NewDenyKeysFilter for allow-all and deny-list filters --- sdk/metric/meter_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/metric/meter_test.go b/sdk/metric/meter_test.go index dfd85c21532..7c1d21f96cd 100644 --- a/sdk/metric/meter_test.go +++ b/sdk/metric/meter_test.go @@ -1565,10 +1565,10 @@ func TestObservableExample(t *testing.T) { selector := func(InstrumentKind) metricdata.Temporality { return temp } reader := NewManualReader(WithTemporalitySelector(selector)) - noopFilter := func(kv attribute.KeyValue) bool { return true } - noFiltered := NewView(Instrument{Name: instName}, Stream{Name: instName, AttributeFilter: noopFilter}) + allowAll := attribute.NewDenyKeysFilter() + noFiltered := NewView(Instrument{Name: instName}, Stream{Name: instName, AttributeFilter: allowAll}) - filter := func(kv attribute.KeyValue) bool { return kv.Key != "tid" } + filter := attribute.NewDenyKeysFilter("tid") filtered := NewView(Instrument{Name: instName}, Stream{Name: filteredStream, AttributeFilter: filter}) mp := NewMeterProvider(WithReader(reader), WithView(noFiltered, filtered)) From baf171dea0dc048a806e7e619b3ade451263f910 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 15 Aug 2023 09:02:37 -0700 Subject: [PATCH 9/9] Remove links from field docs These links do not render. --- sdk/metric/instrument.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/metric/instrument.go b/sdk/metric/instrument.go index bf6e753a2f1..f7224d4b581 100644 --- a/sdk/metric/instrument.go +++ b/sdk/metric/instrument.go @@ -146,13 +146,13 @@ type Stream struct { Unit string // Aggregation the stream uses for an instrument. Aggregation Aggregation - // AttributeFilter is an [attribute.Filter] applied to the attributes + // AttributeFilter is an attribute Filter applied to the attributes // recorded for an instrument's measurement. If the filter returns false // the attribute will not be recorded, otherwise, if it returns true, it // will record the attribute. // - // Use [attribute.NewAllowKeysFilter] to provide an allow-list of - // attribute keys here. + // Use NewAllowKeysFilter from "go.opentelemetry.io/otel/attribute" to + // provide an allow-list of attribute keys here. AttributeFilter attribute.Filter }