Files
8e9c14c012 [pkg/scraperhelper] Switch scraper/scraperhelper to schema-based configuration (#15318)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue.
Ex. Adding a feature - Explain what this achieves.-->
#### Description

Switch `scraper/scraperhelper` `ControllerConfig` to schema-based
generated configuration.

This adds metadata-driven config generation for the scraper controller
settings, publishes the reusable `controller_config` schema from
`pkg/scraperhelper`, and updates the mdatagen sample scraper to embed
that exported config via schema reference.

<!-- Issue number if applicable -->
#### Link to tracking issue
Fixes #15258

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

Added usage example to `samplescraper` test component.

---------

Co-authored-by: Dmitry Anoshin <[email protected]>
2026-05-20 23:06:25 +00:00

188 lines
6.0 KiB
Go

// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package scraperhelper // import "go.opentelemetry.io/collector/scraper/scraperhelper"
import (
"context"
"errors"
"time"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/scraper"
"go.opentelemetry.io/collector/scraper/scrapererror"
"go.opentelemetry.io/collector/scraper/scraperhelper/internal/controller"
)
// ControllerOption apply changes to internal options.
type ControllerOption interface {
apply(*controllerOptions)
}
type optionFunc func(*controllerOptions)
func (of optionFunc) apply(e *controllerOptions) {
of(e)
}
// AddMetricsScraper configures the scraper.Metrics to be called with the
// specified options, and at the specified collection interval.
//
// Observability information will be reported, and the scraped metrics
// will be passed to the next consumer.
func AddMetricsScraper(t component.Type, sc scraper.Metrics) ControllerOption {
f := scraper.NewFactory(t, nil,
scraper.WithMetrics(func(context.Context, scraper.Settings, component.Config) (scraper.Metrics, error) {
return sc, nil
}, component.StabilityLevelAlpha))
return AddFactoryWithConfig(f, nil)
}
// AddScraper configures the scraper.Metrics to be called with the
// specified options, and at the specified collection interval.
//
// Observability information will be reported, and the scraped metrics
// will be passed to the next consumer.
//
// Deprecated: [0.144.0] Use AddMetricsScraper instead.
func AddScraper(t component.Type, sc scraper.Metrics) ControllerOption {
return AddMetricsScraper(t, sc)
}
// AddFactoryWithConfig configures the scraper.Factory and associated config that
// will be used to create a new scraper. The created scraper will be called with
// the specified options, and at the specified collection interval.
//
// Observability information will be reported, and the scraped metrics
// will be passed to the next consumer.
func AddFactoryWithConfig(f scraper.Factory, cfg component.Config) ControllerOption {
return optionFunc(func(o *controllerOptions) {
o.factoriesWithConfig = append(o.factoriesWithConfig, factoryWithConfig{f: f, cfg: cfg})
})
}
// WithTickerChannel allows you to override the scraper controller's ticker
// channel to specify when scrape is called. This is only expected to be
// used by tests.
func WithTickerChannel(tickerCh <-chan time.Time) ControllerOption {
return optionFunc(func(o *controllerOptions) {
o.tickerCh = tickerCh
})
}
type factoryWithConfig struct {
f scraper.Factory
cfg component.Config
}
type controllerOptions struct {
tickerCh <-chan time.Time
factoriesWithConfig []factoryWithConfig
}
// NewLogsController creates a receiver.Logs with the configured options, that can control multiple scraper.Logs.
func NewLogsController(cfg *ControllerConfig,
rSet receiver.Settings,
nextConsumer consumer.Logs,
options ...ControllerOption,
) (receiver.Logs, error) {
co := getOptions(options)
scrapers := make([]scraper.Logs, 0, len(co.factoriesWithConfig))
for _, fwc := range co.factoriesWithConfig {
set := controller.GetSettings(fwc.f.Type(), rSet)
s, err := fwc.f.CreateLogs(context.Background(), set, fwc.cfg)
if err != nil {
return nil, err
}
s, err = wrapObsLogs(s, rSet.ID, set.ID, set.TelemetrySettings)
if err != nil {
return nil, err
}
scrapers = append(scrapers, s)
}
return controller.NewController[scraper.Logs](
cfg, rSet, scrapers, func(ctx context.Context, c *controller.Controller[scraper.Logs]) error {
return scrapeLogs(ctx, c, nextConsumer)
}, co.tickerCh)
}
// NewMetricsController creates a receiver.Metrics with the configured options, that can control multiple scraper.Metrics.
func NewMetricsController(cfg *ControllerConfig,
rSet receiver.Settings,
nextConsumer consumer.Metrics,
options ...ControllerOption,
) (receiver.Metrics, error) {
co := getOptions(options)
scrapers := make([]scraper.Metrics, 0, len(co.factoriesWithConfig))
for _, fwc := range co.factoriesWithConfig {
set := controller.GetSettings(fwc.f.Type(), rSet)
s, err := fwc.f.CreateMetrics(context.Background(), set, fwc.cfg)
if err != nil {
return nil, err
}
s, err = wrapObsMetrics(s, rSet.ID, set.ID, set.TelemetrySettings)
if err != nil {
return nil, err
}
scrapers = append(scrapers, s)
}
return controller.NewController[scraper.Metrics](
cfg, rSet, scrapers, func(ctx context.Context, c *controller.Controller[scraper.Metrics]) error {
return scrapeMetrics(ctx, c, nextConsumer)
}, co.tickerCh)
}
func scrapeLogs(ctx context.Context, c *controller.Controller[scraper.Logs], nextConsumer consumer.Logs) error {
var errs []error
logs := plog.NewLogs()
for i := range c.Scrapers {
md, err := c.Scrapers[i].ScrapeLogs(ctx)
if err != nil {
errs = append(errs, err)
if !scrapererror.IsPartialScrapeError(err) {
continue
}
}
md.ResourceLogs().MoveAndAppendTo(logs.ResourceLogs())
}
logRecordCount := logs.LogRecordCount()
ctx = c.Obsrecv.StartLogsOp(ctx)
err := nextConsumer.ConsumeLogs(ctx, logs)
c.Obsrecv.EndLogsOp(ctx, "", logRecordCount, err)
return errors.Join(append(errs, err)...)
}
func scrapeMetrics(ctx context.Context, c *controller.Controller[scraper.Metrics], nextConsumer consumer.Metrics) error {
var errs []error
metrics := pmetric.NewMetrics()
for i := range c.Scrapers {
md, err := c.Scrapers[i].ScrapeMetrics(ctx)
if err != nil {
errs = append(errs, err)
if !scrapererror.IsPartialScrapeError(err) {
continue
}
}
md.ResourceMetrics().MoveAndAppendTo(metrics.ResourceMetrics())
}
dataPointCount := metrics.DataPointCount()
ctx = c.Obsrecv.StartMetricsOp(ctx)
err := nextConsumer.ConsumeMetrics(ctx, metrics)
c.Obsrecv.EndMetricsOp(ctx, "", dataPointCount, err)
return errors.Join(append(errs, err)...)
}
func getOptions(options []ControllerOption) controllerOptions {
co := controllerOptions{}
for _, op := range options {
op.apply(&co)
}
return co
}