You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2026-06-19 21:45:50 +02:00
fix(stdouttrace/observ): record error.type on opDuration when exportedSpans is disabled (#8432)
When `ExportOp.End` is called with a non-nil error and `exportedSpans` metric is disabled but `opDuration` is enabled, the `error.type` attribute is not included in the duration histogram record. --------- Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>
This commit is contained in:
@@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
### Fixed
|
||||
|
||||
- Interpret HTTP `Retry-After` header values as seconds instead of nanoseconds when retrying OTLP HTTP exports in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`, `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`, `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#8383)
|
||||
- Fix `go.opentelemetry.io/otel/exporters/stdout/stdouttrace` self-observability to record `error.type` on operation duration histogram when the `exportedSpans` metric is disabled. (#8432)
|
||||
|
||||
<!-- Released section -->
|
||||
<!-- Don't change this section unless doing release -->
|
||||
|
||||
@@ -205,7 +205,7 @@ func (e ExportOp) End(success int64, err error) {
|
||||
}
|
||||
|
||||
mOpt := e.inst.setOpt
|
||||
if err != nil && exportedSpansEnable {
|
||||
if err != nil && (exportedSpansEnable || opDurationEnable) {
|
||||
attrs := get[attribute.KeyValue](measureAttrsPool)
|
||||
defer put(measureAttrsPool, attrs)
|
||||
*attrs = append(*attrs, e.inst.attrs...)
|
||||
@@ -216,10 +216,11 @@ func (e ExportOp) End(success int64, err error) {
|
||||
set := attribute.NewSet(*attrs...)
|
||||
mOpt = metric.WithAttributeSet(set)
|
||||
|
||||
// Reset addOpt with new attribute set.
|
||||
*addOpt = append((*addOpt)[:0], mOpt)
|
||||
|
||||
e.inst.exportedSpans.Add(e.ctx, e.nSpans-success, *addOpt...)
|
||||
if exportedSpansEnable {
|
||||
// Reset addOpt with new attribute set.
|
||||
*addOpt = append((*addOpt)[:0], mOpt)
|
||||
e.inst.exportedSpans.Add(e.ctx, e.nSpans-success, *addOpt...)
|
||||
}
|
||||
}
|
||||
|
||||
if opDurationEnable {
|
||||
|
||||
@@ -215,6 +215,101 @@ func TestInstrumentationExportSpansPartialErrored(t *testing.T) {
|
||||
assertMetrics(t, collect(), n, success, assert.AnError)
|
||||
}
|
||||
|
||||
func TestExportSpansErrorTypeRecorded(t *testing.T) {
|
||||
exportedSpansName := otelconv.SDKExporterSpanExported{}.Name()
|
||||
opDurationName := otelconv.SDKExporterOperationDuration{}.Name()
|
||||
inflightSpansName := otelconv.SDKExporterSpanInflight{}.Name()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
drop string
|
||||
errorOnExported bool
|
||||
errorOnOpDuration bool
|
||||
}{
|
||||
{
|
||||
name: "drop exportedSpans: opDuration still records error.type",
|
||||
drop: exportedSpansName,
|
||||
errorOnOpDuration: true,
|
||||
},
|
||||
{
|
||||
name: "drop opDuration: exportedSpans still records error.type",
|
||||
drop: opDurationName,
|
||||
errorOnExported: true,
|
||||
},
|
||||
{
|
||||
name: "drop inflightSpans: error.type still on exported and opDuration",
|
||||
drop: inflightSpansName,
|
||||
errorOnExported: true,
|
||||
errorOnOpDuration: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Setenv("OTEL_GO_X_OBSERVABILITY", "true")
|
||||
|
||||
original := otel.GetMeterProvider()
|
||||
t.Cleanup(func() { otel.SetMeterProvider(original) })
|
||||
|
||||
r := metric.NewManualReader()
|
||||
mp := metric.NewMeterProvider(
|
||||
metric.WithReader(r),
|
||||
metric.WithView(metric.NewView(
|
||||
metric.Instrument{Name: tt.drop},
|
||||
metric.Stream{Aggregation: metric.AggregationDrop{}},
|
||||
)),
|
||||
)
|
||||
otel.SetMeterProvider(mp)
|
||||
|
||||
inst, err := observ.NewInstrumentation(ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, inst)
|
||||
|
||||
const n = 10
|
||||
inst.ExportSpans(t.Context(), n).End(0, assert.AnError)
|
||||
|
||||
var rm metricdata.ResourceMetrics
|
||||
require.NoError(t, r.Collect(t.Context(), &rm))
|
||||
require.Len(t, rm.ScopeMetrics, 1)
|
||||
got := rm.ScopeMetrics[0]
|
||||
|
||||
assert.Equal(t, Scope, got.Scope, "unexpected scope")
|
||||
|
||||
o := metricdatatest.IgnoreTimestamp()
|
||||
seen := make(map[string]bool, 2)
|
||||
for _, m := range got.Metrics {
|
||||
seen[m.Name] = true
|
||||
switch m.Name {
|
||||
case exportedSpansName:
|
||||
var exportedErr error
|
||||
if tt.errorOnExported {
|
||||
exportedErr = assert.AnError
|
||||
}
|
||||
metricdatatest.AssertEqual(t, spanExported(0, n, exportedErr), m, o)
|
||||
case opDurationName:
|
||||
var opErr error
|
||||
if tt.errorOnOpDuration {
|
||||
opErr = assert.AnError
|
||||
}
|
||||
metricdatatest.AssertEqual(
|
||||
t, operationDuration(opErr), m, o, metricdatatest.IgnoreValue(),
|
||||
)
|
||||
case inflightSpansName:
|
||||
metricdatatest.AssertEqual(t, spanInflight(), m, o)
|
||||
}
|
||||
}
|
||||
// Verify the dropped instrument is absent and the other two are present.
|
||||
assert.False(t, seen[tt.drop], "expected %q to be absent", tt.drop)
|
||||
for _, name := range []string{exportedSpansName, opDurationName, inflightSpansName} {
|
||||
if name == tt.drop {
|
||||
continue
|
||||
}
|
||||
assert.True(t, seen[name], "expected %q to be present", name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkInstrumentationExportSpans(b *testing.B) {
|
||||
setup := func(b *testing.B) *observ.Instrumentation {
|
||||
b.Helper()
|
||||
|
||||
Reference in New Issue
Block a user