// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package obsconsumer_test import ( "context" "errors" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/collector/consumer" "go.opentelemetry.io/collector/consumer/consumererror" "go.opentelemetry.io/collector/consumer/consumertest" "go.opentelemetry.io/collector/pdata/ptrace" "go.opentelemetry.io/collector/service/internal/obsconsumer" ) type mockTracesConsumer struct { err error capabilities consumer.Capabilities } func (m *mockTracesConsumer) ConsumeTraces(_ context.Context, _ ptrace.Traces) error { return m.err } func (m *mockTracesConsumer) Capabilities() consumer.Capabilities { return m.capabilities } func TestTracesNopWhenGateDisabled(t *testing.T) { setGateForTest(t, false) mp := sdkmetric.NewMeterProvider() meter := mp.Meter("test") itemCounter, err := meter.Int64Counter("item_counter") require.NoError(t, err) sizeCounter, err := meter.Int64Counter("size_counter") require.NoError(t, err) cons := consumertest.NewNop() require.Equal(t, cons, obsconsumer.NewTraces(cons, itemCounter, sizeCounter)) } func TestTracesItemsOnly(t *testing.T) { setGateForTest(t, true) ctx := context.Background() mockConsumer := &mockTracesConsumer{} reader := sdkmetric.NewManualReader() mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) meter := mp.Meter("test") itemCounter, err := meter.Int64Counter("item_counter") require.NoError(t, err) sizeCounter, err := meter.Int64Counter("size_counter") require.NoError(t, err) sizeCounterDisabled := newDisabledCounter(sizeCounter) consumer := obsconsumer.NewTraces(mockConsumer, itemCounter, sizeCounterDisabled) td := ptrace.NewTraces() r := td.ResourceSpans().AppendEmpty() ss := r.ScopeSpans().AppendEmpty() ss.Spans().AppendEmpty() err = consumer.ConsumeTraces(ctx, td) require.NoError(t, err) var metrics metricdata.ResourceMetrics err = reader.Collect(ctx, &metrics) require.NoError(t, err) require.Len(t, metrics.ScopeMetrics, 1) require.Len(t, metrics.ScopeMetrics[0].Metrics, 1) metric := metrics.ScopeMetrics[0].Metrics[0] require.Equal(t, "item_counter", metric.Name) data := metric.Data.(metricdata.Sum[int64]) require.Len(t, data.DataPoints, 1) require.Equal(t, int64(1), data.DataPoints[0].Value) attrs := data.DataPoints[0].Attributes require.Equal(t, 1, attrs.Len()) val, ok := attrs.Value(attribute.Key(obsconsumer.ComponentOutcome)) require.True(t, ok) require.Equal(t, "success", val.Emit()) } func TestTracesConsumeSuccess(t *testing.T) { setGateForTest(t, true) ctx := context.Background() mockConsumer := &mockTracesConsumer{} reader := sdkmetric.NewManualReader() mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) meter := mp.Meter("test") itemCounter, err := meter.Int64Counter("item_counter") require.NoError(t, err) sizeCounter, err := meter.Int64Counter("size_counter") require.NoError(t, err) consumer := obsconsumer.NewTraces(mockConsumer, itemCounter, sizeCounter) td := ptrace.NewTraces() r := td.ResourceSpans().AppendEmpty() ss := r.ScopeSpans().AppendEmpty() ss.Spans().AppendEmpty() err = consumer.ConsumeTraces(ctx, td) require.NoError(t, err) var rm metricdata.ResourceMetrics err = reader.Collect(ctx, &rm) require.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) require.Len(t, rm.ScopeMetrics[0].Metrics, 2) var itemMetric, sizeMetric metricdata.Metrics for _, m := range rm.ScopeMetrics[0].Metrics { switch m.Name { case "item_counter": itemMetric = m case "size_counter": sizeMetric = m } } require.NotNil(t, itemMetric) require.NotNil(t, sizeMetric) itemData := itemMetric.Data.(metricdata.Sum[int64]) require.Len(t, itemData.DataPoints, 1) require.Equal(t, int64(1), itemData.DataPoints[0].Value) itemAttrs := itemData.DataPoints[0].Attributes require.Equal(t, 1, itemAttrs.Len()) val, ok := itemAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome)) require.True(t, ok) require.Equal(t, "success", val.Emit()) sizeData := sizeMetric.Data.(metricdata.Sum[int64]) require.Len(t, sizeData.DataPoints, 1) require.Positive(t, sizeData.DataPoints[0].Value) sizeAttrs := sizeData.DataPoints[0].Attributes require.Equal(t, 1, sizeAttrs.Len()) val, ok = sizeAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome)) require.True(t, ok) require.Equal(t, "success", val.Emit()) } func TestTracesConsumeFailure(t *testing.T) { setGateForTest(t, true) ctx := context.Background() expectedErr := errors.New("test error") downstreamErr := consumererror.NewDownstream(expectedErr) mockConsumer := &mockTracesConsumer{err: expectedErr} reader := sdkmetric.NewManualReader() mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) meter := mp.Meter("test") itemCounter, err := meter.Int64Counter("item_counter") require.NoError(t, err) sizeCounter, err := meter.Int64Counter("size_counter") require.NoError(t, err) consumer := obsconsumer.NewTraces(mockConsumer, itemCounter, sizeCounter) td := ptrace.NewTraces() r := td.ResourceSpans().AppendEmpty() ss := r.ScopeSpans().AppendEmpty() ss.Spans().AppendEmpty() err = consumer.ConsumeTraces(ctx, td) assert.Equal(t, downstreamErr, err) var rm metricdata.ResourceMetrics err = reader.Collect(ctx, &rm) require.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) require.Len(t, rm.ScopeMetrics[0].Metrics, 2) var itemMetric, sizeMetric metricdata.Metrics for _, m := range rm.ScopeMetrics[0].Metrics { switch m.Name { case "item_counter": itemMetric = m case "size_counter": sizeMetric = m } } require.NotNil(t, itemMetric) require.NotNil(t, sizeMetric) itemData := itemMetric.Data.(metricdata.Sum[int64]) require.Len(t, itemData.DataPoints, 1) require.Equal(t, int64(1), itemData.DataPoints[0].Value) itemAttrs := itemData.DataPoints[0].Attributes require.Equal(t, 1, itemAttrs.Len()) val, ok := itemAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome)) require.True(t, ok) require.Equal(t, "failure", val.Emit()) sizeData := sizeMetric.Data.(metricdata.Sum[int64]) require.Len(t, sizeData.DataPoints, 1) require.Positive(t, sizeData.DataPoints[0].Value) sizeAttrs := sizeData.DataPoints[0].Attributes require.Equal(t, 1, sizeAttrs.Len()) val, ok = sizeAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome)) require.True(t, ok) require.Equal(t, "failure", val.Emit()) } func TestTracesWithStaticAttributes(t *testing.T) { setGateForTest(t, true) ctx := context.Background() mockConsumer := &mockTracesConsumer{} reader := sdkmetric.NewManualReader() mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) meter := mp.Meter("test") itemCounter, err := meter.Int64Counter("item_counter") require.NoError(t, err) sizeCounter, err := meter.Int64Counter("size_counter") require.NoError(t, err) staticAttr := attribute.String("test", "value") consumer := obsconsumer.NewTraces(mockConsumer, itemCounter, sizeCounter, obsconsumer.WithStaticDataPointAttribute(staticAttr)) td := ptrace.NewTraces() r := td.ResourceSpans().AppendEmpty() ss := r.ScopeSpans().AppendEmpty() ss.Spans().AppendEmpty() err = consumer.ConsumeTraces(ctx, td) require.NoError(t, err) var rm metricdata.ResourceMetrics err = reader.Collect(ctx, &rm) require.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) require.Len(t, rm.ScopeMetrics[0].Metrics, 2) var itemMetric, sizeMetric metricdata.Metrics for _, m := range rm.ScopeMetrics[0].Metrics { switch m.Name { case "item_counter": itemMetric = m case "size_counter": sizeMetric = m } } require.NotNil(t, itemMetric) require.NotNil(t, sizeMetric) itemData := itemMetric.Data.(metricdata.Sum[int64]) require.Len(t, itemData.DataPoints, 1) require.Equal(t, int64(1), itemData.DataPoints[0].Value) itemAttrs := itemData.DataPoints[0].Attributes require.Equal(t, 2, itemAttrs.Len()) val, ok := itemAttrs.Value(attribute.Key("test")) require.True(t, ok) require.Equal(t, "value", val.Emit()) val, ok = itemAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome)) require.True(t, ok) require.Equal(t, "success", val.Emit()) sizeData := sizeMetric.Data.(metricdata.Sum[int64]) require.Len(t, sizeData.DataPoints, 1) require.Positive(t, sizeData.DataPoints[0].Value) sizeAttrs := sizeData.DataPoints[0].Attributes require.Equal(t, 2, sizeAttrs.Len()) val, ok = sizeAttrs.Value(attribute.Key("test")) require.True(t, ok) require.Equal(t, "value", val.Emit()) val, ok = sizeAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome)) require.True(t, ok) require.Equal(t, "success", val.Emit()) } func TestTracesMultipleItemsMixedOutcomes(t *testing.T) { setGateForTest(t, true) ctx := context.Background() expectedErr := errors.New("test error") downstreamErr := consumererror.NewDownstream(expectedErr) mockConsumer := &mockTracesConsumer{} reader := sdkmetric.NewManualReader() mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) meter := mp.Meter("test") itemCounter, err := meter.Int64Counter("item_counter") require.NoError(t, err) sizeCounter, err := meter.Int64Counter("size_counter") require.NoError(t, err) consumer := obsconsumer.NewTraces(mockConsumer, itemCounter, sizeCounter) // First batch: 2 successful items td1 := ptrace.NewTraces() for range 2 { r := td1.ResourceSpans().AppendEmpty() ss := r.ScopeSpans().AppendEmpty() ss.Spans().AppendEmpty() } err = consumer.ConsumeTraces(ctx, td1) require.NoError(t, err) // Second batch: 1 failed item mockConsumer.err = expectedErr td2 := ptrace.NewTraces() r := td2.ResourceSpans().AppendEmpty() ss := r.ScopeSpans().AppendEmpty() ss.Spans().AppendEmpty() err = consumer.ConsumeTraces(ctx, td2) assert.Equal(t, downstreamErr, err) // Third batch: 2 successful items mockConsumer.err = nil td3 := ptrace.NewTraces() for range 2 { r = td3.ResourceSpans().AppendEmpty() ss = r.ScopeSpans().AppendEmpty() ss.Spans().AppendEmpty() } err = consumer.ConsumeTraces(ctx, td3) require.NoError(t, err) // Fourth batch: 1 failed item mockConsumer.err = expectedErr td4 := ptrace.NewTraces() r = td4.ResourceSpans().AppendEmpty() ss = r.ScopeSpans().AppendEmpty() ss.Spans().AppendEmpty() err = consumer.ConsumeTraces(ctx, td4) assert.Equal(t, downstreamErr, err) var rm metricdata.ResourceMetrics err = reader.Collect(ctx, &rm) require.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) require.Len(t, rm.ScopeMetrics[0].Metrics, 2) var itemMetric, sizeMetric metricdata.Metrics for _, m := range rm.ScopeMetrics[0].Metrics { switch m.Name { case "item_counter": itemMetric = m case "size_counter": sizeMetric = m } } require.NotNil(t, itemMetric) require.NotNil(t, sizeMetric) itemData := itemMetric.Data.(metricdata.Sum[int64]) require.Len(t, itemData.DataPoints, 2) sizeData := sizeMetric.Data.(metricdata.Sum[int64]) require.Len(t, sizeData.DataPoints, 2) // Find success and failure data points var successDP, failureDP metricdata.DataPoint[int64] for _, dp := range itemData.DataPoints { val, ok := dp.Attributes.Value(attribute.Key(obsconsumer.ComponentOutcome)) if ok && val.Emit() == "success" { successDP = dp } else { failureDP = dp } } require.Equal(t, int64(4), successDP.Value) require.Equal(t, int64(2), failureDP.Value) var successSizeDP, failureSizeDP metricdata.DataPoint[int64] for _, dp := range sizeData.DataPoints { val, ok := dp.Attributes.Value(attribute.Key(obsconsumer.ComponentOutcome)) if ok && val.Emit() == "success" { successSizeDP = dp } else { failureSizeDP = dp } } require.Equal(t, int64(72), successSizeDP.Value) require.Equal(t, int64(36), failureSizeDP.Value) } func TestTracesCapabilities(t *testing.T) { setGateForTest(t, true) mockConsumer := &mockTracesConsumer{ capabilities: consumer.Capabilities{MutatesData: true}, } reader := sdkmetric.NewManualReader() mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) meter := mp.Meter("test") itemCounter, err := meter.Int64Counter("item_counter") require.NoError(t, err) sizeCounter, err := meter.Int64Counter("size_counter") require.NoError(t, err) sizeCounterDisabled := newDisabledCounter(sizeCounter) // Test with item counter only consumer := obsconsumer.NewTraces(mockConsumer, itemCounter, sizeCounterDisabled) require.Equal(t, consumer.Capabilities(), mockConsumer.capabilities) // Test with both counters consumer = obsconsumer.NewTraces(mockConsumer, itemCounter, sizeCounter) require.Equal(t, consumer.Capabilities(), mockConsumer.capabilities) }