Files
telemetry/scraper/scraperhelper/obs_metrics_test.go
Dónal O'Sullivanandzeekay f11f1bf9fe [scraper/scraperhelper] Add scraper ID to error logs (#14461)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue.
Ex. Adding a feature - Explain what this achieves.-->
#### Description

This PR adds the `scraperID` to the error logs for scrapers (metrics,
logs, and profiles). Why is this important?

Currently if an error occurs in any of the scrapers in
`hostmetricsreceiver/internal/scraper/...` there is no info about the
which scraper errored in the logs:

```bash
2026-01-21T10:42:42.830Z	error	[email protected].0/obs_metrics.go:61	Error scraping metrics	{"resource": {"service.instance.id": "63071771-056d-40ca-83e3-585dc1a00d57", "service.name": "otelcontribcol", "service.version": "0.144.0-dev"}, "otelcol.component.id": "hostmetrics", "otelcol.component.kind": "receiver", "otelcol.signal": "metrics", "error": "forced test error for scraper identification"}
```

The update in this PR add's the scraper ID (In this case the memory
scraper errored) to the logs which then gives us:

```bash
2026-01-21T10:23:52.759Z	error	scraperhelper/obs_metrics.go:62	Error scraping metrics	{"resource": {"service.instance.id": "7137f19d-706f-4095-8367-0d68b2732402", "service.name": "otelcontribcol", "service.version": "0.144.0-dev"}, "otelcol.component.id": "hostmetrics", "otelcol.component.kind": "receiver", "otelcol.signal": "metrics", "scraper": "memory", "error": "forced test error for scraper identification"}
```

<!-- Issue number if applicable -->
#### Link to tracking issue
Fixes Issue in
[collector-contrib](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/35814#issuecomment-2676316294).

<!--Describe what testing was performed and which tests were added.-->
#### Testing

**Metrics scrapers:** Locally tested with `collector-contrib` by
updating the mod file...
**Logs scrapers:** The same pattern was applied. Logs scrapers (MySQL,
PostgreSQL, Oracle, SQL Server receivers) require database connections
for integration testing.
**Profiles scrapers (xscraperhelper):** The same pattern was applied.
Note: There are currently no profiles scrapers in collector-contrib to
test against.

**Metrics scraper local test**: Locally this was updated and included in
the `collector-contrib` via updating the mod file. An error was forced
in the `hostmetricsreceiver/internal/scraper/...` both memory and cpu,
we can now see which scraper component had an error:

```bash
2026-01-21T11:51:34.176Z	error	scraperhelper/obs_metrics.go:61	Error scraping metrics	{"resource": {"service.instance.id": "e6832705-bc54-43dd-9b1f-0c06d58b7ead", "service.name": "otelcontribcol", "service.version": "0.144.0-dev"}, "otelcol.component.id": "hostmetrics", "otelcol.component.kind": "receiver", "otelcol.signal": "metrics", "scraper": "memory", "error": "forced test error for scraper identification"}
go.opentelemetry.io/collector/scraper/scraperhelper.wrapObsMetrics.func1
	/home/dos/Documents/opentelemetry-collector/scraper/scraperhelper/obs_metrics.go:61
go.opentelemetry.io/collector/scraper.ScrapeMetricsFunc.ScrapeMetrics
	/home/dos/go/pkg/mod/go.opentelemetry.io/collector/[email protected].0/metrics.go:24
go.opentelemetry.io/collector/scraper/scraperhelper.scrapeMetrics
	/home/dos/Documents/opentelemetry-collector/scraper/scraperhelper/controller.go:167
go.opentelemetry.io/collector/scraper/scraperhelper.NewMetricsController.func1
	/home/dos/Documents/opentelemetry-collector/scraper/scraperhelper/controller.go:139
go.opentelemetry.io/collector/scraper/scraperhelper/internal/controller.(*Controller[...]).startScraping.func1
	/home/dos/Documents/opentelemetry-collector/scraper/scraperhelper/internal/controller/controller.go:118
2026-01-21T11:51:34.177Z	error	scraperhelper/obs_metrics.go:61	Error scraping metrics	{"resource": {"service.instance.id": "e6832705-bc54-43dd-9b1f-0c06d58b7ead", "service.name": "otelcontribcol", "service.version": "0.144.0-dev"}, "otelcol.component.id": "hostmetrics", "otelcol.component.kind": "receiver", "otelcol.signal": "metrics", "scraper": "cpu", "error": "forced test error for scraper identification"}
go.opentelemetry.io/collector/scraper/scraperhelper.wrapObsMetrics.func1
	/home/dos/Documents/opentelemetry-collector/scraper/scraperhelper/obs_metrics.go:61
go.opentelemetry.io/collector/scraper.ScrapeMetricsFunc.ScrapeMetrics
	/home/dos/go/pkg/mod/go.opentelemetry.io/collector/[email protected].0/metrics.go:24
go.opentelemetry.io/collector/scraper/scraperhelper.scrapeMetrics
	/home/dos/Documents/opentelemetry-collector/scraper/scraperhelper/controller.go:167
go.opentelemetry.io/collector/scraper/scraperhelper.NewMetricsController.func1
	/home/dos/Documents/opentelemetry-collector/scraper/scraperhelper/controller.go:139
go.opentelemetry.io/collector/scraper/scraperhelper/internal/controller.(*Controller[...]).startScraping.func1
	/home/dos/Documents/opentelemetry-collector/scraper/scraperhelper/internal/controller/controller.go:118
2026-01-21T11:51:34.199Z	info	Metrics	{"resource": {"service.instance.id": "e6832705-bc54-43dd-9b1f-0c06d58b7ead", "service.name": "otelcontribcol", "service.version": "0.144.0-dev"}, "otelcol.component.id": "debug", "otelcol.component.kind": "exporter", "otelcol.signal": "metrics", "resource metrics": 1, "metrics": 1, "data points": 1}
2026-01-21T11:51:34.199Z	info	ResourceMetrics #0
Resource SchemaURL: https://opentelemetry.io/schemas/1.9.0

```

Closes
https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/35814
2026-02-22 00:54:48 -08:00

167 lines
6.0 KiB
Go

// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package scraperhelper
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/testdata"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/scraper"
"go.opentelemetry.io/collector/scraper/scrapererror"
"go.opentelemetry.io/collector/scraper/scraperhelper/internal/controller"
"go.opentelemetry.io/collector/scraper/scraperhelper/internal/metadatatest"
)
var (
receiverID = component.MustNewID("fakeReceiver")
scraperID = component.MustNewID("fakeScraper")
errFake = errors.New("errFake")
partialErrFake = scrapererror.NewPartialScrapeError(errFake, 2)
)
type testParams struct {
items int
err error
}
func TestScrapeMetricsDataOp(t *testing.T) {
tel := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tel.Shutdown(context.Background())) })
set := tel.NewTelemetrySettings()
parentCtx, parentSpan := set.TracerProvider.Tracer("test").Start(context.Background(), t.Name())
defer parentSpan.End()
params := []testParams{
{items: 23, err: partialErrFake},
{items: 29, err: errFake},
{items: 15, err: nil},
}
for i := range params {
sm, err := scraper.NewMetrics(func(context.Context) (pmetric.Metrics, error) {
return testdata.GenerateMetrics(params[i].items), params[i].err
})
require.NoError(t, err)
sf, err := wrapObsMetrics(sm, receiverID, scraperID, set)
require.NoError(t, err)
_, err = sf.ScrapeMetrics(parentCtx)
require.ErrorIs(t, err, params[i].err)
}
spans := tel.SpanRecorder.Ended()
require.Len(t, spans, len(params))
var scrapedMetricPoints, erroredMetricPoints int
for i, span := range spans {
assert.Equal(t, "scraper/"+scraperID.String()+"/ScrapeMetrics", span.Name())
switch {
case params[i].err == nil:
scrapedMetricPoints += params[i].items
require.Contains(t, span.Attributes(), attribute.Int64(scrapedMetricPointsKey, int64(params[i].items)))
require.Contains(t, span.Attributes(), attribute.Int64(erroredMetricPointsKey, 0))
assert.Equal(t, codes.Unset, span.Status().Code)
case errors.Is(params[i].err, errFake):
// Since we get an error, we cannot record any metrics because we don't know if the returned pmetric.Metrics is valid instance.
require.Contains(t, span.Attributes(), attribute.Int64(scrapedMetricPointsKey, 0))
require.Contains(t, span.Attributes(), attribute.Int64(erroredMetricPointsKey, 0))
assert.Equal(t, codes.Error, span.Status().Code)
assert.Equal(t, params[i].err.Error(), span.Status().Description)
case errors.Is(params[i].err, partialErrFake):
scrapedMetricPoints += params[i].items
erroredMetricPoints += 2
require.Contains(t, span.Attributes(), attribute.Int64(scrapedMetricPointsKey, int64(params[i].items)))
require.Contains(t, span.Attributes(), attribute.Int64(erroredMetricPointsKey, 2))
assert.Equal(t, codes.Error, span.Status().Code)
assert.Equal(t, params[i].err.Error(), span.Status().Description)
default:
t.Fatalf("unexpected err param: %v", params[i].err)
}
}
checkScraperMetrics(t, tel, receiverID, scraperID, int64(scrapedMetricPoints), int64(erroredMetricPoints))
}
func TestCheckScraperMetrics(t *testing.T) {
tel := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tel.Shutdown(context.Background())) })
sm, err := scraper.NewMetrics(func(context.Context) (pmetric.Metrics, error) {
return testdata.GenerateMetrics(7), nil
})
require.NoError(t, err)
sf, err := wrapObsMetrics(sm, receiverID, scraperID, tel.NewTelemetrySettings())
require.NoError(t, err)
_, err = sf.ScrapeMetrics(context.Background())
require.NoError(t, err)
checkScraperMetrics(t, tel, receiverID, scraperID, 7, 0)
}
func TestScrapeMetricsDataOp_LogsScraperID(t *testing.T) {
tel := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tel.Shutdown(context.Background())) })
core, observedLogs := observer.New(zap.ErrorLevel)
telset := tel.NewTelemetrySettings()
telset.Logger = zap.New(core)
rSet := receiver.Settings{
ID: receiverID,
TelemetrySettings: telset,
}
set := controller.GetSettings(scraperID.Type(), rSet)
sm, err := scraper.NewMetrics(func(context.Context) (pmetric.Metrics, error) {
return pmetric.NewMetrics(), errFake
})
require.NoError(t, err)
sf, err := wrapObsMetrics(sm, receiverID, scraperID, set.TelemetrySettings)
require.NoError(t, err)
_, err = sf.ScrapeMetrics(context.Background())
require.ErrorIs(t, err, errFake)
errorLogs := observedLogs.FilterLevelExact(zap.ErrorLevel).All()
require.Len(t, errorLogs, 1)
assert.Equal(t, "Error scraping metrics", errorLogs[0].Message)
assert.Equal(t, scraperID.String(), errorLogs[0].ContextMap()["scraper"])
assert.Equal(t, errFake.Error(), errorLogs[0].ContextMap()["error"])
}
func checkScraperMetrics(t *testing.T, tt *componenttest.Telemetry, receiver, scraper component.ID, scrapedMetricPoints, erroredMetricPoints int64) {
metadatatest.AssertEqualScraperScrapedMetricPoints(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(receiverKey, receiver.String()),
attribute.String(scraperKey, scraper.String())),
Value: scrapedMetricPoints,
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
metadatatest.AssertEqualScraperErroredMetricPoints(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(receiverKey, receiver.String()),
attribute.String(scraperKey, scraper.String())),
Value: erroredMetricPoints,
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}