// 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/plog" "go.opentelemetry.io/collector/service/internal/obsconsumer" ) type mockLogsConsumer struct { err error capabilities consumer.Capabilities } func (m *mockLogsConsumer) ConsumeLogs(_ context.Context, _ plog.Logs) error { return m.err } func (m *mockLogsConsumer) Capabilities() consumer.Capabilities { return m.capabilities } func TestLogsNopWhenGateDisabled(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.NewLogs(cons, itemCounter, sizeCounter)) } func TestLogsItemsOnly(t *testing.T) { setGateForTest(t, true) ctx := context.Background() mockConsumer := &mockLogsConsumer{} 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.NewLogs(mockConsumer, itemCounter, sizeCounterDisabled) ld := plog.NewLogs() r := ld.ResourceLogs().AppendEmpty() sl := r.ScopeLogs().AppendEmpty() sl.LogRecords().AppendEmpty() err = consumer.ConsumeLogs(ctx, ld) 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, 1) metric := rm.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 TestLogsConsumeSuccess(t *testing.T) { setGateForTest(t, true) ctx := context.Background() mockConsumer := &mockLogsConsumer{} 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.NewLogs(mockConsumer, itemCounter, sizeCounter) ld := plog.NewLogs() r := ld.ResourceLogs().AppendEmpty() sl := r.ScopeLogs().AppendEmpty() sl.LogRecords().AppendEmpty() err = consumer.ConsumeLogs(ctx, ld) 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 TestLogsConsumeFailure(t *testing.T) { setGateForTest(t, true) ctx := context.Background() expectedErr := errors.New("test error") downstreamErr := consumererror.NewDownstream(expectedErr) mockConsumer := &mockLogsConsumer{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.NewLogs(mockConsumer, itemCounter, sizeCounter) ld := plog.NewLogs() r := ld.ResourceLogs().AppendEmpty() sl := r.ScopeLogs().AppendEmpty() sl.LogRecords().AppendEmpty() err = consumer.ConsumeLogs(ctx, ld) 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()) require.NotNil(t, sizeMetric) 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 TestLogsWithStaticAttributes(t *testing.T) { setGateForTest(t, true) ctx := context.Background() mockConsumer := &mockLogsConsumer{} 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.NewLogs(mockConsumer, itemCounter, sizeCounter, obsconsumer.WithStaticDataPointAttribute(staticAttr)) ld := plog.NewLogs() r := ld.ResourceLogs().AppendEmpty() sl := r.ScopeLogs().AppendEmpty() sl.LogRecords().AppendEmpty() err = consumer.ConsumeLogs(ctx, ld) 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) 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 TestLogsMultipleItemsMixedOutcomes(t *testing.T) { setGateForTest(t, true) ctx := context.Background() expectedErr := errors.New("test error") downstreamErr := consumererror.NewDownstream(expectedErr) mockConsumer := &mockLogsConsumer{} 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.NewLogs(mockConsumer, itemCounter, sizeCounter) // First batch: 2 successful items ld1 := plog.NewLogs() for range 2 { r := ld1.ResourceLogs().AppendEmpty() sl := r.ScopeLogs().AppendEmpty() sl.LogRecords().AppendEmpty() } err = consumer.ConsumeLogs(ctx, ld1) require.NoError(t, err) // Second batch: 1 failed item mockConsumer.err = expectedErr ld2 := plog.NewLogs() r := ld2.ResourceLogs().AppendEmpty() sl := r.ScopeLogs().AppendEmpty() sl.LogRecords().AppendEmpty() err = consumer.ConsumeLogs(ctx, ld2) assert.Equal(t, downstreamErr, err) // Third batch: 2 successful items mockConsumer.err = nil ld3 := plog.NewLogs() for range 2 { r = ld3.ResourceLogs().AppendEmpty() sl = r.ScopeLogs().AppendEmpty() sl.LogRecords().AppendEmpty() } err = consumer.ConsumeLogs(ctx, ld3) require.NoError(t, err) // Fourth batch: 1 failed item mockConsumer.err = expectedErr ld4 := plog.NewLogs() r = ld4.ResourceLogs().AppendEmpty() sl = r.ScopeLogs().AppendEmpty() sl.LogRecords().AppendEmpty() err = consumer.ConsumeLogs(ctx, ld4) 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) 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(64), successSizeDP.Value) require.Equal(t, int64(32), failureSizeDP.Value) } func TestLogsCapabilities(t *testing.T) { setGateForTest(t, true) mockConsumer := &mockLogsConsumer{ 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.NewLogs(mockConsumer, itemCounter, sizeCounterDisabled) require.Equal(t, consumer.Capabilities(), mockConsumer.capabilities) // Test with both counters consumer = obsconsumer.NewLogs(mockConsumer, itemCounter, sizeCounter) require.Equal(t, consumer.Capabilities(), mockConsumer.capabilities) }