Files
Dónal O'Sullivanandzeekay 70a57f7229 [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

155 lines
5.6 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/plog"
"go.opentelemetry.io/collector/pdata/testdata"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/scraper"
"go.opentelemetry.io/collector/scraper/scraperhelper/internal/controller"
"go.opentelemetry.io/collector/scraper/scraperhelper/internal/metadatatest"
)
func TestScrapeLogsDataOp(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.NewLogs(func(context.Context) (plog.Logs, error) {
return testdata.GenerateLogs(params[i].items), params[i].err
})
require.NoError(t, err)
sf, err := wrapObsLogs(sm, receiverID, scraperID, set)
require.NoError(t, err)
_, err = sf.ScrapeLogs(parentCtx)
require.ErrorIs(t, err, params[i].err)
}
spans := tel.SpanRecorder.Ended()
require.Len(t, spans, len(params))
var scrapedLogRecords, erroredLogRecords int
for i, span := range spans {
assert.Equal(t, "scraper/"+scraperID.String()+"/ScrapeLogs", span.Name())
switch {
case params[i].err == nil:
scrapedLogRecords += params[i].items
require.Contains(t, span.Attributes(), attribute.Int64(scrapedLogRecordsKey, int64(params[i].items)))
require.Contains(t, span.Attributes(), attribute.Int64(erroredLogRecordsKey, 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 plog.Logs is valid instance.
require.Contains(t, span.Attributes(), attribute.Int64(scrapedLogRecordsKey, 0))
require.Contains(t, span.Attributes(), attribute.Int64(erroredLogRecordsKey, 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):
scrapedLogRecords += params[i].items
erroredLogRecords += 2
require.Contains(t, span.Attributes(), attribute.Int64(scrapedLogRecordsKey, int64(params[i].items)))
require.Contains(t, span.Attributes(), attribute.Int64(erroredLogRecordsKey, 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)
}
}
checkScraperLogs(t, tel, receiverID, scraperID, int64(scrapedLogRecords), int64(erroredLogRecords))
}
func TestCheckScraperLogs(t *testing.T) {
tel := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tel.Shutdown(context.Background())) })
sm, err := scraper.NewLogs(func(context.Context) (plog.Logs, error) {
return testdata.GenerateLogs(7), nil
})
require.NoError(t, err)
sf, err := wrapObsLogs(sm, receiverID, scraperID, tel.NewTelemetrySettings())
require.NoError(t, err)
_, err = sf.ScrapeLogs(context.Background())
require.NoError(t, err)
checkScraperLogs(t, tel, receiverID, scraperID, 7, 0)
}
func TestScrapeLogsDataOp_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.NewLogs(func(context.Context) (plog.Logs, error) {
return plog.NewLogs(), errFake
})
require.NoError(t, err)
sf, err := wrapObsLogs(sm, receiverID, scraperID, set.TelemetrySettings)
require.NoError(t, err)
_, err = sf.ScrapeLogs(context.Background())
require.ErrorIs(t, err, errFake)
errorLogs := observedLogs.FilterLevelExact(zap.ErrorLevel).All()
require.Len(t, errorLogs, 1)
assert.Equal(t, "Error scraping logs", errorLogs[0].Message)
assert.Equal(t, scraperID.String(), errorLogs[0].ContextMap()["scraper"])
assert.Equal(t, errFake.Error(), errorLogs[0].ContextMap()["error"])
}
func checkScraperLogs(t *testing.T, tel *componenttest.Telemetry, receiver, scraper component.ID, scrapedLogRecords, erroredLogRecords int64) {
metadatatest.AssertEqualScraperScrapedLogRecords(t, tel,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(receiverKey, receiver.String()),
attribute.String(scraperKey, scraper.String())),
Value: scrapedLogRecords,
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
metadatatest.AssertEqualScraperErroredLogRecords(t, tel,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(receiverKey, receiver.String()),
attribute.String(scraperKey, scraper.String())),
Value: erroredLogRecords,
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}