You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-11-23 22:34:47 +02:00
Encapsulate stdouttrace.Exporter instrumentation in internal package (#7307)
[Follow
guidelines](a5dcd68ebb/CONTRIBUTING.md (encapsulation))
and move instrumentation into its own type.
This commit is contained in:
@@ -8,3 +8,4 @@ nam
|
|||||||
valu
|
valu
|
||||||
thirdparty
|
thirdparty
|
||||||
addOpt
|
addOpt
|
||||||
|
observ
|
||||||
|
|||||||
205
exporters/stdout/stdouttrace/internal/observ/instrumentation.go
Normal file
205
exporters/stdout/stdouttrace/internal/observ/instrumentation.go
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
// Package observ provides experimental observability instrumentation
|
||||||
|
// for the stdout trace exporter.
|
||||||
|
package observ // import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/observ"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal"
|
||||||
|
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/x"
|
||||||
|
"go.opentelemetry.io/otel/metric"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
|
||||||
|
"go.opentelemetry.io/otel/semconv/v1.37.0/otelconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ComponentType uniquely identifies the OpenTelemetry Exporter component
|
||||||
|
// being instrumented.
|
||||||
|
//
|
||||||
|
// The STDOUT trace exporter is not a standardized OTel component type, so
|
||||||
|
// it uses the Go package prefixed type name to ensure uniqueness and
|
||||||
|
// identity.
|
||||||
|
ComponentType = "go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter"
|
||||||
|
|
||||||
|
// ScopeName is the unique name of the meter used for instrumentation.
|
||||||
|
ScopeName = "go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/observ"
|
||||||
|
|
||||||
|
// SchemaURL is the schema URL of the metrics produced by this
|
||||||
|
// instrumentation.
|
||||||
|
SchemaURL = semconv.SchemaURL
|
||||||
|
|
||||||
|
// Version is the current version of this instrumentation.
|
||||||
|
//
|
||||||
|
// This matches the version of the exporter.
|
||||||
|
Version = internal.Version
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
measureAttrsPool = &sync.Pool{
|
||||||
|
New: func() any {
|
||||||
|
// "component.name" + "component.type" + "error.type"
|
||||||
|
const n = 1 + 1 + 1
|
||||||
|
s := make([]attribute.KeyValue, 0, n)
|
||||||
|
// Return a pointer to a slice instead of a slice itself
|
||||||
|
// to avoid allocations on every call.
|
||||||
|
return &s
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
addOptPool = &sync.Pool{
|
||||||
|
New: func() any {
|
||||||
|
const n = 1 // WithAttributeSet
|
||||||
|
o := make([]metric.AddOption, 0, n)
|
||||||
|
return &o
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
recordOptPool = &sync.Pool{
|
||||||
|
New: func() any {
|
||||||
|
const n = 1 // WithAttributeSet
|
||||||
|
o := make([]metric.RecordOption, 0, n)
|
||||||
|
return &o
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func get[T any](p *sync.Pool) *[]T { return p.Get().(*[]T) }
|
||||||
|
|
||||||
|
func put[T any](p *sync.Pool, s *[]T) {
|
||||||
|
*s = (*s)[:0] // Reset.
|
||||||
|
p.Put(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ComponentName(id int64) string {
|
||||||
|
return fmt.Sprintf("%s/%d", ComponentType, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instrumentation is experimental instrumentation for the exporter.
|
||||||
|
type Instrumentation struct {
|
||||||
|
inflightSpans metric.Int64UpDownCounter
|
||||||
|
exportedSpans metric.Int64Counter
|
||||||
|
opDuration metric.Float64Histogram
|
||||||
|
|
||||||
|
attrs []attribute.KeyValue
|
||||||
|
setOpt metric.MeasurementOption
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInstrumentation returns instrumentation for a STDOUT trace exporter with
|
||||||
|
// the provided ID using the global MeterProvider.
|
||||||
|
//
|
||||||
|
// If the experimental observability is disabled, nil is returned.
|
||||||
|
func NewInstrumentation(id int64) (*Instrumentation, error) {
|
||||||
|
if !x.SelfObservability.Enabled() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
i := &Instrumentation{
|
||||||
|
attrs: []attribute.KeyValue{
|
||||||
|
semconv.OTelComponentName(ComponentName(id)),
|
||||||
|
semconv.OTelComponentTypeKey.String(ComponentType),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
s := attribute.NewSet(i.attrs...)
|
||||||
|
i.setOpt = metric.WithAttributeSet(s)
|
||||||
|
|
||||||
|
mp := otel.GetMeterProvider()
|
||||||
|
m := mp.Meter(
|
||||||
|
ScopeName,
|
||||||
|
metric.WithInstrumentationVersion(Version),
|
||||||
|
metric.WithSchemaURL(SchemaURL),
|
||||||
|
)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
inflightSpans, e := otelconv.NewSDKExporterSpanInflight(m)
|
||||||
|
if e != nil {
|
||||||
|
e = fmt.Errorf("failed to create span inflight metric: %w", e)
|
||||||
|
err = errors.Join(err, e)
|
||||||
|
}
|
||||||
|
i.inflightSpans = inflightSpans.Inst()
|
||||||
|
|
||||||
|
exportedSpans, e := otelconv.NewSDKExporterSpanExported(m)
|
||||||
|
if e != nil {
|
||||||
|
e = fmt.Errorf("failed to create span exported metric: %w", e)
|
||||||
|
err = errors.Join(err, e)
|
||||||
|
}
|
||||||
|
i.exportedSpans = exportedSpans.Inst()
|
||||||
|
|
||||||
|
opDuration, e := otelconv.NewSDKExporterOperationDuration(m)
|
||||||
|
if e != nil {
|
||||||
|
e = fmt.Errorf("failed to create operation duration metric: %w", e)
|
||||||
|
err = errors.Join(err, e)
|
||||||
|
}
|
||||||
|
i.opDuration = opDuration.Inst()
|
||||||
|
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportSpansDone is a function that is called when a call to an Exporter's
|
||||||
|
// ExportSpans method completes.
|
||||||
|
//
|
||||||
|
// The number of successful exports is provided as success. Any error that is
|
||||||
|
// encountered is provided as err.
|
||||||
|
type ExportSpansDone func(success int64, err error)
|
||||||
|
|
||||||
|
// ExportSpans instruments the ExportSpans method of the exporter. It returns a
|
||||||
|
// function that needs to be deferred so it is called when the method returns.
|
||||||
|
func (i *Instrumentation) ExportSpans(ctx context.Context, nSpans int) ExportSpansDone {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
addOpt := get[metric.AddOption](addOptPool)
|
||||||
|
defer put(addOptPool, addOpt)
|
||||||
|
*addOpt = append(*addOpt, i.setOpt)
|
||||||
|
i.inflightSpans.Add(ctx, int64(nSpans), *addOpt...)
|
||||||
|
|
||||||
|
return i.end(ctx, start, int64(nSpans))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instrumentation) end(ctx context.Context, start time.Time, n int64) ExportSpansDone {
|
||||||
|
return func(success int64, err error) {
|
||||||
|
addOpt := get[metric.AddOption](addOptPool)
|
||||||
|
defer put(addOptPool, addOpt)
|
||||||
|
*addOpt = append(*addOpt, i.setOpt)
|
||||||
|
|
||||||
|
i.inflightSpans.Add(ctx, -n, *addOpt...)
|
||||||
|
|
||||||
|
// Record the success and duration of the operation.
|
||||||
|
//
|
||||||
|
// Do not exclude 0 values, as they are valid and indicate no spans
|
||||||
|
// were exported which is meaningful for certain aggregations.
|
||||||
|
i.exportedSpans.Add(ctx, success, *addOpt...)
|
||||||
|
|
||||||
|
mOpt := i.setOpt
|
||||||
|
if err != nil {
|
||||||
|
attrs := get[attribute.KeyValue](measureAttrsPool)
|
||||||
|
defer put(measureAttrsPool, attrs)
|
||||||
|
*attrs = append(*attrs, i.attrs...)
|
||||||
|
*attrs = append(*attrs, semconv.ErrorType(err))
|
||||||
|
|
||||||
|
// Do not inefficiently make a copy of attrs by using
|
||||||
|
// WithAttributes instead of WithAttributeSet.
|
||||||
|
set := attribute.NewSet(*attrs...)
|
||||||
|
mOpt = metric.WithAttributeSet(set)
|
||||||
|
|
||||||
|
// Reset addOpt with new attribute set.
|
||||||
|
*addOpt = append((*addOpt)[:0], mOpt)
|
||||||
|
|
||||||
|
i.exportedSpans.Add(ctx, n-success, *addOpt...)
|
||||||
|
}
|
||||||
|
|
||||||
|
recordOpt := get[metric.RecordOption](recordOptPool)
|
||||||
|
defer put(recordOptPool, recordOpt)
|
||||||
|
*recordOpt = append(*recordOpt, mOpt)
|
||||||
|
i.opDuration.Record(ctx, time.Since(start).Seconds(), *recordOpt...)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package observ_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/observ"
|
||||||
|
mapi "go.opentelemetry.io/otel/metric"
|
||||||
|
"go.opentelemetry.io/otel/sdk/instrumentation"
|
||||||
|
"go.opentelemetry.io/otel/sdk/metric"
|
||||||
|
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||||
|
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
|
||||||
|
"go.opentelemetry.io/otel/semconv/v1.37.0/otelconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ID = 0
|
||||||
|
|
||||||
|
var Scope = instrumentation.Scope{
|
||||||
|
Name: observ.ScopeName,
|
||||||
|
Version: observ.Version,
|
||||||
|
SchemaURL: observ.SchemaURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
type errMeterProvider struct {
|
||||||
|
mapi.MeterProvider
|
||||||
|
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *errMeterProvider) Meter(string, ...mapi.MeterOption) mapi.Meter {
|
||||||
|
return &errMeter{err: m.err}
|
||||||
|
}
|
||||||
|
|
||||||
|
type errMeter struct {
|
||||||
|
mapi.Meter
|
||||||
|
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *errMeter) Int64UpDownCounter(string, ...mapi.Int64UpDownCounterOption) (mapi.Int64UpDownCounter, error) {
|
||||||
|
return nil, m.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *errMeter) Int64Counter(string, ...mapi.Int64CounterOption) (mapi.Int64Counter, error) {
|
||||||
|
return nil, m.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *errMeter) Float64Histogram(string, ...mapi.Float64HistogramOption) (mapi.Float64Histogram, error) {
|
||||||
|
return nil, m.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewInstrumentationObservabiltyErrors(t *testing.T) {
|
||||||
|
orig := otel.GetMeterProvider()
|
||||||
|
t.Cleanup(func() { otel.SetMeterProvider(orig) })
|
||||||
|
mp := &errMeterProvider{err: assert.AnError}
|
||||||
|
otel.SetMeterProvider(mp)
|
||||||
|
|
||||||
|
t.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", "true")
|
||||||
|
|
||||||
|
_, err := observ.NewInstrumentation(ID)
|
||||||
|
require.ErrorIs(t, err, assert.AnError, "new instrument errors")
|
||||||
|
|
||||||
|
assert.ErrorContains(t, err, "inflight metric")
|
||||||
|
assert.ErrorContains(t, err, "span exported metric")
|
||||||
|
assert.ErrorContains(t, err, "operation duration metric")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewInstrumentationObservabiltyDisabled(t *testing.T) {
|
||||||
|
// Do not set OTEL_GO_X_SELF_OBSERVABILITY.
|
||||||
|
got, err := observ.NewInstrumentation(ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Nil(t, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setup(t *testing.T) (*observ.Instrumentation, func() metricdata.ScopeMetrics) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
t.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", "true")
|
||||||
|
|
||||||
|
original := otel.GetMeterProvider()
|
||||||
|
t.Cleanup(func() { otel.SetMeterProvider(original) })
|
||||||
|
|
||||||
|
r := metric.NewManualReader()
|
||||||
|
mp := metric.NewMeterProvider(metric.WithReader(r))
|
||||||
|
otel.SetMeterProvider(mp)
|
||||||
|
|
||||||
|
inst, err := observ.NewInstrumentation(ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, inst)
|
||||||
|
|
||||||
|
return inst, func() metricdata.ScopeMetrics {
|
||||||
|
var rm metricdata.ResourceMetrics
|
||||||
|
require.NoError(t, r.Collect(context.Background(), &rm))
|
||||||
|
|
||||||
|
require.Len(t, rm.ScopeMetrics, 1)
|
||||||
|
return rm.ScopeMetrics[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func set(err error) attribute.Set {
|
||||||
|
attrs := []attribute.KeyValue{
|
||||||
|
semconv.OTelComponentName(observ.ComponentName(ID)),
|
||||||
|
semconv.OTelComponentTypeKey.String(observ.ComponentType),
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
attrs = append(attrs, semconv.ErrorType(err))
|
||||||
|
}
|
||||||
|
return attribute.NewSet(attrs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func spanInflight() metricdata.Metrics {
|
||||||
|
return metricdata.Metrics{
|
||||||
|
Name: otelconv.SDKExporterSpanInflight{}.Name(),
|
||||||
|
Description: otelconv.SDKExporterSpanInflight{}.Description(),
|
||||||
|
Unit: otelconv.SDKExporterSpanInflight{}.Unit(),
|
||||||
|
Data: metricdata.Sum[int64]{
|
||||||
|
Temporality: metricdata.CumulativeTemporality,
|
||||||
|
DataPoints: []metricdata.DataPoint[int64]{
|
||||||
|
{Attributes: set(nil), Value: 0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func spanExported(success, total int64, err error) metricdata.Metrics {
|
||||||
|
dp := []metricdata.DataPoint[int64]{
|
||||||
|
{Attributes: set(nil), Value: success},
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
dp = append(dp, metricdata.DataPoint[int64]{
|
||||||
|
Attributes: set(err),
|
||||||
|
Value: total - success,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return metricdata.Metrics{
|
||||||
|
Name: otelconv.SDKExporterSpanExported{}.Name(),
|
||||||
|
Description: otelconv.SDKExporterSpanExported{}.Description(),
|
||||||
|
Unit: otelconv.SDKExporterSpanExported{}.Unit(),
|
||||||
|
Data: metricdata.Sum[int64]{
|
||||||
|
Temporality: metricdata.CumulativeTemporality,
|
||||||
|
IsMonotonic: true,
|
||||||
|
DataPoints: dp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func operationDuration(err error) metricdata.Metrics {
|
||||||
|
return metricdata.Metrics{
|
||||||
|
Name: otelconv.SDKExporterOperationDuration{}.Name(),
|
||||||
|
Description: otelconv.SDKExporterOperationDuration{}.Description(),
|
||||||
|
Unit: otelconv.SDKExporterOperationDuration{}.Unit(),
|
||||||
|
Data: metricdata.Histogram[float64]{
|
||||||
|
Temporality: metricdata.CumulativeTemporality,
|
||||||
|
DataPoints: []metricdata.HistogramDataPoint[float64]{
|
||||||
|
{Attributes: set(err)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertMetrics(t *testing.T, got metricdata.ScopeMetrics, spans, success int64, err error) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
assert.Equal(t, Scope, got.Scope, "unexpected scope")
|
||||||
|
|
||||||
|
m := got.Metrics
|
||||||
|
require.Len(t, m, 3, "expected 3 metrics")
|
||||||
|
|
||||||
|
o := metricdatatest.IgnoreTimestamp()
|
||||||
|
want := spanInflight()
|
||||||
|
metricdatatest.AssertEqual(t, want, m[0], o)
|
||||||
|
|
||||||
|
want = spanExported(success, spans, err)
|
||||||
|
metricdatatest.AssertEqual(t, want, m[1], o)
|
||||||
|
|
||||||
|
want = operationDuration(err)
|
||||||
|
metricdatatest.AssertEqual(t, want, m[2], o, metricdatatest.IgnoreValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInstrumentationExportSpans(t *testing.T) {
|
||||||
|
inst, collect := setup(t)
|
||||||
|
|
||||||
|
const n = 10
|
||||||
|
end := inst.ExportSpans(context.Background(), n)
|
||||||
|
end(n, nil)
|
||||||
|
|
||||||
|
assertMetrics(t, collect(), n, n, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInstrumentationExportSpansAllErrored(t *testing.T) {
|
||||||
|
inst, collect := setup(t)
|
||||||
|
|
||||||
|
const n = 10
|
||||||
|
end := inst.ExportSpans(context.Background(), n)
|
||||||
|
const success = 0
|
||||||
|
end(success, assert.AnError)
|
||||||
|
|
||||||
|
assertMetrics(t, collect(), n, success, assert.AnError)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInstrumentationExportSpansPartialErrored(t *testing.T) {
|
||||||
|
inst, collect := setup(t)
|
||||||
|
|
||||||
|
const n = 10
|
||||||
|
end := inst.ExportSpans(context.Background(), n)
|
||||||
|
const success = 5
|
||||||
|
end(success, assert.AnError)
|
||||||
|
|
||||||
|
assertMetrics(t, collect(), n, success, assert.AnError)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkInstrumentationExportSpans(b *testing.B) {
|
||||||
|
b.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", "true")
|
||||||
|
inst, err := observ.NewInstrumentation(ID)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatalf("failed to create instrumentation: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var end observ.ExportSpansDone
|
||||||
|
err = errors.New("benchmark error")
|
||||||
|
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for b.Loop() {
|
||||||
|
end = inst.ExportSpans(context.Background(), 10)
|
||||||
|
end(4, err)
|
||||||
|
}
|
||||||
|
_ = end
|
||||||
|
}
|
||||||
8
exporters/stdout/stdouttrace/internal/version.go
Normal file
8
exporters/stdout/stdouttrace/internal/version.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package internal // import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal"
|
||||||
|
|
||||||
|
// Version is the current release version of the OpenTelemetry stdouttrace
|
||||||
|
// exporter in use.
|
||||||
|
const Version = "1.38.0"
|
||||||
@@ -11,23 +11,12 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
|
||||||
"go.opentelemetry.io/otel/attribute"
|
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/counter"
|
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/counter"
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/x"
|
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/observ"
|
||||||
"go.opentelemetry.io/otel/metric"
|
|
||||||
"go.opentelemetry.io/otel/sdk"
|
|
||||||
"go.opentelemetry.io/otel/sdk/trace"
|
"go.opentelemetry.io/otel/sdk/trace"
|
||||||
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
||||||
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
|
|
||||||
"go.opentelemetry.io/otel/semconv/v1.37.0/otelconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// otelComponentType is a name identifying the type of the OpenTelemetry
|
|
||||||
// component. It is not a standardized OTel component type, so it uses the
|
|
||||||
// Go package prefixed type name to ensure uniqueness and identity.
|
|
||||||
const otelComponentType = "go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter"
|
|
||||||
|
|
||||||
var zeroTime time.Time
|
var zeroTime time.Time
|
||||||
|
|
||||||
var _ trace.SpanExporter = &Exporter{}
|
var _ trace.SpanExporter = &Exporter{}
|
||||||
@@ -46,39 +35,8 @@ func New(options ...Option) (*Exporter, error) {
|
|||||||
timestamps: cfg.Timestamps,
|
timestamps: cfg.Timestamps,
|
||||||
}
|
}
|
||||||
|
|
||||||
if !x.SelfObservability.Enabled() {
|
var err error
|
||||||
return exporter, nil
|
exporter.inst, err = observ.NewInstrumentation(counter.NextExporterID())
|
||||||
}
|
|
||||||
|
|
||||||
exporter.selfObservabilityEnabled = true
|
|
||||||
exporter.selfObservabilityAttrs = []attribute.KeyValue{
|
|
||||||
semconv.OTelComponentName(fmt.Sprintf("%s/%d", otelComponentType, counter.NextExporterID())),
|
|
||||||
semconv.OTelComponentTypeKey.String(otelComponentType),
|
|
||||||
}
|
|
||||||
s := attribute.NewSet(exporter.selfObservabilityAttrs...)
|
|
||||||
exporter.selfObservabilitySetOpt = metric.WithAttributeSet(s)
|
|
||||||
|
|
||||||
mp := otel.GetMeterProvider()
|
|
||||||
m := mp.Meter(
|
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace",
|
|
||||||
metric.WithInstrumentationVersion(sdk.Version()),
|
|
||||||
metric.WithSchemaURL(semconv.SchemaURL),
|
|
||||||
)
|
|
||||||
|
|
||||||
var err, e error
|
|
||||||
if exporter.spanInflightMetric, e = otelconv.NewSDKExporterSpanInflight(m); e != nil {
|
|
||||||
e = fmt.Errorf("failed to create span inflight metric: %w", e)
|
|
||||||
err = errors.Join(err, e)
|
|
||||||
}
|
|
||||||
if exporter.spanExportedMetric, e = otelconv.NewSDKExporterSpanExported(m); e != nil {
|
|
||||||
e = fmt.Errorf("failed to create span exported metric: %w", e)
|
|
||||||
err = errors.Join(err, e)
|
|
||||||
}
|
|
||||||
if exporter.operationDurationMetric, e = otelconv.NewSDKExporterOperationDuration(m); e != nil {
|
|
||||||
e = fmt.Errorf("failed to create operation duration metric: %w", e)
|
|
||||||
err = errors.Join(err, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
return exporter, err
|
return exporter, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,107 +49,15 @@ type Exporter struct {
|
|||||||
stoppedMu sync.RWMutex
|
stoppedMu sync.RWMutex
|
||||||
stopped bool
|
stopped bool
|
||||||
|
|
||||||
selfObservabilityEnabled bool
|
inst *observ.Instrumentation
|
||||||
selfObservabilityAttrs []attribute.KeyValue // selfObservability common attributes
|
|
||||||
selfObservabilitySetOpt metric.MeasurementOption
|
|
||||||
spanInflightMetric otelconv.SDKExporterSpanInflight
|
|
||||||
spanExportedMetric otelconv.SDKExporterSpanExported
|
|
||||||
operationDurationMetric otelconv.SDKExporterOperationDuration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
measureAttrsPool = sync.Pool{
|
|
||||||
New: func() any {
|
|
||||||
// "component.name" + "component.type" + "error.type"
|
|
||||||
const n = 1 + 1 + 1
|
|
||||||
s := make([]attribute.KeyValue, 0, n)
|
|
||||||
// Return a pointer to a slice instead of a slice itself
|
|
||||||
// to avoid allocations on every call.
|
|
||||||
return &s
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
addOptPool = &sync.Pool{
|
|
||||||
New: func() any {
|
|
||||||
const n = 1 // WithAttributeSet
|
|
||||||
o := make([]metric.AddOption, 0, n)
|
|
||||||
return &o
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
recordOptPool = &sync.Pool{
|
|
||||||
New: func() any {
|
|
||||||
const n = 1 // WithAttributeSet
|
|
||||||
o := make([]metric.RecordOption, 0, n)
|
|
||||||
return &o
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// ExportSpans writes spans in json format to stdout.
|
// ExportSpans writes spans in json format to stdout.
|
||||||
func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) (err error) {
|
func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) (err error) {
|
||||||
var success int64
|
var success int64
|
||||||
if e.selfObservabilityEnabled {
|
if e.inst != nil {
|
||||||
count := int64(len(spans))
|
end := e.inst.ExportSpans(ctx, len(spans))
|
||||||
|
defer func() { end(success, err) }()
|
||||||
addOpt := addOptPool.Get().(*[]metric.AddOption)
|
|
||||||
defer func() {
|
|
||||||
*addOpt = (*addOpt)[:0]
|
|
||||||
addOptPool.Put(addOpt)
|
|
||||||
}()
|
|
||||||
|
|
||||||
*addOpt = append(*addOpt, e.selfObservabilitySetOpt)
|
|
||||||
|
|
||||||
e.spanInflightMetric.Inst().Add(ctx, count, *addOpt...)
|
|
||||||
defer func(starting time.Time) {
|
|
||||||
e.spanInflightMetric.Inst().Add(ctx, -count, *addOpt...)
|
|
||||||
|
|
||||||
// Record the success and duration of the operation.
|
|
||||||
//
|
|
||||||
// Do not exclude 0 values, as they are valid and indicate no spans
|
|
||||||
// were exported which is meaningful for certain aggregations.
|
|
||||||
e.spanExportedMetric.Inst().Add(ctx, success, *addOpt...)
|
|
||||||
|
|
||||||
mOpt := e.selfObservabilitySetOpt
|
|
||||||
if err != nil {
|
|
||||||
// additional attributes for self-observability,
|
|
||||||
// only spanExportedMetric and operationDurationMetric are supported.
|
|
||||||
attrs := measureAttrsPool.Get().(*[]attribute.KeyValue)
|
|
||||||
defer func() {
|
|
||||||
*attrs = (*attrs)[:0] // reset the slice for reuse
|
|
||||||
measureAttrsPool.Put(attrs)
|
|
||||||
}()
|
|
||||||
*attrs = append(*attrs, e.selfObservabilityAttrs...)
|
|
||||||
*attrs = append(*attrs, semconv.ErrorType(err))
|
|
||||||
|
|
||||||
// Do not inefficiently make a copy of attrs by using
|
|
||||||
// WithAttributes instead of WithAttributeSet.
|
|
||||||
set := attribute.NewSet(*attrs...)
|
|
||||||
mOpt = metric.WithAttributeSet(set)
|
|
||||||
|
|
||||||
// Reset addOpt with new attribute set.
|
|
||||||
*addOpt = append((*addOpt)[:0], mOpt)
|
|
||||||
|
|
||||||
e.spanExportedMetric.Inst().Add(
|
|
||||||
ctx,
|
|
||||||
count-success,
|
|
||||||
*addOpt...,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
recordOpt := recordOptPool.Get().(*[]metric.RecordOption)
|
|
||||||
defer func() {
|
|
||||||
*recordOpt = (*recordOpt)[:0]
|
|
||||||
recordOptPool.Put(recordOpt)
|
|
||||||
}()
|
|
||||||
|
|
||||||
*recordOpt = append(*recordOpt, mOpt)
|
|
||||||
e.operationDurationMetric.Inst().Record(
|
|
||||||
ctx,
|
|
||||||
time.Since(starting).Seconds(),
|
|
||||||
*recordOpt...,
|
|
||||||
)
|
|
||||||
}(time.Now())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ctx.Err(); err != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
|
|||||||
@@ -20,8 +20,7 @@ import (
|
|||||||
"go.opentelemetry.io/otel/codes"
|
"go.opentelemetry.io/otel/codes"
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
|
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/counter"
|
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/counter"
|
||||||
mapi "go.opentelemetry.io/otel/metric"
|
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/observ"
|
||||||
"go.opentelemetry.io/otel/sdk"
|
|
||||||
"go.opentelemetry.io/otel/sdk/instrumentation"
|
"go.opentelemetry.io/otel/sdk/instrumentation"
|
||||||
"go.opentelemetry.io/otel/sdk/metric"
|
"go.opentelemetry.io/otel/sdk/metric"
|
||||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||||
@@ -274,9 +273,9 @@ func TestSelfObservability(t *testing.T) {
|
|||||||
require.Len(t, sm.Metrics, 3)
|
require.Len(t, sm.Metrics, 3)
|
||||||
|
|
||||||
assert.Equal(t, instrumentation.Scope{
|
assert.Equal(t, instrumentation.Scope{
|
||||||
Name: "go.opentelemetry.io/otel/exporters/stdout/stdouttrace",
|
Name: observ.ScopeName,
|
||||||
Version: sdk.Version(),
|
Version: observ.Version,
|
||||||
SchemaURL: semconv.SchemaURL,
|
SchemaURL: observ.SchemaURL,
|
||||||
}, sm.Scope)
|
}, sm.Scope)
|
||||||
|
|
||||||
metricdatatest.AssertEqual(t, metricdata.Metrics{
|
metricdatatest.AssertEqual(t, metricdata.Metrics{
|
||||||
@@ -288,12 +287,8 @@ func TestSelfObservability(t *testing.T) {
|
|||||||
DataPoints: []metricdata.DataPoint[int64]{
|
DataPoints: []metricdata.DataPoint[int64]{
|
||||||
{
|
{
|
||||||
Attributes: attribute.NewSet(
|
Attributes: attribute.NewSet(
|
||||||
semconv.OTelComponentName(
|
semconv.OTelComponentName(observ.ComponentName(0)),
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter/0",
|
semconv.OTelComponentTypeKey.String(observ.ComponentType),
|
||||||
),
|
|
||||||
semconv.OTelComponentTypeKey.String(
|
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter",
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Value: 0,
|
Value: 0,
|
||||||
},
|
},
|
||||||
@@ -311,12 +306,8 @@ func TestSelfObservability(t *testing.T) {
|
|||||||
DataPoints: []metricdata.DataPoint[int64]{
|
DataPoints: []metricdata.DataPoint[int64]{
|
||||||
{
|
{
|
||||||
Attributes: attribute.NewSet(
|
Attributes: attribute.NewSet(
|
||||||
semconv.OTelComponentName(
|
semconv.OTelComponentName(observ.ComponentName(0)),
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter/0",
|
semconv.OTelComponentTypeKey.String(observ.ComponentType),
|
||||||
),
|
|
||||||
semconv.OTelComponentTypeKey.String(
|
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter",
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Value: 2,
|
Value: 2,
|
||||||
},
|
},
|
||||||
@@ -333,259 +324,8 @@ func TestSelfObservability(t *testing.T) {
|
|||||||
DataPoints: []metricdata.HistogramDataPoint[float64]{
|
DataPoints: []metricdata.HistogramDataPoint[float64]{
|
||||||
{
|
{
|
||||||
Attributes: attribute.NewSet(
|
Attributes: attribute.NewSet(
|
||||||
semconv.OTelComponentName(
|
semconv.OTelComponentName(observ.ComponentName(0)),
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter/0",
|
semconv.OTelComponentTypeKey.String(observ.ComponentType),
|
||||||
),
|
|
||||||
semconv.OTelComponentTypeKey.String(
|
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, sm.Metrics[2], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue())
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Enabled, but ExportSpans returns error",
|
|
||||||
enabled: true,
|
|
||||||
callExportSpans: func(t *testing.T, exporter *stdouttrace.Exporter) {
|
|
||||||
t.Helper()
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
cancel()
|
|
||||||
|
|
||||||
err := exporter.ExportSpans(ctx, tracetest.SpanStubs{
|
|
||||||
{Name: "/foo"},
|
|
||||||
{Name: "/bar"},
|
|
||||||
}.Snapshots())
|
|
||||||
require.Error(t, err)
|
|
||||||
},
|
|
||||||
assertMetrics: func(t *testing.T, rm metricdata.ResourceMetrics) {
|
|
||||||
t.Helper()
|
|
||||||
require.Len(t, rm.ScopeMetrics, 1)
|
|
||||||
|
|
||||||
sm := rm.ScopeMetrics[0]
|
|
||||||
require.Len(t, sm.Metrics, 3)
|
|
||||||
|
|
||||||
assert.Equal(t, instrumentation.Scope{
|
|
||||||
Name: "go.opentelemetry.io/otel/exporters/stdout/stdouttrace",
|
|
||||||
Version: sdk.Version(),
|
|
||||||
SchemaURL: semconv.SchemaURL,
|
|
||||||
}, sm.Scope)
|
|
||||||
|
|
||||||
metricdatatest.AssertEqual(t, metricdata.Metrics{
|
|
||||||
Name: otelconv.SDKExporterSpanInflight{}.Name(),
|
|
||||||
Description: otelconv.SDKExporterSpanInflight{}.Description(),
|
|
||||||
Unit: otelconv.SDKExporterSpanInflight{}.Unit(),
|
|
||||||
Data: metricdata.Sum[int64]{
|
|
||||||
Temporality: metricdata.CumulativeTemporality,
|
|
||||||
DataPoints: []metricdata.DataPoint[int64]{
|
|
||||||
{
|
|
||||||
Attributes: attribute.NewSet(
|
|
||||||
semconv.OTelComponentName(
|
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter/0",
|
|
||||||
),
|
|
||||||
semconv.OTelComponentTypeKey.String(
|
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Value: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, sm.Metrics[0], metricdatatest.IgnoreTimestamp())
|
|
||||||
|
|
||||||
metricdatatest.AssertEqual(t, metricdata.Metrics{
|
|
||||||
Name: otelconv.SDKExporterSpanExported{}.Name(),
|
|
||||||
Description: otelconv.SDKExporterSpanExported{}.Description(),
|
|
||||||
Unit: otelconv.SDKExporterSpanExported{}.Unit(),
|
|
||||||
Data: metricdata.Sum[int64]{
|
|
||||||
Temporality: metricdata.CumulativeTemporality,
|
|
||||||
IsMonotonic: true,
|
|
||||||
DataPoints: []metricdata.DataPoint[int64]{
|
|
||||||
{
|
|
||||||
Attributes: attribute.NewSet(
|
|
||||||
semconv.OTelComponentName(
|
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter/0",
|
|
||||||
),
|
|
||||||
semconv.OTelComponentTypeKey.String(
|
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Value: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Attributes: attribute.NewSet(
|
|
||||||
semconv.OTelComponentName(
|
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter/0",
|
|
||||||
),
|
|
||||||
semconv.OTelComponentTypeKey.String(
|
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter",
|
|
||||||
),
|
|
||||||
semconv.ErrorType(context.Canceled),
|
|
||||||
),
|
|
||||||
Value: 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, sm.Metrics[1], metricdatatest.IgnoreTimestamp())
|
|
||||||
|
|
||||||
metricdatatest.AssertEqual(t, metricdata.Metrics{
|
|
||||||
Name: otelconv.SDKExporterOperationDuration{}.Name(),
|
|
||||||
Description: otelconv.SDKExporterOperationDuration{}.Description(),
|
|
||||||
Unit: otelconv.SDKExporterOperationDuration{}.Unit(),
|
|
||||||
Data: metricdata.Histogram[float64]{
|
|
||||||
Temporality: metricdata.CumulativeTemporality,
|
|
||||||
DataPoints: []metricdata.HistogramDataPoint[float64]{
|
|
||||||
{
|
|
||||||
Attributes: attribute.NewSet(
|
|
||||||
semconv.OTelComponentName(
|
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter/0",
|
|
||||||
),
|
|
||||||
semconv.OTelComponentTypeKey.String(
|
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter",
|
|
||||||
),
|
|
||||||
semconv.ErrorType(context.Canceled),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, sm.Metrics[2], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue())
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "PartialExport",
|
|
||||||
enabled: true,
|
|
||||||
callExportSpans: func(t *testing.T, exporter *stdouttrace.Exporter) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
err := exporter.ExportSpans(context.Background(), tracetest.SpanStubs{
|
|
||||||
{Name: "/foo"},
|
|
||||||
{
|
|
||||||
Name: "JSON encoder cannot marshal math.Inf(1)",
|
|
||||||
Attributes: []attribute.KeyValue{attribute.Float64("", math.Inf(1))},
|
|
||||||
},
|
|
||||||
{Name: "/bar"},
|
|
||||||
}.Snapshots())
|
|
||||||
require.Error(t, err)
|
|
||||||
},
|
|
||||||
assertMetrics: func(t *testing.T, rm metricdata.ResourceMetrics) {
|
|
||||||
t.Helper()
|
|
||||||
require.Len(t, rm.ScopeMetrics, 1)
|
|
||||||
|
|
||||||
sm := rm.ScopeMetrics[0]
|
|
||||||
require.Len(t, sm.Metrics, 3)
|
|
||||||
|
|
||||||
assert.Equal(t, instrumentation.Scope{
|
|
||||||
Name: "go.opentelemetry.io/otel/exporters/stdout/stdouttrace",
|
|
||||||
Version: sdk.Version(),
|
|
||||||
SchemaURL: semconv.SchemaURL,
|
|
||||||
}, sm.Scope)
|
|
||||||
|
|
||||||
metricdatatest.AssertEqual(t, metricdata.Metrics{
|
|
||||||
Name: otelconv.SDKExporterSpanInflight{}.Name(),
|
|
||||||
Description: otelconv.SDKExporterSpanInflight{}.Description(),
|
|
||||||
Unit: otelconv.SDKExporterSpanInflight{}.Unit(),
|
|
||||||
Data: metricdata.Sum[int64]{
|
|
||||||
Temporality: metricdata.CumulativeTemporality,
|
|
||||||
DataPoints: []metricdata.DataPoint[int64]{
|
|
||||||
{
|
|
||||||
Attributes: attribute.NewSet(
|
|
||||||
semconv.OTelComponentName(
|
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter/0",
|
|
||||||
),
|
|
||||||
semconv.OTelComponentTypeKey.String(
|
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Value: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, sm.Metrics[0], metricdatatest.IgnoreTimestamp())
|
|
||||||
|
|
||||||
require.IsType(t, metricdata.Sum[int64]{}, sm.Metrics[1].Data)
|
|
||||||
sum := sm.Metrics[1].Data.(metricdata.Sum[int64])
|
|
||||||
var found bool
|
|
||||||
for i := range sum.DataPoints {
|
|
||||||
sum.DataPoints[i].Attributes, _ = sum.DataPoints[i].Attributes.Filter(
|
|
||||||
func(kv attribute.KeyValue) bool {
|
|
||||||
if kv.Key == semconv.ErrorTypeKey {
|
|
||||||
found = true
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
assert.True(t, found, "missing error type attribute in span export metric")
|
|
||||||
sm.Metrics[1].Data = sum
|
|
||||||
|
|
||||||
metricdatatest.AssertEqual(t, metricdata.Metrics{
|
|
||||||
Name: otelconv.SDKExporterSpanExported{}.Name(),
|
|
||||||
Description: otelconv.SDKExporterSpanExported{}.Description(),
|
|
||||||
Unit: otelconv.SDKExporterSpanExported{}.Unit(),
|
|
||||||
Data: metricdata.Sum[int64]{
|
|
||||||
Temporality: metricdata.CumulativeTemporality,
|
|
||||||
IsMonotonic: true,
|
|
||||||
DataPoints: []metricdata.DataPoint[int64]{
|
|
||||||
{
|
|
||||||
Attributes: attribute.NewSet(
|
|
||||||
semconv.OTelComponentName(
|
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter/0",
|
|
||||||
),
|
|
||||||
semconv.OTelComponentTypeKey.String(
|
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Value: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Attributes: attribute.NewSet(
|
|
||||||
semconv.OTelComponentName(
|
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter/0",
|
|
||||||
),
|
|
||||||
semconv.OTelComponentTypeKey.String(
|
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Value: 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, sm.Metrics[1], metricdatatest.IgnoreTimestamp())
|
|
||||||
|
|
||||||
require.IsType(t, metricdata.Histogram[float64]{}, sm.Metrics[2].Data)
|
|
||||||
hist := sm.Metrics[2].Data.(metricdata.Histogram[float64])
|
|
||||||
require.Len(t, hist.DataPoints, 1)
|
|
||||||
found = false
|
|
||||||
hist.DataPoints[0].Attributes, _ = hist.DataPoints[0].Attributes.Filter(
|
|
||||||
func(kv attribute.KeyValue) bool {
|
|
||||||
if kv.Key == semconv.ErrorTypeKey {
|
|
||||||
found = true
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
)
|
|
||||||
assert.True(t, found, "missing error type attribute in operation duration metric")
|
|
||||||
sm.Metrics[2].Data = hist
|
|
||||||
|
|
||||||
metricdatatest.AssertEqual(t, metricdata.Metrics{
|
|
||||||
Name: otelconv.SDKExporterOperationDuration{}.Name(),
|
|
||||||
Description: otelconv.SDKExporterOperationDuration{}.Description(),
|
|
||||||
Unit: otelconv.SDKExporterOperationDuration{}.Unit(),
|
|
||||||
Data: metricdata.Histogram[float64]{
|
|
||||||
Temporality: metricdata.CumulativeTemporality,
|
|
||||||
DataPoints: []metricdata.HistogramDataPoint[float64]{
|
|
||||||
{
|
|
||||||
Attributes: attribute.NewSet(
|
|
||||||
semconv.OTelComponentName(
|
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter/0",
|
|
||||||
),
|
|
||||||
semconv.OTelComponentTypeKey.String(
|
|
||||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter",
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -625,49 +365,6 @@ func TestSelfObservability(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type errMeterProvider struct {
|
|
||||||
mapi.MeterProvider
|
|
||||||
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *errMeterProvider) Meter(string, ...mapi.MeterOption) mapi.Meter {
|
|
||||||
return &errMeter{err: m.err}
|
|
||||||
}
|
|
||||||
|
|
||||||
type errMeter struct {
|
|
||||||
mapi.Meter
|
|
||||||
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *errMeter) Int64UpDownCounter(string, ...mapi.Int64UpDownCounterOption) (mapi.Int64UpDownCounter, error) {
|
|
||||||
return nil, m.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *errMeter) Int64Counter(string, ...mapi.Int64CounterOption) (mapi.Int64Counter, error) {
|
|
||||||
return nil, m.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *errMeter) Float64Histogram(string, ...mapi.Float64HistogramOption) (mapi.Float64Histogram, error) {
|
|
||||||
return nil, m.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSelfObservabilityInstrumentErrors(t *testing.T) {
|
|
||||||
orig := otel.GetMeterProvider()
|
|
||||||
t.Cleanup(func() { otel.SetMeterProvider(orig) })
|
|
||||||
mp := &errMeterProvider{err: assert.AnError}
|
|
||||||
otel.SetMeterProvider(mp)
|
|
||||||
|
|
||||||
t.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", "true")
|
|
||||||
_, err := stdouttrace.New()
|
|
||||||
require.ErrorIs(t, err, assert.AnError, "new instrument errors")
|
|
||||||
|
|
||||||
assert.ErrorContains(t, err, "inflight metric")
|
|
||||||
assert.ErrorContains(t, err, "span exported metric")
|
|
||||||
assert.ErrorContains(t, err, "operation duration metric")
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkExporterExportSpans(b *testing.B) {
|
func BenchmarkExporterExportSpans(b *testing.B) {
|
||||||
ss := tracetest.SpanStubs{
|
ss := tracetest.SpanStubs{
|
||||||
{Name: "/foo"},
|
{Name: "/foo"},
|
||||||
|
|||||||
@@ -42,3 +42,7 @@ module-sets:
|
|||||||
excluded-modules:
|
excluded-modules:
|
||||||
- go.opentelemetry.io/otel/internal/tools
|
- go.opentelemetry.io/otel/internal/tools
|
||||||
- go.opentelemetry.io/otel/trace/internal/telemetry/test
|
- go.opentelemetry.io/otel/trace/internal/telemetry/test
|
||||||
|
modules:
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace:
|
||||||
|
version-refs:
|
||||||
|
- ./exporters/stdout/stdouttrace/internal/version.go
|
||||||
|
|||||||
Reference in New Issue
Block a user