diff --git a/.chloggen/obsreport-exporter.yaml b/.chloggen/obsreport-exporter.yaml new file mode 100644 index 00000000000..549243a5f29 --- /dev/null +++ b/.chloggen/obsreport-exporter.yaml @@ -0,0 +1,16 @@ +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver) +component: obsreport + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: "Instrument `obsreport.Exporter` metrics with otel-go" + +# One or more tracking issues or pull requests related to the change +issues: [6346] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: diff --git a/obsreport/obsreport_exporter.go b/obsreport/obsreport_exporter.go index 8a8bddfd6ae..c104153b2a9 100644 --- a/obsreport/obsreport_exporter.go +++ b/obsreport/obsreport_exporter.go @@ -20,20 +20,42 @@ import ( "go.opencensus.io/stats" "go.opencensus.io/tag" "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric/instrument" + "go.opentelemetry.io/otel/metric/instrument/syncint64" + "go.opentelemetry.io/otel/metric/unit" "go.opentelemetry.io/otel/trace" + "go.uber.org/zap" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config" "go.opentelemetry.io/collector/config/configtelemetry" + "go.opentelemetry.io/collector/featuregate" + "go.opentelemetry.io/collector/internal/obsreportconfig" "go.opentelemetry.io/collector/internal/obsreportconfig/obsmetrics" ) +const ( + exporterName = "exporter" + + exporterScope = scopeName + nameSep + exporterName +) + // Exporter is a helper to add observability to a component.Exporter. type Exporter struct { level configtelemetry.Level spanNamePrefix string mutators []tag.Mutator tracer trace.Tracer + logger *zap.Logger + + useOtelForMetrics bool + otelAttrs []attribute.KeyValue + sentSpans syncint64.Counter + failedToSendSpans syncint64.Counter + sentMetricPoints syncint64.Counter + failedToSendMetricPoints syncint64.Counter + sentLogRecords syncint64.Counter + failedToSendLogRecords syncint64.Counter } // ExporterSettings are settings for creating an Exporter. @@ -44,12 +66,75 @@ type ExporterSettings struct { // NewExporter creates a new Exporter. func NewExporter(cfg ExporterSettings) *Exporter { - return &Exporter{ + return newExporter(cfg, featuregate.GetRegistry()) +} + +func newExporter(cfg ExporterSettings, registry *featuregate.Registry) *Exporter { + exp := &Exporter{ level: cfg.ExporterCreateSettings.TelemetrySettings.MetricsLevel, spanNamePrefix: obsmetrics.ExporterPrefix + cfg.ExporterID.String(), mutators: []tag.Mutator{tag.Upsert(obsmetrics.TagKeyExporter, cfg.ExporterID.String(), tag.WithTTL(tag.TTLNoPropagation))}, tracer: cfg.ExporterCreateSettings.TracerProvider.Tracer(cfg.ExporterID.String()), + logger: cfg.ExporterCreateSettings.Logger, + + useOtelForMetrics: registry.IsEnabled(obsreportconfig.UseOtelForInternalMetricsfeatureGateID), + otelAttrs: []attribute.KeyValue{ + attribute.String(obsmetrics.ExporterKey, cfg.ExporterID.String()), + }, + } + + exp.createOtelMetrics(cfg) + + return exp +} + +func (exp *Exporter) createOtelMetrics(cfg ExporterSettings) { + if !exp.useOtelForMetrics { + return + } + meter := cfg.ExporterCreateSettings.MeterProvider.Meter(exporterScope) + + var err error + handleError := func(metricName string, err error) { + if err != nil { + exp.logger.Warn("failed to create otel instrument", zap.Error(err), zap.String("metric", metricName)) + } } + exp.sentSpans, err = meter.SyncInt64().Counter( + obsmetrics.ExporterPrefix+obsmetrics.SentSpansKey, + instrument.WithDescription("Number of spans successfully sent to destination."), + instrument.WithUnit(unit.Dimensionless)) + handleError(obsmetrics.ExporterPrefix+obsmetrics.SentSpansKey, err) + + exp.failedToSendSpans, err = meter.SyncInt64().Counter( + obsmetrics.ExporterPrefix+obsmetrics.FailedToSendSpansKey, + instrument.WithDescription("Number of spans in failed attempts to send to destination."), + instrument.WithUnit(unit.Dimensionless)) + handleError(obsmetrics.ExporterPrefix+obsmetrics.FailedToSendSpansKey, err) + + exp.sentMetricPoints, err = meter.SyncInt64().Counter( + obsmetrics.ExporterPrefix+obsmetrics.SentMetricPointsKey, + instrument.WithDescription("Number of metric points successfully sent to destination."), + instrument.WithUnit(unit.Dimensionless)) + handleError(obsmetrics.ExporterPrefix+obsmetrics.SentMetricPointsKey, err) + + exp.failedToSendMetricPoints, err = meter.SyncInt64().Counter( + obsmetrics.ExporterPrefix+obsmetrics.FailedToSendMetricPointsKey, + instrument.WithDescription("Number of metric points in failed attempts to send to destination."), + instrument.WithUnit(unit.Dimensionless)) + handleError(obsmetrics.ExporterPrefix+obsmetrics.FailedToSendMetricPointsKey, err) + + exp.sentLogRecords, err = meter.SyncInt64().Counter( + obsmetrics.ExporterPrefix+obsmetrics.SentLogRecordsKey, + instrument.WithDescription("Number of log record successfully sent to destination."), + instrument.WithUnit(unit.Dimensionless)) + handleError(obsmetrics.ExporterPrefix+obsmetrics.SentLogRecordsKey, err) + + exp.failedToSendLogRecords, err = meter.SyncInt64().Counter( + obsmetrics.ExporterPrefix+obsmetrics.FailedToSendLogRecordsKey, + instrument.WithDescription("Number of log records in failed attempts to send to destination."), + instrument.WithUnit(unit.Dimensionless)) + handleError(obsmetrics.ExporterPrefix+obsmetrics.FailedToSendLogRecordsKey, err) } // StartTracesOp is called at the start of an Export operation. @@ -62,7 +147,7 @@ func (exp *Exporter) StartTracesOp(ctx context.Context) context.Context { // EndTracesOp completes the export operation that was started with StartTracesOp. func (exp *Exporter) EndTracesOp(ctx context.Context, numSpans int, err error) { numSent, numFailedToSend := toNumItems(numSpans, err) - exp.recordMetrics(ctx, numSent, numFailedToSend, obsmetrics.ExporterSentSpans, obsmetrics.ExporterFailedToSendSpans) + exp.recordMetrics(ctx, config.TracesDataType, numSent, numFailedToSend) endSpan(ctx, err, numSent, numFailedToSend, obsmetrics.SentSpansKey, obsmetrics.FailedToSendSpansKey) } @@ -77,7 +162,7 @@ func (exp *Exporter) StartMetricsOp(ctx context.Context) context.Context { // StartMetricsOp. func (exp *Exporter) EndMetricsOp(ctx context.Context, numMetricPoints int, err error) { numSent, numFailedToSend := toNumItems(numMetricPoints, err) - exp.recordMetrics(ctx, numSent, numFailedToSend, obsmetrics.ExporterSentMetricPoints, obsmetrics.ExporterFailedToSendMetricPoints) + exp.recordMetrics(ctx, config.MetricsDataType, numSent, numFailedToSend) endSpan(ctx, err, numSent, numFailedToSend, obsmetrics.SentMetricPointsKey, obsmetrics.FailedToSendMetricPointsKey) } @@ -91,7 +176,7 @@ func (exp *Exporter) StartLogsOp(ctx context.Context) context.Context { // EndLogsOp completes the export operation that was started with StartLogsOp. func (exp *Exporter) EndLogsOp(ctx context.Context, numLogRecords int, err error) { numSent, numFailedToSend := toNumItems(numLogRecords, err) - exp.recordMetrics(ctx, numSent, numFailedToSend, obsmetrics.ExporterSentLogRecords, obsmetrics.ExporterFailedToSendLogRecords) + exp.recordMetrics(ctx, config.LogsDataType, numSent, numFailedToSend) endSpan(ctx, err, numSent, numFailedToSend, obsmetrics.SentLogRecordsKey, obsmetrics.FailedToSendLogRecordsKey) } @@ -103,16 +188,54 @@ func (exp *Exporter) startOp(ctx context.Context, operationSuffix string) contex return ctx } -func (exp *Exporter) recordMetrics(ctx context.Context, numSent, numFailedToSend int64, sentMeasure, failedToSendMeasure *stats.Int64Measure) { +func (exp *Exporter) recordMetrics(ctx context.Context, dataType config.DataType, numSent, numFailed int64) { if exp.level == configtelemetry.LevelNone { return } - // Ignore the error for now. This should not happen. - if numFailedToSend > 0 { - _ = stats.RecordWithTags(ctx, exp.mutators, sentMeasure.M(numSent), failedToSendMeasure.M(numFailedToSend)) + if exp.useOtelForMetrics { + exp.recordWithOtel(ctx, dataType, numSent, numFailed) } else { - _ = stats.RecordWithTags(ctx, exp.mutators, sentMeasure.M(numSent)) + exp.recordWithOC(ctx, dataType, numSent, numFailed) + } +} + +func (exp *Exporter) recordWithOtel(ctx context.Context, dataType config.DataType, sent int64, failed int64) { + var sentMeasure, failedMeasure syncint64.Counter + switch dataType { + case config.TracesDataType: + sentMeasure = exp.sentSpans + failedMeasure = exp.failedToSendSpans + case config.MetricsDataType: + sentMeasure = exp.sentMetricPoints + failedMeasure = exp.failedToSendMetricPoints + case config.LogsDataType: + sentMeasure = exp.sentLogRecords + failedMeasure = exp.failedToSendLogRecords } + + sentMeasure.Add(ctx, sent, exp.otelAttrs...) + failedMeasure.Add(ctx, failed, exp.otelAttrs...) +} + +func (exp *Exporter) recordWithOC(ctx context.Context, dataType config.DataType, sent int64, failed int64) { + var sentMeasure, failedMeasure *stats.Int64Measure + switch dataType { + case config.TracesDataType: + sentMeasure = obsmetrics.ExporterSentSpans + failedMeasure = obsmetrics.ExporterFailedToSendSpans + case config.MetricsDataType: + sentMeasure = obsmetrics.ExporterSentMetricPoints + failedMeasure = obsmetrics.ExporterFailedToSendMetricPoints + case config.LogsDataType: + sentMeasure = obsmetrics.ExporterSentLogRecords + failedMeasure = obsmetrics.ExporterFailedToSendLogRecords + } + + _ = stats.RecordWithTags( + ctx, + exp.mutators, + sentMeasure.M(sent), + failedMeasure.M(failed)) } func endSpan(ctx context.Context, err error, numSent, numFailedToSend int64, sentItemsKey, failedToSendItemsKey string) { diff --git a/obsreport/obsreport_test.go b/obsreport/obsreport_test.go index 69dbbc250b6..8941b60b178 100644 --- a/obsreport/obsreport_test.go +++ b/obsreport/obsreport_test.go @@ -272,152 +272,147 @@ func TestScrapeMetricsDataOp(t *testing.T) { } func TestExportTraceDataOp(t *testing.T) { - tt, err := obsreporttest.SetupTelemetry() - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) }) + testTelemetry(t, func(tt obsreporttest.TestTelemetry, registry *featuregate.Registry) { + parentCtx, parentSpan := tt.TracerProvider.Tracer("test").Start(context.Background(), t.Name()) + defer parentSpan.End() - parentCtx, parentSpan := tt.TracerProvider.Tracer("test").Start(context.Background(), t.Name()) - defer parentSpan.End() + obsrep := newExporter(ExporterSettings{ + ExporterID: exporter, + ExporterCreateSettings: tt.ToExporterCreateSettings(), + }, registry) - obsrep := NewExporter(ExporterSettings{ - ExporterID: exporter, - ExporterCreateSettings: tt.ToExporterCreateSettings(), - }) - - params := []testParams{ - {items: 22, err: nil}, - {items: 14, err: errFake}, - } - for i := range params { - ctx := obsrep.StartTracesOp(parentCtx) - assert.NotNil(t, ctx) - obsrep.EndTracesOp(ctx, params[i].items, params[i].err) - } + params := []testParams{ + {items: 22, err: nil}, + {items: 14, err: errFake}, + } + for i := range params { + ctx := obsrep.StartTracesOp(parentCtx) + assert.NotNil(t, ctx) + obsrep.EndTracesOp(ctx, params[i].items, params[i].err) + } - spans := tt.SpanRecorder.Ended() - require.Equal(t, len(params), len(spans)) + spans := tt.SpanRecorder.Ended() + require.Equal(t, len(params), len(spans)) - var sentSpans, failedToSendSpans int - for i, span := range spans { - assert.Equal(t, "exporter/"+exporter.String()+"/traces", span.Name()) - switch { - case params[i].err == nil: - sentSpans += params[i].items - require.Contains(t, span.Attributes(), attribute.KeyValue{Key: obsmetrics.SentSpansKey, Value: attribute.Int64Value(int64(params[i].items))}) - require.Contains(t, span.Attributes(), attribute.KeyValue{Key: obsmetrics.FailedToSendSpansKey, Value: attribute.Int64Value(0)}) - assert.Equal(t, codes.Unset, span.Status().Code) - case errors.Is(params[i].err, errFake): - failedToSendSpans += params[i].items - require.Contains(t, span.Attributes(), attribute.KeyValue{Key: obsmetrics.SentSpansKey, Value: attribute.Int64Value(0)}) - require.Contains(t, span.Attributes(), attribute.KeyValue{Key: obsmetrics.FailedToSendSpansKey, Value: attribute.Int64Value(int64(params[i].items))}) - assert.Equal(t, codes.Error, span.Status().Code) - assert.Equal(t, params[i].err.Error(), span.Status().Description) - default: - t.Fatalf("unexpected error: %v", params[i].err) + var sentSpans, failedToSendSpans int + for i, span := range spans { + assert.Equal(t, "exporter/"+exporter.String()+"/traces", span.Name()) + switch { + case params[i].err == nil: + sentSpans += params[i].items + require.Contains(t, span.Attributes(), attribute.KeyValue{Key: obsmetrics.SentSpansKey, Value: attribute.Int64Value(int64(params[i].items))}) + require.Contains(t, span.Attributes(), attribute.KeyValue{Key: obsmetrics.FailedToSendSpansKey, Value: attribute.Int64Value(0)}) + assert.Equal(t, codes.Unset, span.Status().Code) + case errors.Is(params[i].err, errFake): + failedToSendSpans += params[i].items + require.Contains(t, span.Attributes(), attribute.KeyValue{Key: obsmetrics.SentSpansKey, Value: attribute.Int64Value(0)}) + require.Contains(t, span.Attributes(), attribute.KeyValue{Key: obsmetrics.FailedToSendSpansKey, Value: attribute.Int64Value(int64(params[i].items))}) + assert.Equal(t, codes.Error, span.Status().Code) + assert.Equal(t, params[i].err.Error(), span.Status().Description) + default: + t.Fatalf("unexpected error: %v", params[i].err) + } } - } - require.NoError(t, obsreporttest.CheckExporterTraces(tt, exporter, int64(sentSpans), int64(failedToSendSpans))) + require.NoError(t, obsreporttest.CheckExporterTraces(tt, exporter, int64(sentSpans), int64(failedToSendSpans))) + + }) } func TestExportMetricsOp(t *testing.T) { - tt, err := obsreporttest.SetupTelemetry() - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) }) - - parentCtx, parentSpan := tt.TracerProvider.Tracer("test").Start(context.Background(), t.Name()) - defer parentSpan.End() + testTelemetry(t, func(tt obsreporttest.TestTelemetry, registry *featuregate.Registry) { + parentCtx, parentSpan := tt.TracerProvider.Tracer("test").Start(context.Background(), t.Name()) + defer parentSpan.End() - obsrep := NewExporter(ExporterSettings{ - ExporterID: exporter, - ExporterCreateSettings: tt.ToExporterCreateSettings(), - }) + obsrep := newExporter(ExporterSettings{ + ExporterID: exporter, + ExporterCreateSettings: tt.ToExporterCreateSettings(), + }, registry) - params := []testParams{ - {items: 17, err: nil}, - {items: 23, err: errFake}, - } - for i := range params { - ctx := obsrep.StartMetricsOp(parentCtx) - assert.NotNil(t, ctx) + params := []testParams{ + {items: 17, err: nil}, + {items: 23, err: errFake}, + } + for i := range params { + ctx := obsrep.StartMetricsOp(parentCtx) + assert.NotNil(t, ctx) - obsrep.EndMetricsOp(ctx, params[i].items, params[i].err) - } + obsrep.EndMetricsOp(ctx, params[i].items, params[i].err) + } - spans := tt.SpanRecorder.Ended() - require.Equal(t, len(params), len(spans)) + spans := tt.SpanRecorder.Ended() + require.Equal(t, len(params), len(spans)) - var sentMetricPoints, failedToSendMetricPoints int - for i, span := range spans { - assert.Equal(t, "exporter/"+exporter.String()+"/metrics", span.Name()) - switch { - case params[i].err == nil: - sentMetricPoints += params[i].items - require.Contains(t, span.Attributes(), attribute.KeyValue{Key: obsmetrics.SentMetricPointsKey, Value: attribute.Int64Value(int64(params[i].items))}) - require.Contains(t, span.Attributes(), attribute.KeyValue{Key: obsmetrics.FailedToSendMetricPointsKey, Value: attribute.Int64Value(0)}) - assert.Equal(t, codes.Unset, span.Status().Code) - case errors.Is(params[i].err, errFake): - failedToSendMetricPoints += params[i].items - require.Contains(t, span.Attributes(), attribute.KeyValue{Key: obsmetrics.SentMetricPointsKey, Value: attribute.Int64Value(0)}) - require.Contains(t, span.Attributes(), attribute.KeyValue{Key: obsmetrics.FailedToSendMetricPointsKey, Value: attribute.Int64Value(int64(params[i].items))}) - assert.Equal(t, codes.Error, span.Status().Code) - assert.Equal(t, params[i].err.Error(), span.Status().Description) - default: - t.Fatalf("unexpected error: %v", params[i].err) + var sentMetricPoints, failedToSendMetricPoints int + for i, span := range spans { + assert.Equal(t, "exporter/"+exporter.String()+"/metrics", span.Name()) + switch { + case params[i].err == nil: + sentMetricPoints += params[i].items + require.Contains(t, span.Attributes(), attribute.KeyValue{Key: obsmetrics.SentMetricPointsKey, Value: attribute.Int64Value(int64(params[i].items))}) + require.Contains(t, span.Attributes(), attribute.KeyValue{Key: obsmetrics.FailedToSendMetricPointsKey, Value: attribute.Int64Value(0)}) + assert.Equal(t, codes.Unset, span.Status().Code) + case errors.Is(params[i].err, errFake): + failedToSendMetricPoints += params[i].items + require.Contains(t, span.Attributes(), attribute.KeyValue{Key: obsmetrics.SentMetricPointsKey, Value: attribute.Int64Value(0)}) + require.Contains(t, span.Attributes(), attribute.KeyValue{Key: obsmetrics.FailedToSendMetricPointsKey, Value: attribute.Int64Value(int64(params[i].items))}) + assert.Equal(t, codes.Error, span.Status().Code) + assert.Equal(t, params[i].err.Error(), span.Status().Description) + default: + t.Fatalf("unexpected error: %v", params[i].err) + } } - } - require.NoError(t, obsreporttest.CheckExporterMetrics(tt, exporter, int64(sentMetricPoints), int64(failedToSendMetricPoints))) + require.NoError(t, obsreporttest.CheckExporterMetrics(tt, exporter, int64(sentMetricPoints), int64(failedToSendMetricPoints))) + }) } func TestExportLogsOp(t *testing.T) { - tt, err := obsreporttest.SetupTelemetry() - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) }) - - parentCtx, parentSpan := tt.TracerProvider.Tracer("test").Start(context.Background(), t.Name()) - defer parentSpan.End() + testTelemetry(t, func(tt obsreporttest.TestTelemetry, registry *featuregate.Registry) { + parentCtx, parentSpan := tt.TracerProvider.Tracer("test").Start(context.Background(), t.Name()) + defer parentSpan.End() - obsrep := NewExporter(ExporterSettings{ - ExporterID: exporter, - ExporterCreateSettings: tt.ToExporterCreateSettings(), - }) + obsrep := newExporter(ExporterSettings{ + ExporterID: exporter, + ExporterCreateSettings: tt.ToExporterCreateSettings(), + }, registry) - params := []testParams{ - {items: 17, err: nil}, - {items: 23, err: errFake}, - } - for i := range params { - ctx := obsrep.StartLogsOp(parentCtx) - assert.NotNil(t, ctx) + params := []testParams{ + {items: 17, err: nil}, + {items: 23, err: errFake}, + } + for i := range params { + ctx := obsrep.StartLogsOp(parentCtx) + assert.NotNil(t, ctx) - obsrep.EndLogsOp(ctx, params[i].items, params[i].err) - } + obsrep.EndLogsOp(ctx, params[i].items, params[i].err) + } - spans := tt.SpanRecorder.Ended() - require.Equal(t, len(params), len(spans)) + spans := tt.SpanRecorder.Ended() + require.Equal(t, len(params), len(spans)) - var sentLogRecords, failedToSendLogRecords int - for i, span := range spans { - assert.Equal(t, "exporter/"+exporter.String()+"/logs", span.Name()) - switch { - case params[i].err == nil: - sentLogRecords += params[i].items - require.Contains(t, span.Attributes(), attribute.KeyValue{Key: obsmetrics.SentLogRecordsKey, Value: attribute.Int64Value(int64(params[i].items))}) - require.Contains(t, span.Attributes(), attribute.KeyValue{Key: obsmetrics.FailedToSendLogRecordsKey, Value: attribute.Int64Value(0)}) - assert.Equal(t, codes.Unset, span.Status().Code) - case errors.Is(params[i].err, errFake): - failedToSendLogRecords += params[i].items - require.Contains(t, span.Attributes(), attribute.KeyValue{Key: obsmetrics.SentLogRecordsKey, Value: attribute.Int64Value(0)}) - require.Contains(t, span.Attributes(), attribute.KeyValue{Key: obsmetrics.FailedToSendLogRecordsKey, Value: attribute.Int64Value(int64(params[i].items))}) - assert.Equal(t, codes.Error, span.Status().Code) - assert.Equal(t, params[i].err.Error(), span.Status().Description) - default: - t.Fatalf("unexpected error: %v", params[i].err) + var sentLogRecords, failedToSendLogRecords int + for i, span := range spans { + assert.Equal(t, "exporter/"+exporter.String()+"/logs", span.Name()) + switch { + case params[i].err == nil: + sentLogRecords += params[i].items + require.Contains(t, span.Attributes(), attribute.KeyValue{Key: obsmetrics.SentLogRecordsKey, Value: attribute.Int64Value(int64(params[i].items))}) + require.Contains(t, span.Attributes(), attribute.KeyValue{Key: obsmetrics.FailedToSendLogRecordsKey, Value: attribute.Int64Value(0)}) + assert.Equal(t, codes.Unset, span.Status().Code) + case errors.Is(params[i].err, errFake): + failedToSendLogRecords += params[i].items + require.Contains(t, span.Attributes(), attribute.KeyValue{Key: obsmetrics.SentLogRecordsKey, Value: attribute.Int64Value(0)}) + require.Contains(t, span.Attributes(), attribute.KeyValue{Key: obsmetrics.FailedToSendLogRecordsKey, Value: attribute.Int64Value(int64(params[i].items))}) + assert.Equal(t, codes.Error, span.Status().Code) + assert.Equal(t, params[i].err.Error(), span.Status().Description) + default: + t.Fatalf("unexpected error: %v", params[i].err) + } } - } - require.NoError(t, obsreporttest.CheckExporterLogs(tt, exporter, int64(sentLogRecords), int64(failedToSendLogRecords))) + require.NoError(t, obsreporttest.CheckExporterLogs(tt, exporter, int64(sentLogRecords), int64(failedToSendLogRecords))) + }) } func TestReceiveWithLongLivedCtx(t *testing.T) { diff --git a/obsreport/obsreporttest/obsreporttest.go b/obsreport/obsreporttest/obsreporttest.go index c506535ec18..98bd5d2b4ac 100644 --- a/obsreport/obsreporttest/obsreporttest.go +++ b/obsreport/obsreporttest/obsreporttest.go @@ -140,38 +140,20 @@ func SetupTelemetry() (TestTelemetry, error) { // CheckExporterTraces checks that for the current exported values for trace exporter metrics match given values. // When this function is called it is required to also call SetupTelemetry as first thing. -func CheckExporterTraces(_ TestTelemetry, exporter config.ComponentID, sentSpans, sendFailedSpans int64) error { - exporterTags := tagsForExporterView(exporter) - if sendFailedSpans > 0 { - return multierr.Combine( - checkValueForView(exporterTags, sentSpans, "exporter/sent_spans"), - checkValueForView(exporterTags, sendFailedSpans, "exporter/send_failed_spans")) - } - return checkValueForView(exporterTags, sentSpans, "exporter/sent_spans") +func CheckExporterTraces(tts TestTelemetry, exporter config.ComponentID, sentSpans, sendFailedSpans int64) error { + return tts.otelPrometheusChecker.checkExporterTraces(exporter, sentSpans, sendFailedSpans) } // CheckExporterMetrics checks that for the current exported values for metrics exporter metrics match given values. // When this function is called it is required to also call SetupTelemetry as first thing. -func CheckExporterMetrics(_ TestTelemetry, exporter config.ComponentID, sentMetricsPoints, sendFailedMetricsPoints int64) error { - exporterTags := tagsForExporterView(exporter) - if sendFailedMetricsPoints > 0 { - return multierr.Combine( - checkValueForView(exporterTags, sentMetricsPoints, "exporter/sent_metric_points"), - checkValueForView(exporterTags, sendFailedMetricsPoints, "exporter/send_failed_metric_points")) - } - return checkValueForView(exporterTags, sentMetricsPoints, "exporter/sent_metric_points") +func CheckExporterMetrics(tts TestTelemetry, exporter config.ComponentID, sentMetricsPoints, sendFailedMetricsPoints int64) error { + return tts.otelPrometheusChecker.checkExporterMetrics(exporter, sentMetricsPoints, sendFailedMetricsPoints) } // CheckExporterLogs checks that for the current exported values for logs exporter metrics match given values. // When this function is called it is required to also call SetupTelemetry as first thing. -func CheckExporterLogs(_ TestTelemetry, exporter config.ComponentID, sentLogRecords, sendFailedLogRecords int64) error { - exporterTags := tagsForExporterView(exporter) - if sendFailedLogRecords > 0 { - return multierr.Combine( - checkValueForView(exporterTags, sentLogRecords, "exporter/sent_log_records"), - checkValueForView(exporterTags, sendFailedLogRecords, "exporter/send_failed_log_records")) - } - return checkValueForView(exporterTags, sentLogRecords, "exporter/sent_log_records") +func CheckExporterLogs(tts TestTelemetry, exporter config.ComponentID, sentLogRecords, sendFailedLogRecords int64) error { + return tts.otelPrometheusChecker.checkExporterLogs(exporter, sentLogRecords, sendFailedLogRecords) } // CheckProcessorTraces checks that for the current exported values for trace exporter metrics match given values. @@ -271,13 +253,6 @@ func tagsForProcessorView(processor config.ComponentID) []tag.Tag { } } -// tagsForExporterView returns the tags that are needed for the exporter views. -func tagsForExporterView(exporter config.ComponentID) []tag.Tag { - return []tag.Tag{ - {Key: exporterTag, Value: exporter.String()}, - } -} - func sortTags(tags []tag.Tag) { sort.SliceStable(tags, func(i, j int) bool { return tags[i].Key.Name() < tags[j].Key.Name() diff --git a/obsreport/obsreporttest/otelprometheuschecker.go b/obsreport/obsreporttest/otelprometheuschecker.go index 9c6586de998..d189d3037e8 100644 --- a/obsreport/obsreporttest/otelprometheuschecker.go +++ b/obsreport/obsreporttest/otelprometheuschecker.go @@ -55,6 +55,27 @@ func (pc *prometheusChecker) checkReceiverMetrics(receiver config.ComponentID, p pc.checkCounter("receiver_refused_metric_points", droppedMetricPoints, receiverAttrs)) } +func (pc *prometheusChecker) checkExporterTraces(exporter config.ComponentID, sentSpans, sendFailedSpans int64) error { + exporterAttrs := attributesForExporterMetrics(exporter) + return multierr.Combine( + pc.checkCounter("exporter_sent_spans", sentSpans, exporterAttrs), + pc.checkCounter("exporter_send_failed_spans", sendFailedSpans, exporterAttrs)) +} + +func (pc *prometheusChecker) checkExporterLogs(exporter config.ComponentID, sentLogRecords, sendFailedLogRecords int64) error { + exporterAttrs := attributesForExporterMetrics(exporter) + return multierr.Combine( + pc.checkCounter("exporter_sent_log_records", sentLogRecords, exporterAttrs), + pc.checkCounter("exporter_send_failed_log_records", sendFailedLogRecords, exporterAttrs)) +} + +func (pc *prometheusChecker) checkExporterMetrics(exporter config.ComponentID, sentMetricPoints, sendFailedMetricPoints int64) error { + exporterAttrs := attributesForExporterMetrics(exporter) + return multierr.Combine( + pc.checkCounter("exporter_sent_metric_points", sentMetricPoints, exporterAttrs), + pc.checkCounter("exporter_send_failed_metric_points", sendFailedMetricPoints, exporterAttrs)) +} + func (pc *prometheusChecker) checkCounter(expectedMetric string, value int64, attrs []attribute.KeyValue) error { // Forces a flush for the opencensus view data. _, _ = view.RetrieveData(expectedMetric) @@ -131,3 +152,10 @@ func attributesForReceiverMetrics(receiver config.ComponentID, transport string) attribute.String(transportTag.Name(), transport), } } + +// attributesForReceiverMetrics returns the attributes that are needed for the receiver metrics. +func attributesForExporterMetrics(exporter config.ComponentID) []attribute.KeyValue { + return []attribute.KeyValue{ + attribute.String(exporterTag.Name(), exporter.String()), + } +} diff --git a/obsreport/obsreporttest/otelprometheuschecker_test.go b/obsreport/obsreporttest/otelprometheuschecker_test.go index 7c1615ccb32..ac5027e4ac9 100644 --- a/obsreport/obsreporttest/otelprometheuschecker_test.go +++ b/obsreport/obsreporttest/otelprometheuschecker_test.go @@ -28,6 +28,24 @@ func newStubPromChecker() prometheusChecker { return prometheusChecker{ promHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte(` +# HELP exporter_send_failed_spans Number of spans in failed attempts to send to destination. +# TYPE exporter_send_failed_spans counter +exporter_send_failed_spans{exporter="fakeExporter"} 14 +# HELP exporter_sent_spans Number of spans successfully sent to destination. +# TYPE exporter_sent_spans counter +exporter_sent_spans{exporter="fakeExporter"} 43 +# HELP exporter_send_failed_metric_points Number of metrics in failed attempts to send to destination. +# TYPE exporter_send_failed_metric_points counter +exporter_send_failed_metric_points{exporter="fakeExporter"} 42 +# HELP exporter_sent_metric_points Number of metrics successfully sent to destination. +# TYPE exporter_sent_metric_points counter +exporter_sent_metric_points{exporter="fakeExporter"} 8 +# HELP exporter_send_failed_log_records Number of logs in failed attempts to send to destination. +# TYPE exporter_send_failed_log_records counter +exporter_send_failed_log_records{exporter="fakeExporter"} 36 +# HELP exporter_sent_log_records Number of logs successfully sent to destination. +# TYPE exporter_sent_log_records counter +exporter_sent_log_records{exporter="fakeExporter"} 103 # HELP receiver_accepted_log_records Number of log records successfully pushed into the pipeline. # TYPE receiver_accepted_log_records counter receiver_accepted_log_records{receiver="fakeReceiver",transport="fakeTransport"} 102 @@ -57,6 +75,7 @@ gauge_metric 49 func TestPromChecker(t *testing.T) { pc := newStubPromChecker() receiver := config.NewComponentID("fakeReceiver") + exporter := config.NewComponentID("fakeExporter") transport := "fakeTransport" assert.NoError(t, @@ -98,4 +117,19 @@ func TestPromChecker(t *testing.T) { pc.checkReceiverLogs(receiver, transport, 102, 35), "metrics from Receiver Logs should be valid", ) + + assert.NoError(t, + pc.checkExporterTraces(exporter, 43, 14), + "metrics from Exporter Traces should be valid", + ) + + assert.NoError(t, + pc.checkExporterMetrics(exporter, 8, 42), + "metrics from Exporter Metrics should be valid", + ) + + assert.NoError(t, + pc.checkExporterLogs(exporter, 103, 36), + "metrics from Exporter Logs should be valid", + ) }