diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e99c6ad1ad..7ad926bd20b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Add the new `go.opentelemetry.io/contrib/instrgen` package to provide auto-generated source code instrumentation. (#3068, #3108) - Add `SDK.Shutdown` method in `"go.opentelemetry.io/contrib/config"`. (#4583) - `NewSDK` in `go.opentelemetry.io/contrib/config` now returns a configured SDK with a valid `TracerProvider`. (#4741) +- `NewSDK` in `go.opentelemetry.io/contrib/config` now returns a configured SDK with a valid `MeterProvider`. (#) ### Changed diff --git a/config/config.go b/config/config.go index 9c182e0ff30..8fe6d6239ae 100644 --- a/config/config.go +++ b/config/config.go @@ -69,7 +69,11 @@ func NewSDK(opts ...ConfigurationOption) (SDK, error) { return SDK{}, err } - mp, mpShutdown := initMeterProvider(o) + mp, mpShutdown, err := meterProvider(o, r) + if err != nil { + return SDK{}, err + } + tp, tpShutdown, err := tracerProvider(o, r) if err != nil { return SDK{}, err diff --git a/config/metric.go b/config/metric.go index 870d8a8c5c9..b472225c0fb 100644 --- a/config/metric.go +++ b/config/metric.go @@ -4,15 +4,67 @@ package config // import "go.opentelemetry.io/contrib/config" import ( + "context" + "errors" + "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric/noop" sdkmetric "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/resource" ) -func initMeterProvider(cfg configOptions) (metric.MeterProvider, shutdownFunc) { +func meterProvider(cfg configOptions, res *resource.Resource) (metric.MeterProvider, shutdownFunc, error) { if cfg.opentelemetryConfig.MeterProvider == nil { - return noop.NewMeterProvider(), noopShutdown + return noop.NewMeterProvider(), noopShutdown, nil + } + opts := []sdkmetric.Option{ + sdkmetric.WithResource(res), + } + + var errs []error + for _, reader := range cfg.opentelemetryConfig.MeterProvider.Readers { + r, err := metricReader(cfg.ctx, reader) + if err == nil { + opts = append(opts, sdkmetric.WithReader(r)) + } else { + errs = append(errs, err) + } + } + if len(errs) > 0 { + return noop.NewMeterProvider(), noopShutdown, errors.Join(errs...) + } + + mp := sdkmetric.NewMeterProvider(opts...) + return mp, mp.Shutdown, nil +} + +func metricReader(ctx context.Context, r MetricReader) (sdkmetric.Reader, error) { + if r.Periodic != nil && r.Pull != nil { + return nil, errors.New("must not specify multiple metric reader type") + } + + if r.Periodic != nil { + exp, err := metricExporter(ctx, r.Periodic.Exporter) + if err != nil { + return nil, err + } + return periodMetricReader(r.Periodic, exp) + } + + if r.Pull != nil { + + } + return nil, errors.New("no valid metric reader") +} + +func periodMetricReader(pmr *PeriodicMetricReader, exp sdkmetric.Exporter) (sdkmetric.Reader, error) { + var opts []sdkmetric.PeriodicReaderOption + return sdkmetric.NewPeriodicReader(exp, opts...), nil +} + +func metricExporter(ctx context.Context, exporter MetricExporter) (sdkmetric.Exporter, error) { + if exporter.Console != nil && exporter.OTLP != nil { + return nil, errors.New("must not specify multiple exporters") } - mp := sdkmetric.NewMeterProvider() - return mp, mp.Shutdown + return nil, errors.New("no valid metric exporter") } diff --git a/config/metric_test.go b/config/metric_test.go index dd5dfdd74f6..95ac09a946c 100644 --- a/config/metric_test.go +++ b/config/metric_test.go @@ -4,15 +4,19 @@ package config import ( + "context" + "errors" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric/noop" + "go.opentelemetry.io/otel/sdk/resource" ) -func TestInitMeterProvider(t *testing.T) { +func TestMeterProvider(t *testing.T) { tests := []struct { name string cfg configOptions @@ -23,10 +27,53 @@ func TestInitMeterProvider(t *testing.T) { name: "no-meter-provider-configured", wantProvider: noop.NewMeterProvider(), }, + { + name: "error-in-config", + cfg: configOptions{ + opentelemetryConfig: OpenTelemetryConfiguration{ + MeterProvider: &MeterProvider{ + Readers: []MetricReader{ + { + Periodic: &PeriodicMetricReader{}, + Pull: &PullMetricReader{}, + }, + }, + }, + }, + }, + wantProvider: noop.NewMeterProvider(), + wantErr: errors.Join(errors.New("must not specify multiple metric reader type")), + }, + { + name: "multiple-errors-in-config", + cfg: configOptions{ + opentelemetryConfig: OpenTelemetryConfiguration{ + MeterProvider: &MeterProvider{ + Readers: []MetricReader{ + { + Periodic: &PeriodicMetricReader{}, + Pull: &PullMetricReader{}, + }, + { + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + Console: Console{}, + OTLP: &OTLPMetric{}, + }, + }, + }, + }, + }, + }, + }, + wantProvider: noop.NewMeterProvider(), + wantErr: errors.Join(errors.New("must not specify multiple metric reader type"), errors.New("must not specify multiple exporters")), + }, } for _, tt := range tests { - mp, err := initMeterProvider(tt.cfg) + mp, shutdown, err := meterProvider(tt.cfg, resource.Default()) require.Equal(t, tt.wantProvider, mp) - require.NoError(t, tt.wantErr, err) + assert.Equal(t, tt.wantErr, err) + require.NoError(t, shutdown(context.Background())) } }