1// Copyright The OpenTelemetry Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14package exporterhelper 15 16import ( 17 "context" 18 "errors" 19 "testing" 20 21 "github.com/stretchr/testify/assert" 22 "github.com/stretchr/testify/require" 23 "go.opentelemetry.io/otel" 24 "go.opentelemetry.io/otel/attribute" 25 "go.opentelemetry.io/otel/oteltest" 26 "go.opentelemetry.io/otel/trace" 27 28 "go.opentelemetry.io/collector/component" 29 "go.opentelemetry.io/collector/component/componenttest" 30 "go.opentelemetry.io/collector/config" 31 "go.opentelemetry.io/collector/consumer" 32 "go.opentelemetry.io/collector/consumer/consumererror" 33 "go.opentelemetry.io/collector/consumer/consumerhelper" 34 "go.opentelemetry.io/collector/internal/obsreportconfig/obsmetrics" 35 "go.opentelemetry.io/collector/internal/testdata" 36 "go.opentelemetry.io/collector/model/pdata" 37 "go.opentelemetry.io/collector/obsreport/obsreporttest" 38) 39 40const ( 41 fakeTraceParentSpanName = "fake_trace_parent_span_name" 42) 43 44var ( 45 fakeTracesExporterName = config.NewIDWithName("fake_traces_exporter", "with_name") 46 fakeTracesExporterConfig = config.NewExporterSettings(fakeTracesExporterName) 47) 48 49func TestTracesRequest(t *testing.T) { 50 mr := newTracesRequest(context.Background(), testdata.GenerateTracesOneSpan(), nil) 51 52 traceErr := consumererror.NewTraces(errors.New("some error"), pdata.NewTraces()) 53 assert.EqualValues(t, newTracesRequest(context.Background(), pdata.NewTraces(), nil), mr.onError(traceErr)) 54} 55 56func TestTracesExporter_InvalidName(t *testing.T) { 57 te, err := NewTracesExporter(nil, componenttest.NewNopExporterCreateSettings(), newTraceDataPusher(nil)) 58 require.Nil(t, te) 59 require.Equal(t, errNilConfig, err) 60} 61 62func TestTracesExporter_NilLogger(t *testing.T) { 63 te, err := NewTracesExporter(&fakeTracesExporterConfig, component.ExporterCreateSettings{}, newTraceDataPusher(nil)) 64 require.Nil(t, te) 65 require.Equal(t, errNilLogger, err) 66} 67 68func TestTracesExporter_NilPushTraceData(t *testing.T) { 69 te, err := NewTracesExporter(&fakeTracesExporterConfig, componenttest.NewNopExporterCreateSettings(), nil) 70 require.Nil(t, te) 71 require.Equal(t, errNilPushTraceData, err) 72} 73 74func TestTracesExporter_Default(t *testing.T) { 75 td := pdata.NewTraces() 76 te, err := NewTracesExporter(&fakeTracesExporterConfig, componenttest.NewNopExporterCreateSettings(), newTraceDataPusher(nil)) 77 assert.NotNil(t, te) 78 assert.NoError(t, err) 79 80 assert.Equal(t, consumer.Capabilities{MutatesData: false}, te.Capabilities()) 81 assert.NoError(t, te.Start(context.Background(), componenttest.NewNopHost())) 82 assert.NoError(t, te.ConsumeTraces(context.Background(), td)) 83 assert.NoError(t, te.Shutdown(context.Background())) 84} 85 86func TestTracesExporter_WithCapabilities(t *testing.T) { 87 capabilities := consumer.Capabilities{MutatesData: true} 88 te, err := NewTracesExporter(&fakeTracesExporterConfig, componenttest.NewNopExporterCreateSettings(), newTraceDataPusher(nil), WithCapabilities(capabilities)) 89 assert.NotNil(t, te) 90 assert.NoError(t, err) 91 92 assert.Equal(t, capabilities, te.Capabilities()) 93} 94 95func TestTracesExporter_Default_ReturnError(t *testing.T) { 96 td := pdata.NewTraces() 97 want := errors.New("my_error") 98 te, err := NewTracesExporter(&fakeTracesExporterConfig, componenttest.NewNopExporterCreateSettings(), newTraceDataPusher(want)) 99 require.NoError(t, err) 100 require.NotNil(t, te) 101 102 err = te.ConsumeTraces(context.Background(), td) 103 require.Equal(t, want, err) 104} 105 106func TestTracesExporter_WithRecordMetrics(t *testing.T) { 107 te, err := NewTracesExporter(&fakeTracesExporterConfig, componenttest.NewNopExporterCreateSettings(), newTraceDataPusher(nil)) 108 require.NoError(t, err) 109 require.NotNil(t, te) 110 111 checkRecordedMetricsForTracesExporter(t, te, nil) 112} 113 114func TestTracesExporter_WithRecordMetrics_ReturnError(t *testing.T) { 115 want := errors.New("my_error") 116 te, err := NewTracesExporter(&fakeTracesExporterConfig, componenttest.NewNopExporterCreateSettings(), newTraceDataPusher(want)) 117 require.NoError(t, err) 118 require.NotNil(t, te) 119 120 checkRecordedMetricsForTracesExporter(t, te, want) 121} 122 123func TestTracesExporter_WithRecordEnqueueFailedMetrics(t *testing.T) { 124 doneFn, err := obsreporttest.SetupRecordedMetricsTest() 125 require.NoError(t, err) 126 defer doneFn() 127 128 rCfg := DefaultRetrySettings() 129 qCfg := DefaultQueueSettings() 130 qCfg.NumConsumers = 1 131 qCfg.QueueSize = 2 132 wantErr := errors.New("some-error") 133 te, err := NewTracesExporter(&fakeTracesExporterConfig, componenttest.NewNopExporterCreateSettings(), newTraceDataPusher(wantErr), WithRetry(rCfg), WithQueue(qCfg)) 134 require.NoError(t, err) 135 require.NotNil(t, te) 136 137 td := testdata.GenerateTracesTwoSpansSameResource() 138 const numBatches = 7 139 for i := 0; i < numBatches; i++ { 140 te.ConsumeTraces(context.Background(), td) 141 } 142 143 // 2 batched must be in queue, and 5 batches (10 spans) rejected due to queue overflow 144 checkExporterEnqueueFailedTracesStats(t, fakeTracesExporterName, int64(10)) 145} 146 147func TestTracesExporter_WithSpan(t *testing.T) { 148 sr := new(oteltest.SpanRecorder) 149 tp := oteltest.NewTracerProvider(oteltest.WithSpanRecorder(sr)) 150 otel.SetTracerProvider(tp) 151 defer otel.SetTracerProvider(trace.NewNoopTracerProvider()) 152 153 te, err := NewTracesExporter(&fakeTracesExporterConfig, componenttest.NewNopExporterCreateSettings(), newTraceDataPusher(nil)) 154 require.NoError(t, err) 155 require.NotNil(t, te) 156 157 checkWrapSpanForTracesExporter(t, sr, tp.Tracer("test"), te, nil, 1) 158} 159 160func TestTracesExporter_WithSpan_ReturnError(t *testing.T) { 161 sr := new(oteltest.SpanRecorder) 162 tp := oteltest.NewTracerProvider(oteltest.WithSpanRecorder(sr)) 163 otel.SetTracerProvider(tp) 164 defer otel.SetTracerProvider(trace.NewNoopTracerProvider()) 165 166 want := errors.New("my_error") 167 te, err := NewTracesExporter(&fakeTracesExporterConfig, componenttest.NewNopExporterCreateSettings(), newTraceDataPusher(want)) 168 require.NoError(t, err) 169 require.NotNil(t, te) 170 171 checkWrapSpanForTracesExporter(t, sr, tp.Tracer("test"), te, want, 1) 172} 173 174func TestTracesExporter_WithShutdown(t *testing.T) { 175 shutdownCalled := false 176 shutdown := func(context.Context) error { shutdownCalled = true; return nil } 177 178 te, err := NewTracesExporter(&fakeTracesExporterConfig, componenttest.NewNopExporterCreateSettings(), newTraceDataPusher(nil), WithShutdown(shutdown)) 179 assert.NotNil(t, te) 180 assert.NoError(t, err) 181 182 assert.NoError(t, te.Start(context.Background(), componenttest.NewNopHost())) 183 assert.NoError(t, te.Shutdown(context.Background())) 184 assert.True(t, shutdownCalled) 185} 186 187func TestTracesExporter_WithShutdown_ReturnError(t *testing.T) { 188 want := errors.New("my_error") 189 shutdownErr := func(context.Context) error { return want } 190 191 te, err := NewTracesExporter(&fakeTracesExporterConfig, componenttest.NewNopExporterCreateSettings(), newTraceDataPusher(nil), WithShutdown(shutdownErr)) 192 assert.NotNil(t, te) 193 assert.NoError(t, err) 194 195 assert.NoError(t, te.Start(context.Background(), componenttest.NewNopHost())) 196 assert.Equal(t, te.Shutdown(context.Background()), want) 197} 198 199func newTraceDataPusher(retError error) consumerhelper.ConsumeTracesFunc { 200 return func(ctx context.Context, td pdata.Traces) error { 201 return retError 202 } 203} 204 205func checkRecordedMetricsForTracesExporter(t *testing.T, te component.TracesExporter, wantError error) { 206 doneFn, err := obsreporttest.SetupRecordedMetricsTest() 207 require.NoError(t, err) 208 defer doneFn() 209 210 td := testdata.GenerateTracesTwoSpansSameResource() 211 const numBatches = 7 212 for i := 0; i < numBatches; i++ { 213 require.Equal(t, wantError, te.ConsumeTraces(context.Background(), td)) 214 } 215 216 // TODO: When the new metrics correctly count partial dropped fix this. 217 if wantError != nil { 218 obsreporttest.CheckExporterTraces(t, fakeTracesExporterName, 0, int64(numBatches*td.SpanCount())) 219 } else { 220 obsreporttest.CheckExporterTraces(t, fakeTracesExporterName, int64(numBatches*td.SpanCount()), 0) 221 } 222} 223 224func generateTraceTraffic(t *testing.T, tracer trace.Tracer, te component.TracesExporter, numRequests int, wantError error) { 225 td := pdata.NewTraces() 226 td.ResourceSpans().AppendEmpty().InstrumentationLibrarySpans().AppendEmpty().Spans().AppendEmpty() 227 ctx, span := tracer.Start(context.Background(), fakeTraceParentSpanName) 228 defer span.End() 229 for i := 0; i < numRequests; i++ { 230 require.Equal(t, wantError, te.ConsumeTraces(ctx, td)) 231 } 232} 233 234func checkWrapSpanForTracesExporter(t *testing.T, sr *oteltest.SpanRecorder, tracer trace.Tracer, te component.TracesExporter, wantError error, numSpans int64) { 235 const numRequests = 5 236 generateTraceTraffic(t, tracer, te, numRequests, wantError) 237 238 // Inspection time! 239 gotSpanData := sr.Completed() 240 require.Equal(t, numRequests+1, len(gotSpanData)) 241 242 parentSpan := gotSpanData[numRequests] 243 require.Equalf(t, fakeTraceParentSpanName, parentSpan.Name(), "SpanData %v", parentSpan) 244 245 for _, sd := range gotSpanData[:numRequests] { 246 require.Equalf(t, parentSpan.SpanContext().SpanID(), sd.ParentSpanID(), "Exporter span not a child\nSpanData %v", sd) 247 checkStatus(t, sd, wantError) 248 249 sentSpans := numSpans 250 var failedToSendSpans int64 251 if wantError != nil { 252 sentSpans = 0 253 failedToSendSpans = numSpans 254 } 255 256 require.Equalf(t, attribute.Int64Value(sentSpans), sd.Attributes()[obsmetrics.SentSpansKey], "SpanData %v", sd) 257 require.Equalf(t, attribute.Int64Value(failedToSendSpans), sd.Attributes()[obsmetrics.FailedToSendSpansKey], "SpanData %v", sd) 258 } 259} 260