1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2024-12-12 10:04:29 +02:00
opentelemetry-go/sdk/metric/view/view.go
Bogdan Drutu f535b1e65f
Remove unnecessary build restrictions for go1.18 (#3197)
Signed-off-by: Bogdan Drutu <bogdandrutu@gmail.com>

Signed-off-by: Bogdan Drutu <bogdandrutu@gmail.com>
2022-09-21 10:00:00 -07:00

233 lines
6.8 KiB
Go

// 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 view // import "go.opentelemetry.io/otel/sdk/metric/view"
import (
"fmt"
"regexp"
"strings"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/internal/global"
"go.opentelemetry.io/otel/sdk/instrumentation"
"go.opentelemetry.io/otel/sdk/metric/aggregation"
)
// View provides users with the flexibility to customize the metrics that are
// output by the SDK. A View can be used to ignore, change the name,
// description, and aggregation of, and customize which attribute(s) are to be
// reported by Instruments.
//
// An empty View will match all instruments, and do no transformations.
type View struct {
instrumentName *regexp.Regexp
hasWildcard bool
scope instrumentation.Scope
instrumentKind InstrumentKind
filter attribute.Filter
name string
description string
agg aggregation.Aggregation
}
// New returns a new configured View. If there are any duplicate Options passed,
// the last one passed will take precedence. The unique, de-duplicated,
// Options are all applied to the View. An instrument needs to match all of
// the match Options passed for the View to be applied to it. Similarly, all
// transform operation Options are applied to matched Instruments.
func New(opts ...Option) (View, error) {
v := View{}
for _, opt := range opts {
v = opt.apply(v)
}
emptyScope := instrumentation.Scope{}
if v.instrumentName == nil &&
v.scope == emptyScope &&
v.instrumentKind == undefinedInstrument {
return View{}, fmt.Errorf("must provide at least 1 match option")
}
if v.hasWildcard && v.name != "" {
return View{}, fmt.Errorf("invalid view: view name specified for multiple instruments")
}
return v, nil
}
// TransformInstrument will check if an instrument matches this view
// and will convert it if it does.
func (v View) TransformInstrument(inst Instrument) (transformed Instrument, match bool) {
if !v.match(inst) {
return Instrument{}, false
}
if v.name != "" {
inst.Name = v.name
}
if v.description != "" {
inst.Description = v.description
}
if v.agg != nil {
inst.Aggregation = v.agg
}
return inst, true
}
// AttributeFilter returns a function that returns only attributes specified by
// WithFilterAttributes. If no filter was provided nil is returned.
func (v View) AttributeFilter() func(attribute.Set) attribute.Set {
if v.filter == nil {
return nil
}
return func(input attribute.Set) attribute.Set {
out, _ := input.Filter(v.filter)
return out
}
}
func (v View) matchName(name string) bool {
return v.instrumentName == nil || v.instrumentName.MatchString(name)
}
func (v View) matchScopeName(name string) bool {
return v.scope.Name == "" || name == v.scope.Name
}
func (v View) matchScopeVersion(version string) bool {
return v.scope.Version == "" || version == v.scope.Version
}
func (v View) matchScopeSchemaURL(schemaURL string) bool {
return v.scope.SchemaURL == "" || schemaURL == v.scope.SchemaURL
}
func (v View) matchInstrumentKind(kind InstrumentKind) bool {
return v.instrumentKind == undefinedInstrument || kind == v.instrumentKind
}
func (v View) match(i Instrument) bool {
return v.matchName(i.Name) &&
v.matchScopeName(i.Scope.Name) &&
v.matchScopeSchemaURL(i.Scope.SchemaURL) &&
v.matchScopeVersion(i.Scope.Version) &&
v.matchInstrumentKind(i.Kind)
}
// Option applies a configuration option value to a View.
type Option interface {
apply(View) View
}
type optionFunc func(View) View
func (f optionFunc) apply(v View) View {
return f(v)
}
// MatchInstrumentName will match an instrument based on the its name.
// This will accept wildcards of * for zero or more characters, and ? for
// exactly one character. A name of "*" (default) will match all instruments.
func MatchInstrumentName(name string) Option {
return optionFunc(func(v View) View {
if strings.ContainsAny(name, "*?") {
v.hasWildcard = true
}
name = regexp.QuoteMeta(name)
name = "^" + name + "$"
name = strings.ReplaceAll(name, "\\?", ".")
name = strings.ReplaceAll(name, "\\*", ".*")
v.instrumentName = regexp.MustCompile(name)
return v
})
}
// MatchInstrumentKind with match an instrument based on the instrument's kind.
// The default is to match all instrument kinds.
func MatchInstrumentKind(kind InstrumentKind) Option {
return optionFunc(func(v View) View {
v.instrumentKind = kind
return v
})
}
// MatchInstrumentationScope will do an exact match on any
// instrumentation.Scope field that is non-empty (""). The default is to match all
// instrumentation scopes.
func MatchInstrumentationScope(scope instrumentation.Scope) Option {
return optionFunc(func(v View) View {
v.scope = scope
return v
})
}
// WithRename will rename the instrument the view matches. If not used or empty the
// instrument name will not be changed. Must be used with a non-wildcard
// instrument name match. The default does not change the instrument name.
func WithRename(name string) Option {
return optionFunc(func(v View) View {
v.name = name
return v
})
}
// WithSetDescription will change the description of the instruments the view
// matches to desc. If not used or empty the description will not be changed.
func WithSetDescription(desc string) Option {
return optionFunc(func(v View) View {
v.description = desc
return v
})
}
// WithFilterAttributes will select attributes that have a matching key. If not used
// or empty no filter will be applied.
func WithFilterAttributes(keys ...attribute.Key) Option {
return optionFunc(func(v View) View {
if len(keys) == 0 {
return v
}
filterKeys := map[attribute.Key]struct{}{}
for _, key := range keys {
filterKeys[key] = struct{}{}
}
v.filter = attribute.Filter(func(kv attribute.KeyValue) bool {
_, ok := filterKeys[kv.Key]
return ok
})
return v
})
}
// WithSetAggregation will use the aggregation a for matching instruments. If
// this option is not provided, the reader defined aggregation for the
// instrument will be used.
//
// If a is misconfigured, it will not be used and an error will be logged.
func WithSetAggregation(a aggregation.Aggregation) Option {
cpA := a.Copy()
if err := cpA.Err(); err != nil {
global.Error(err, "not using aggregation with view", "aggregation", a)
return optionFunc(func(v View) View { return v })
}
return optionFunc(func(v View) View {
v.agg = cpA
return v
})
}