From 81a9bab814231c386bfee8078a66f13bd457288d Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Thu, 1 Sep 2022 14:19:03 -0700 Subject: [PATCH] Add WithScopeAttributes MeterOption to metric API package (#3132) * Add WithScopeAttributes MeterOption to metric pkg * Add MeterConfig unit tests * Add changes to changelog * Fix import linting * Update MeterProvider documentation Include information about how to use WithScopeAttributes. --- CHANGELOG.md | 1 + metric/config.go | 18 +++++++++++ metric/config_test.go | 69 +++++++++++++++++++++++++++++++++++++++++++ metric/meter.go | 51 +++++++++++++++++++++++++++----- 4 files changed, 131 insertions(+), 8 deletions(-) create mode 100644 metric/config_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 82983be0535..9e4a8df6440 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Upgrade go.opentelemetry.io/proto/otlp from v0.18.0 to v0.19.0 (#3107) - Add an `Attribute` field to the `Scope` type in `go.opentelemetry.io/otel/sdk/instrumentation`. (#3131) - Add the `WithScopeAttributes` `TracerOption` to the `go.opentelemetry.io/otel/trace` package. (#3131) +- Add the `WithScopeAttributes` `MeterOption` to the `go.opentelemetry.io/otel/metric` package. (#3132) ### Changed diff --git a/metric/config.go b/metric/config.go index 621e4c5fcb8..ddadd62f9f9 100644 --- a/metric/config.go +++ b/metric/config.go @@ -14,10 +14,13 @@ package metric // import "go.opentelemetry.io/otel/metric" +import "go.opentelemetry.io/otel/attribute" + // MeterConfig contains options for Meters. type MeterConfig struct { instrumentationVersion string schemaURL string + attributes attribute.Set } // InstrumentationVersion is the version of the library providing instrumentation. @@ -30,6 +33,11 @@ func (cfg MeterConfig) SchemaURL() string { return cfg.schemaURL } +// Attributes returns the scope attribute set of the Meter. +func (t MeterConfig) Attributes() attribute.Set { + return t.attributes +} + // MeterOption is an interface for applying Meter options. type MeterOption interface { // applyMeter is used to set a MeterOption value of a MeterConfig. @@ -67,3 +75,13 @@ func WithSchemaURL(schemaURL string) MeterOption { return config }) } + +// WithScopeAttributes sets the attributes for the scope of a Meter. The +// attributes are stored as an attribute set. Duplicate values are removed, the +// last value is used. +func WithScopeAttributes(attr ...attribute.KeyValue) MeterOption { + return meterOptionFunc(func(cfg MeterConfig) MeterConfig { + cfg.attributes = attribute.NewSet(attr...) + return cfg + }) +} diff --git a/metric/config_test.go b/metric/config_test.go new file mode 100644 index 00000000000..359e14c0603 --- /dev/null +++ b/metric/config_test.go @@ -0,0 +1,69 @@ +// 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 metric // import "go.opentelemetry.io/otel/metric" + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/otel/attribute" +) + +func TestMeterConfig(t *testing.T) { + t.Run("Empty", func(t *testing.T) { + assert.Equal(t, NewMeterConfig(), MeterConfig{}) + }) + + t.Run("InstrumentationVersion", func(t *testing.T) { + v0, v1 := "v0.1.0", "v1.0.0" + + assert.Equal(t, NewMeterConfig( + WithInstrumentationVersion(v0), + ).InstrumentationVersion(), v0) + + assert.Equal(t, NewMeterConfig( + WithInstrumentationVersion(v0), + WithInstrumentationVersion(v1), + ).InstrumentationVersion(), v1, "last option has precedence") + }) + + t.Run("SchemaURL", func(t *testing.T) { + s120 := "https://opentelemetry.io/schemas/1.2.0" + s130 := "https://opentelemetry.io/schemas/1.3.0" + + assert.Equal(t, NewMeterConfig( + WithSchemaURL(s120), + ).SchemaURL(), s120) + + assert.Equal(t, NewMeterConfig( + WithSchemaURL(s120), + WithSchemaURL(s130), + ).SchemaURL(), s130, "last option has precedence") + }) + + t.Run("Attributes", func(t *testing.T) { + one, two := attribute.Int("key", 1), attribute.Int("key", 2) + + assert.Equal(t, NewMeterConfig( + WithScopeAttributes(one, two), + ).Attributes(), attribute.NewSet(two), "last attribute is used") + + assert.Equal(t, NewMeterConfig( + WithScopeAttributes(two), + WithScopeAttributes(one), + ).Attributes(), attribute.NewSet(one), "last option has precedence") + }) +} diff --git a/metric/meter.go b/metric/meter.go index 21fc1c499fb..b548405353c 100644 --- a/metric/meter.go +++ b/metric/meter.go @@ -24,15 +24,50 @@ import ( "go.opentelemetry.io/otel/metric/instrument/syncint64" ) -// MeterProvider provides access to named Meter instances, for instrumenting -// an application or library. +// MeterProvider provides Meters that are used by instrumentation code to +// create instruments that measure code operations. +// +// A MeterProvider is the collection destination of all measurements made from +// instruments the provided Meters created, it represents a unique telemetry +// collection pipeline. How that pipeline is defined, meaning how those +// measurements are collected, processed, and where they are exported, depends +// on its implementation. Instrumentation authors do not need to define this +// implementation, rather just use the provided Meters to instrument code. +// +// Commonly, instrumentation code will accept a MeterProvider implementation at +// runtime from its users or it can simply use the globally registered one (see +// https://pkg.go.dev/go.opentelemetry.io/otel/metric/global#MeterProvider). +// +// Warning: methods may be added to this interface in minor releases. type MeterProvider interface { - // Meter creates an instance of a `Meter` interface. The instrumentationName - // must be the name of the library providing instrumentation. This name may - // be the same as the instrumented code only if that code provides built-in - // instrumentation. If the instrumentationName is empty, then a - // implementation defined default name will be used instead. - Meter(instrumentationName string, opts ...MeterOption) Meter + // Meter returns a unique Meter scoped to be used by instrumentation code + // to measure code operations. The scope and identity of that + // instrumentation code is uniquely defined by the name and options passed. + // + // The passed name needs to uniquely identify instrumentation code. + // Therefore, it is recommended that name is the Go package name of the + // library providing instrumentation (note: not the code being + // instrumented). Instrumentation libraries can have multiple versions, + // therefore, the WithInstrumentationVersion option should be used to + // distinguish these different codebases. Additionally, instrumentation + // libraries may sometimes use metric measurements to communicate different + // domains of code operations data (i.e. using different Meters to + // communicate user experience and back-end operations). If this is the + // case, the WithScopeAttributes option should be used to uniquely identify + // Meters that handle the different domains of code operations data. + // + // If the same name and options are passed multiple times, the same Meter + // will be returned (it is up to the implementation if this will be the + // same underlying instance of that Meter or not). It is not necessary to + // call this multiple times with the same name and options to get an + // up-to-date Meter. All implementations will ensure any MeterProvider + // configuration changes are propagated to all provided Meters. + // + // If name is empty, then an implementation defined default name will be + // used instead. + // + // This method is safe to call concurrently. + Meter(name string, options ...MeterOption) Meter } // Meter provides access to instrument instances for recording metrics.