You've already forked opentelemetry-go
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user