1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-11-25 22:41:46 +02:00

Redesign RegisterCallback API (#3584)

* Update RegisterCallback and Callback declerations

RegisterCallback accepts variadic Asynchronous instruments instead of a
slice.

Callback accepts an observation result recorder to ensure instruments
that are observed by a callback.

* Update global, noop, SDK implementations
* Fix examples
* Add changes to changelog
* Test RegisterCallback for invalid observers
* Test callbacks from foreign sources not collected
* Support registering delegating instruments
This commit is contained in:
Tyler Yahn
2023-01-19 06:18:26 -08:00
committed by GitHub
parent e8c6e45178
commit 69b18e62a7
12 changed files with 630 additions and 114 deletions

View File

@@ -16,14 +16,20 @@ package metric
import (
"context"
"fmt"
"strings"
"sync"
"testing"
"github.com/go-logr/logr"
"github.com/go-logr/logr/testr"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/metric/global"
"go.opentelemetry.io/otel/metric/instrument"
"go.opentelemetry.io/otel/sdk/instrumentation"
"go.opentelemetry.io/otel/sdk/metric/aggregation"
@@ -91,7 +97,7 @@ func TestMeterInstrumentConcurrency(t *testing.T) {
wg.Wait()
}
var emptyCallback metric.Callback = func(ctx context.Context) error { return nil }
var emptyCallback metric.Callback = func(context.Context, metric.Observer) error { return nil }
// A Meter Should be able register Callbacks Concurrently.
func TestMeterCallbackCreationConcurrency(t *testing.T) {
@@ -179,8 +185,8 @@ func TestMeterCreatesInstruments(t *testing.T) {
}
ctr, err := m.Int64ObservableCounter("aint", instrument.WithInt64Callback(cback))
assert.NoError(t, err)
_, err = m.RegisterCallback(func(ctx context.Context) error {
ctr.Observe(ctx, 3)
_, err = m.RegisterCallback(func(_ context.Context, o metric.Observer) error {
o.ObserveInt64(ctr, 3)
return nil
}, ctr)
assert.NoError(t, err)
@@ -209,8 +215,8 @@ func TestMeterCreatesInstruments(t *testing.T) {
}
ctr, err := m.Int64ObservableUpDownCounter("aint", instrument.WithInt64Callback(cback))
assert.NoError(t, err)
_, err = m.RegisterCallback(func(ctx context.Context) error {
ctr.Observe(ctx, 11)
_, err = m.RegisterCallback(func(_ context.Context, o metric.Observer) error {
o.ObserveInt64(ctr, 11)
return nil
}, ctr)
assert.NoError(t, err)
@@ -239,8 +245,8 @@ func TestMeterCreatesInstruments(t *testing.T) {
}
gauge, err := m.Int64ObservableGauge("agauge", instrument.WithInt64Callback(cback))
assert.NoError(t, err)
_, err = m.RegisterCallback(func(ctx context.Context) error {
gauge.Observe(ctx, 11)
_, err = m.RegisterCallback(func(_ context.Context, o metric.Observer) error {
o.ObserveInt64(gauge, 11)
return nil
}, gauge)
assert.NoError(t, err)
@@ -267,8 +273,8 @@ func TestMeterCreatesInstruments(t *testing.T) {
}
ctr, err := m.Float64ObservableCounter("afloat", instrument.WithFloat64Callback(cback))
assert.NoError(t, err)
_, err = m.RegisterCallback(func(ctx context.Context) error {
ctr.Observe(ctx, 3)
_, err = m.RegisterCallback(func(_ context.Context, o metric.Observer) error {
o.ObserveFloat64(ctr, 3)
return nil
}, ctr)
assert.NoError(t, err)
@@ -297,8 +303,8 @@ func TestMeterCreatesInstruments(t *testing.T) {
}
ctr, err := m.Float64ObservableUpDownCounter("afloat", instrument.WithFloat64Callback(cback))
assert.NoError(t, err)
_, err = m.RegisterCallback(func(ctx context.Context) error {
ctr.Observe(ctx, 11)
_, err = m.RegisterCallback(func(_ context.Context, o metric.Observer) error {
o.ObserveFloat64(ctr, 11)
return nil
}, ctr)
assert.NoError(t, err)
@@ -327,8 +333,8 @@ func TestMeterCreatesInstruments(t *testing.T) {
}
gauge, err := m.Float64ObservableGauge("agauge", instrument.WithFloat64Callback(cback))
assert.NoError(t, err)
_, err = m.RegisterCallback(func(ctx context.Context) error {
gauge.Observe(ctx, 11)
_, err = m.RegisterCallback(func(_ context.Context, o metric.Observer) error {
o.ObserveFloat64(gauge, 11)
return nil
}, gauge)
assert.NoError(t, err)
@@ -496,6 +502,229 @@ func TestMeterCreatesInstruments(t *testing.T) {
}
}
func TestRegisterNonSDKObserverErrors(t *testing.T) {
rdr := NewManualReader()
mp := NewMeterProvider(WithReader(rdr))
meter := mp.Meter("scope")
type obsrv struct{ instrument.Asynchronous }
o := obsrv{}
_, err := meter.RegisterCallback(
func(context.Context, metric.Observer) error { return nil },
o,
)
assert.ErrorContains(
t,
err,
"invalid observable: from different implementation",
"External instrument registred",
)
}
func TestMeterMixingOnRegisterErrors(t *testing.T) {
rdr := NewManualReader()
mp := NewMeterProvider(WithReader(rdr))
m1 := mp.Meter("scope1")
m2 := mp.Meter("scope2")
iCtr, err := m2.Int64ObservableCounter("int64 ctr")
require.NoError(t, err)
fCtr, err := m2.Float64ObservableCounter("float64 ctr")
require.NoError(t, err)
_, err = m1.RegisterCallback(
func(context.Context, metric.Observer) error { return nil },
iCtr, fCtr,
)
assert.ErrorContains(
t,
err,
`invalid registration: observable "int64 ctr" from Meter "scope2", registered with Meter "scope1"`,
"Instrument registred with non-creation Meter",
)
assert.ErrorContains(
t,
err,
`invalid registration: observable "float64 ctr" from Meter "scope2", registered with Meter "scope1"`,
"Instrument registred with non-creation Meter",
)
}
func TestCallbackObserverNonRegistered(t *testing.T) {
rdr := NewManualReader()
mp := NewMeterProvider(WithReader(rdr))
m1 := mp.Meter("scope1")
valid, err := m1.Int64ObservableCounter("ctr")
require.NoError(t, err)
m2 := mp.Meter("scope2")
iCtr, err := m2.Int64ObservableCounter("int64 ctr")
require.NoError(t, err)
fCtr, err := m2.Float64ObservableCounter("float64 ctr")
require.NoError(t, err)
// Panics if Observe is called.
type int64Obsrv struct{ instrument.Int64Observer }
int64Foreign := int64Obsrv{}
type float64Obsrv struct{ instrument.Float64Observer }
float64Foreign := float64Obsrv{}
_, err = m1.RegisterCallback(
func(_ context.Context, o metric.Observer) error {
o.ObserveInt64(valid, 1)
o.ObserveInt64(iCtr, 1)
o.ObserveFloat64(fCtr, 1)
o.ObserveInt64(int64Foreign, 1)
o.ObserveFloat64(float64Foreign, 1)
return nil
},
valid,
)
require.NoError(t, err)
var got metricdata.ResourceMetrics
assert.NotPanics(t, func() {
got, err = rdr.Collect(context.Background())
})
assert.NoError(t, err)
want := metricdata.ResourceMetrics{
Resource: resource.Default(),
ScopeMetrics: []metricdata.ScopeMetrics{
{
Scope: instrumentation.Scope{
Name: "scope1",
},
Metrics: []metricdata.Metrics{
{
Name: "ctr",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: []metricdata.DataPoint[int64]{
{
Value: 1,
},
},
},
},
},
},
},
}
metricdatatest.AssertEqual(t, want, got, metricdatatest.IgnoreTimestamp())
}
type logSink struct {
logr.LogSink
messages []string
}
func newLogSink(t *testing.T) *logSink {
return &logSink{LogSink: testr.New(t).GetSink()}
}
func (l *logSink) Info(level int, msg string, keysAndValues ...interface{}) {
l.messages = append(l.messages, msg)
l.LogSink.Info(level, msg, keysAndValues...)
}
func (l *logSink) Error(err error, msg string, keysAndValues ...interface{}) {
l.messages = append(l.messages, fmt.Sprintf("%s: %s", err, msg))
l.LogSink.Error(err, msg, keysAndValues...)
}
func (l *logSink) String() string {
out := make([]string, len(l.messages))
for i := range l.messages {
out[i] = "\t-" + l.messages[i]
}
return strings.Join(out, "\n")
}
func TestGlobalInstRegisterCallback(t *testing.T) {
l := newLogSink(t)
otel.SetLogger(logr.New(l))
const mtrName = "TestGlobalInstRegisterCallback"
preMtr := global.Meter(mtrName)
preInt64Ctr, err := preMtr.Int64ObservableCounter("pre.int64.counter")
require.NoError(t, err)
preFloat64Ctr, err := preMtr.Float64ObservableCounter("pre.float64.counter")
require.NoError(t, err)
rdr := NewManualReader()
mp := NewMeterProvider(WithReader(rdr), WithResource(resource.Empty()))
global.SetMeterProvider(mp)
postMtr := global.Meter(mtrName)
postInt64Ctr, err := postMtr.Int64ObservableCounter("post.int64.counter")
require.NoError(t, err)
postFloat64Ctr, err := postMtr.Float64ObservableCounter("post.float64.counter")
require.NoError(t, err)
cb := func(_ context.Context, o metric.Observer) error {
o.ObserveInt64(preInt64Ctr, 1)
o.ObserveFloat64(preFloat64Ctr, 2)
o.ObserveInt64(postInt64Ctr, 3)
o.ObserveFloat64(postFloat64Ctr, 4)
return nil
}
_, err = preMtr.RegisterCallback(cb, preInt64Ctr, preFloat64Ctr, postInt64Ctr, postFloat64Ctr)
assert.NoError(t, err)
_, err = preMtr.RegisterCallback(cb, preInt64Ctr, preFloat64Ctr, postInt64Ctr, postFloat64Ctr)
assert.NoError(t, err)
got, err := rdr.Collect(context.Background())
assert.NoError(t, err)
assert.Lenf(t, l.messages, 0, "Warnings and errors logged:\n%s", l)
metricdatatest.AssertEqual(t, metricdata.ResourceMetrics{
ScopeMetrics: []metricdata.ScopeMetrics{
{
Scope: instrumentation.Scope{Name: "TestGlobalInstRegisterCallback"},
Metrics: []metricdata.Metrics{
{
Name: "pre.int64.counter",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: []metricdata.DataPoint[int64]{{Value: 1}},
},
},
{
Name: "pre.float64.counter",
Data: metricdata.Sum[float64]{
DataPoints: []metricdata.DataPoint[float64]{{Value: 2}},
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
},
},
{
Name: "post.int64.counter",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: []metricdata.DataPoint[int64]{{Value: 3}},
},
},
{
Name: "post.float64.counter",
Data: metricdata.Sum[float64]{
DataPoints: []metricdata.DataPoint[float64]{{Value: 4}},
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
},
},
},
},
},
}, got, metricdatatest.IgnoreTimestamp())
}
func TestMetersProvideScope(t *testing.T) {
rdr := NewManualReader()
mp := NewMeterProvider(WithReader(rdr))
@@ -503,8 +732,8 @@ func TestMetersProvideScope(t *testing.T) {
m1 := mp.Meter("scope1")
ctr1, err := m1.Float64ObservableCounter("ctr1")
assert.NoError(t, err)
_, err = m1.RegisterCallback(func(ctx context.Context) error {
ctr1.Observe(ctx, 5)
_, err = m1.RegisterCallback(func(_ context.Context, o metric.Observer) error {
o.ObserveFloat64(ctr1, 5)
return nil
}, ctr1)
assert.NoError(t, err)
@@ -512,8 +741,8 @@ func TestMetersProvideScope(t *testing.T) {
m2 := mp.Meter("scope2")
ctr2, err := m2.Int64ObservableCounter("ctr2")
assert.NoError(t, err)
_, err = m1.RegisterCallback(func(ctx context.Context) error {
ctr2.Observe(ctx, 7)
_, err = m2.RegisterCallback(func(_ context.Context, o metric.Observer) error {
o.ObserveInt64(ctr2, 7)
return nil
}, ctr2)
assert.NoError(t, err)
@@ -592,7 +821,7 @@ func TestUnregisterUnregisters(t *testing.T) {
var called bool
reg, err := m.RegisterCallback(
func(context.Context) error {
func(context.Context, metric.Observer) error {
called = true
return nil
},
@@ -646,7 +875,7 @@ func TestRegisterCallbackDropAggregations(t *testing.T) {
var called bool
_, err = m.RegisterCallback(
func(context.Context) error {
func(context.Context, metric.Observer) error {
called = true
return nil
},
@@ -681,9 +910,9 @@ func TestAttributeFilter(t *testing.T) {
if err != nil {
return err
}
_, err = mtr.RegisterCallback(func(ctx context.Context) error {
ctr.Observe(ctx, 1.0, attribute.String("foo", "bar"), attribute.Int("version", 1))
ctr.Observe(ctx, 2.0, attribute.String("foo", "bar"), attribute.Int("version", 2))
_, err = mtr.RegisterCallback(func(_ context.Context, o metric.Observer) error {
o.ObserveFloat64(ctr, 1.0, attribute.String("foo", "bar"), attribute.Int("version", 1))
o.ObserveFloat64(ctr, 2.0, attribute.String("foo", "bar"), attribute.Int("version", 2))
return nil
}, ctr)
return err
@@ -709,9 +938,9 @@ func TestAttributeFilter(t *testing.T) {
if err != nil {
return err
}
_, err = mtr.RegisterCallback(func(ctx context.Context) error {
ctr.Observe(ctx, 1.0, attribute.String("foo", "bar"), attribute.Int("version", 1))
ctr.Observe(ctx, 2.0, attribute.String("foo", "bar"), attribute.Int("version", 2))
_, err = mtr.RegisterCallback(func(_ context.Context, o metric.Observer) error {
o.ObserveFloat64(ctr, 1.0, attribute.String("foo", "bar"), attribute.Int("version", 1))
o.ObserveFloat64(ctr, 2.0, attribute.String("foo", "bar"), attribute.Int("version", 2))
return nil
}, ctr)
return err
@@ -737,9 +966,9 @@ func TestAttributeFilter(t *testing.T) {
if err != nil {
return err
}
_, err = mtr.RegisterCallback(func(ctx context.Context) error {
ctr.Observe(ctx, 1.0, attribute.String("foo", "bar"), attribute.Int("version", 1))
ctr.Observe(ctx, 2.0, attribute.String("foo", "bar"), attribute.Int("version", 2))
_, err = mtr.RegisterCallback(func(_ context.Context, o metric.Observer) error {
o.ObserveFloat64(ctr, 1.0, attribute.String("foo", "bar"), attribute.Int("version", 1))
o.ObserveFloat64(ctr, 2.0, attribute.String("foo", "bar"), attribute.Int("version", 2))
return nil
}, ctr)
return err
@@ -763,9 +992,9 @@ func TestAttributeFilter(t *testing.T) {
if err != nil {
return err
}
_, err = mtr.RegisterCallback(func(ctx context.Context) error {
ctr.Observe(ctx, 10, attribute.String("foo", "bar"), attribute.Int("version", 1))
ctr.Observe(ctx, 20, attribute.String("foo", "bar"), attribute.Int("version", 2))
_, err = mtr.RegisterCallback(func(_ context.Context, o metric.Observer) error {
o.ObserveInt64(ctr, 10, attribute.String("foo", "bar"), attribute.Int("version", 1))
o.ObserveInt64(ctr, 20, attribute.String("foo", "bar"), attribute.Int("version", 2))
return nil
}, ctr)
return err
@@ -791,9 +1020,9 @@ func TestAttributeFilter(t *testing.T) {
if err != nil {
return err
}
_, err = mtr.RegisterCallback(func(ctx context.Context) error {
ctr.Observe(ctx, 10, attribute.String("foo", "bar"), attribute.Int("version", 1))
ctr.Observe(ctx, 20, attribute.String("foo", "bar"), attribute.Int("version", 2))
_, err = mtr.RegisterCallback(func(_ context.Context, o metric.Observer) error {
o.ObserveInt64(ctr, 10, attribute.String("foo", "bar"), attribute.Int("version", 1))
o.ObserveInt64(ctr, 20, attribute.String("foo", "bar"), attribute.Int("version", 2))
return nil
}, ctr)
return err
@@ -819,9 +1048,9 @@ func TestAttributeFilter(t *testing.T) {
if err != nil {
return err
}
_, err = mtr.RegisterCallback(func(ctx context.Context) error {
ctr.Observe(ctx, 10, attribute.String("foo", "bar"), attribute.Int("version", 1))
ctr.Observe(ctx, 20, attribute.String("foo", "bar"), attribute.Int("version", 2))
_, err = mtr.RegisterCallback(func(_ context.Context, o metric.Observer) error {
o.ObserveInt64(ctr, 10, attribute.String("foo", "bar"), attribute.Int("version", 1))
o.ObserveInt64(ctr, 20, attribute.String("foo", "bar"), attribute.Int("version", 2))
return nil
}, ctr)
return err