From 8a60220c970c2b9792884cebffc9dadee4cefd14 Mon Sep 17 00:00:00 2001 From: Joshua MacDonald Date: Thu, 17 Oct 2024 08:59:07 -0700 Subject: [PATCH] Self telemetry tidy-up (#798) **Description:** There was an accidental regression in self-telemetry setup. This was discovered while updating to the latest release. Fixed here, plus more idiomatic Go. Note that the Trace exporter setup allows options to override the providers, which is used for internal test purposes. The Metric exporter does not have the same customization, so the Prom/OTLP metric exporters are somewhat simpler. --- lightstep/sdk/internal/common.go | 65 +++++++------------ lightstep/sdk/internal/go.mod | 9 +-- lightstep/sdk/internal/go.sum | 25 ------- .../metric/exporters/otlp/otelcol/client.go | 28 +++++--- lightstep/sdk/metric/exporters/prom/client.go | 31 ++++++--- .../sdk/metric/internal/export/export.go | 2 +- .../trace/exporters/otlp/otelcol/client.go | 51 +++++++++++---- 7 files changed, 101 insertions(+), 110 deletions(-) diff --git a/lightstep/sdk/internal/common.go b/lightstep/sdk/internal/common.go index fb44ed34..3e7f71e5 100644 --- a/lightstep/sdk/internal/common.go +++ b/lightstep/sdk/internal/common.go @@ -15,15 +15,14 @@ package internal import ( + "errors" + "strings" + "sync" + + "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config/configtelemetry" - "go.opentelemetry.io/collector/exporter" - "go.opentelemetry.io/otel" - noopmetric "go.opentelemetry.io/otel/metric/noop" "go.opentelemetry.io/otel/trace" - nooptrace "go.opentelemetry.io/otel/trace/noop" "go.uber.org/zap" - "strings" - "sync" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/otel/attribute" @@ -206,46 +205,26 @@ const ( func ConfigureSelfTelemetry( name string, - selfSpans bool, - selfMetrics bool, - exporterSettings *exporter.Settings, - tracer *trace.Tracer, - telemetryItemsCounter *metricapi.Int64Counter, -) error { + tp trace.TracerProvider, + mp metric.MeterProvider, +) (component.TelemetrySettings, trace.Tracer, metricapi.Int64Counter, error) { + var settings component.TelemetrySettings // setup logs - logger, err := zap.NewProduction() - if err != nil { - return err - } - - exporterSettings.TelemetrySettings.Logger = logger + logger, logErr := zap.NewProduction() - // setup traces - tracerProvider := trace.TracerProvider(nooptrace.NewTracerProvider()) - if selfSpans { - tracerProvider = otel.GetTracerProvider() - } - - exporterSettings.TelemetrySettings.TracerProvider = tracerProvider - *tracer = exporterSettings.TelemetrySettings.TracerProvider.Tracer(name) - - // setup metrics - meterProvider := metricapi.MeterProvider(noopmetric.NewMeterProvider()) - if selfMetrics { - meterProvider = NOTelColMeterProvider(otel.GetMeterProvider()) - } - - exporterSettings.TelemetrySettings.MetricsLevel = configtelemetry.LevelNormal - exporterSettings.TelemetrySettings.MeterProvider = meterProvider - exporterSettings.TelemetrySettings.LeveledMeterProvider = func(level configtelemetry.Level) metricapi.MeterProvider { - return meterProvider - } + // this replaces otelcol_ w/ otelsdk_ + mp = NOTelColMeterProvider(mp) - // setup the telemetry item counter metric - if telemetryItemsCounter != nil { - meter := exporterSettings.TelemetrySettings.LeveledMeterProvider(configtelemetry.LevelNormal).Meter(name) - *telemetryItemsCounter, err = meter.Int64Counter(selfTelemetryItemsCounterName) + settings.Logger = logger + settings.TracerProvider = tp + settings.MeterProvider = mp + settings.MetricsLevel = configtelemetry.LevelNormal + settings.LeveledMeterProvider = func(level configtelemetry.Level) metricapi.MeterProvider { + return mp } - return err + tracer := tp.Tracer(name) + meter := mp.Meter(name) + counter, metErr := meter.Int64Counter(selfTelemetryItemsCounterName) + return settings, tracer, counter, errors.Join(logErr, metErr) } diff --git a/lightstep/sdk/internal/go.mod b/lightstep/sdk/internal/go.mod index 77ae409c..7ed5d167 100644 --- a/lightstep/sdk/internal/go.mod +++ b/lightstep/sdk/internal/go.mod @@ -6,8 +6,8 @@ toolchain go1.22.5 require ( github.com/stretchr/testify v1.9.0 + go.opentelemetry.io/collector/component v0.111.0 go.opentelemetry.io/collector/config/configtelemetry v0.111.0 - go.opentelemetry.io/collector/exporter v0.111.0 go.opentelemetry.io/collector/pdata v1.17.0 go.opentelemetry.io/otel v1.30.0 go.opentelemetry.io/otel/metric v1.30.0 @@ -22,15 +22,8 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect - go.opentelemetry.io/collector/component v0.111.0 // indirect - go.opentelemetry.io/collector/consumer v0.111.0 // indirect - go.opentelemetry.io/collector/internal/globalsignal v0.111.0 // indirect - go.opentelemetry.io/collector/pipeline v0.111.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/net v0.28.0 // indirect golang.org/x/sys v0.25.0 // indirect diff --git a/lightstep/sdk/internal/go.sum b/lightstep/sdk/internal/go.sum index 5bc5446c..23e45f71 100644 --- a/lightstep/sdk/internal/go.sum +++ b/lightstep/sdk/internal/go.sum @@ -1,4 +1,3 @@ -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -10,28 +9,18 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -40,22 +29,8 @@ go.opentelemetry.io/collector/component v0.111.0 h1:AiDIrhkq6sbHnU9Rhq6t4DC4Gal4 go.opentelemetry.io/collector/component v0.111.0/go.mod h1:wYwbRuhzK5bm5x1bX+ukm1tT50QXYLs4MKwzyfiVGoE= go.opentelemetry.io/collector/config/configtelemetry v0.111.0 h1:Q3TJRM2A3FIDjIvzWa3uFArsdFN0I/0GzcWynHjC+oY= go.opentelemetry.io/collector/config/configtelemetry v0.111.0/go.mod h1:R0MBUxjSMVMIhljuDHWIygzzJWQyZHXXWIgQNxcFwhc= -go.opentelemetry.io/collector/consumer v0.111.0 h1:d2kRTDnu+p0q4D5fTU+Pk59KRm5F2JRYrk30Ep5j0xI= -go.opentelemetry.io/collector/consumer v0.111.0/go.mod h1:FjY9bPbVkFZLKKxnNbGsIqaz3lcFDKGf+7wxA1uCugs= -go.opentelemetry.io/collector/consumer/consumerprofiles v0.111.0 h1:w9kGdTaXdwD/ZtbxVOvuYQEFKBX3THQgEz/enQnMt9s= -go.opentelemetry.io/collector/consumer/consumerprofiles v0.111.0/go.mod h1:Ebt1jDdrQb3G2sNHrWHNr5wS3UJ9k3h8LHCqUPTbxLY= -go.opentelemetry.io/collector/consumer/consumertest v0.111.0 h1:ZEikGRPdrhVAq7xhJVc8WapRBVN/CdPnMEnXgpRGu1U= -go.opentelemetry.io/collector/consumer/consumertest v0.111.0/go.mod h1:EHPrn8ovcTGdTDlCEi1grOXSP3jUUYU0zvl92uA5L+4= -go.opentelemetry.io/collector/exporter v0.111.0 h1:NpiP6xXGOmSi59RlB5gGTB+PtCLldVeK3vCQBJPW0sU= -go.opentelemetry.io/collector/exporter v0.111.0/go.mod h1:FjO80zGWZjqXil8vM1MS8gyxxzZ29WmChTNV2y9xjHo= -go.opentelemetry.io/collector/internal/globalsignal v0.111.0 h1:oq0nSD+7K2Q1Fx5d3s6lPRdKZeTL0FEg4sIaR7ZJzIc= -go.opentelemetry.io/collector/internal/globalsignal v0.111.0/go.mod h1:GqMXodPWOxK5uqpX8MaMXC2389y2XJTa5nPwf8FYDK8= go.opentelemetry.io/collector/pdata v1.17.0 h1:z8cjjT2FThAehWu5fbF48OnZyK5q8xd1UhC4XszDo0w= go.opentelemetry.io/collector/pdata v1.17.0/go.mod h1:yZaQ9KZAm/qie96LTygRKxOXMq0/54h8OW7330ycuvQ= -go.opentelemetry.io/collector/pdata/pprofile v0.111.0 h1:4if6rItcX8a6X4bIh6lwQnlE+ncKXQaIim7F5O7ZA58= -go.opentelemetry.io/collector/pdata/pprofile v0.111.0/go.mod h1:iBwrNFB6za1qspy46ZE41H3MmcxUogn2AuYbrWdoMd8= -go.opentelemetry.io/collector/pipeline v0.111.0 h1:qENDGvWWnDXguEfmj8eO+5kr8Y6XFKytU5SuMinz3Ls= -go.opentelemetry.io/collector/pipeline v0.111.0/go.mod h1:ZZMU3019geEU283rTW5M/LkcqLqHp/YI2Nl6/Vp68PQ= go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= diff --git a/lightstep/sdk/metric/exporters/otlp/otelcol/client.go b/lightstep/sdk/metric/exporters/otlp/otelcol/client.go index 46cd18c8..edb06972 100644 --- a/lightstep/sdk/metric/exporters/otlp/otelcol/client.go +++ b/lightstep/sdk/metric/exporters/otlp/otelcol/client.go @@ -34,8 +34,11 @@ import ( "go.opentelemetry.io/collector/exporter" "go.opentelemetry.io/collector/exporter/exporterhelper" "go.opentelemetry.io/collector/processor" + "go.opentelemetry.io/otel" metricapi "go.opentelemetry.io/otel/metric" + metricnoop "go.opentelemetry.io/otel/metric/noop" traceapi "go.opentelemetry.io/otel/trace" + tracenoop "go.opentelemetry.io/otel/trace/noop" "go.uber.org/multierr" ) @@ -147,16 +150,23 @@ func NewExporter(ctx context.Context, cfg Config) (metric.PushExporter, error) { c.settings.ID = component.NewID(component.MustNewType("otel_sdk_metric_otlp")) } - err := internal.ConfigureSelfTelemetry( - "lightstep-go/sdk/metric", - cfg.SelfSpans, - cfg.SelfMetrics, - &c.settings, - &c.tracer, - &c.counter, - ) - if err != nil { + var mp metricapi.MeterProvider = metricnoop.NewMeterProvider() + var tp traceapi.TracerProvider = tracenoop.NewTracerProvider() + + if cfg.SelfSpans { + tp = otel.GetTracerProvider() + } + if cfg.SelfMetrics { + mp = otel.GetMeterProvider() + } + + if settings, tracer, counter, err := + internal.ConfigureSelfTelemetry("lightstep-go/sdk/metric", tp, mp); err != nil { return nil, err + } else { + c.settings.TelemetrySettings = settings + c.tracer = tracer + c.counter = counter } exp, err := otelarrowexporter.NewFactory().CreateMetricsExporter(ctx, c.settings, &cfg.Exporter) diff --git a/lightstep/sdk/metric/exporters/prom/client.go b/lightstep/sdk/metric/exporters/prom/client.go index e760ce85..af1eedfb 100644 --- a/lightstep/sdk/metric/exporters/prom/client.go +++ b/lightstep/sdk/metric/exporters/prom/client.go @@ -17,6 +17,8 @@ package prom import ( "context" "fmt" + "time" + "github.com/lightstep/otel-launcher-go/lightstep/sdk/internal" "github.com/lightstep/otel-launcher-go/lightstep/sdk/metric" "github.com/lightstep/otel-launcher-go/lightstep/sdk/metric/data" @@ -26,10 +28,12 @@ import ( "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config/confighttp" "go.opentelemetry.io/collector/exporter" + "go.opentelemetry.io/otel" metricapi "go.opentelemetry.io/otel/metric" + metricnoop "go.opentelemetry.io/otel/metric/noop" traceapi "go.opentelemetry.io/otel/trace" + tracenoop "go.opentelemetry.io/otel/trace/noop" "go.uber.org/multierr" - "time" ) // TODO: Config, Option, and the option impls are duplicated between @@ -97,16 +101,23 @@ func NewExporter(ctx context.Context, cfg Config) (metric.PushExporter, error) { c.settings.ID = component.NewID(component.MustNewType("otel_sdk_metric_prom")) - err := internal.ConfigureSelfTelemetry( - "lightstep-go/sdk/metric", - cfg.SelfSpans, - cfg.SelfMetrics, - &c.settings, - &c.tracer, - &c.counter, - ) - if err != nil { + var mp metricapi.MeterProvider = metricnoop.NewMeterProvider() + var tp traceapi.TracerProvider = tracenoop.NewTracerProvider() + + if cfg.SelfSpans { + tp = otel.GetTracerProvider() + } + if cfg.SelfMetrics { + mp = otel.GetMeterProvider() + } + + if settings, tracer, counter, err := + internal.ConfigureSelfTelemetry("lightstep-go/sdk/metric", tp, mp); err != nil { return nil, err + } else { + c.settings.TelemetrySettings = settings + c.tracer = tracer + c.counter = counter } exp, err := prometheusexporter.NewFactory().CreateMetricsExporter(ctx, c.settings, &cfg.Exporter) diff --git a/lightstep/sdk/metric/internal/export/export.go b/lightstep/sdk/metric/internal/export/export.go index f1a9ae7b..a2262284 100644 --- a/lightstep/sdk/metric/internal/export/export.go +++ b/lightstep/sdk/metric/internal/export/export.go @@ -356,7 +356,7 @@ func ExportMetrics( if err == nil { span.SetStatus(otelcodes.Ok, state) } else { - span.SetStatus(otelcodes.Error, state) + span.SetStatus(otelcodes.Error, err.Error()) } return err } diff --git a/lightstep/sdk/trace/exporters/otlp/otelcol/client.go b/lightstep/sdk/trace/exporters/otlp/otelcol/client.go index 181377cb..76c70973 100644 --- a/lightstep/sdk/trace/exporters/otlp/otelcol/client.go +++ b/lightstep/sdk/trace/exporters/otlp/otelcol/client.go @@ -30,11 +30,14 @@ import ( "go.opentelemetry.io/collector/exporter" "go.opentelemetry.io/collector/exporter/exporterhelper" "go.opentelemetry.io/collector/processor" + "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" otelcodes "go.opentelemetry.io/otel/codes" - "go.opentelemetry.io/otel/metric" + metricapi "go.opentelemetry.io/otel/metric" + metricnoop "go.opentelemetry.io/otel/metric/noop" "go.opentelemetry.io/otel/sdk/trace" traceapi "go.opentelemetry.io/otel/trace" + tracenoop "go.opentelemetry.io/otel/trace/noop" "go.uber.org/multierr" "github.com/lightstep/otel-launcher-go/lightstep/sdk/internal" @@ -53,7 +56,7 @@ type Config struct { type ExporterOptions struct { TracerProvider traceapi.TracerProvider - MeterProvider metric.MeterProvider + MeterProvider metricapi.MeterProvider } func WithTracerProvider(tp traceapi.TracerProvider) func(*ExporterOptions) { @@ -62,7 +65,7 @@ func WithTracerProvider(tp traceapi.TracerProvider) func(*ExporterOptions) { } } -func WithMeterProvider(mp metric.MeterProvider) func(*ExporterOptions) { +func WithMeterProvider(mp metricapi.MeterProvider) func(*ExporterOptions) { return func(opts *ExporterOptions) { opts.MeterProvider = mp } @@ -75,6 +78,7 @@ type client struct { batcher processor.Traces settings exporter.Settings tracer traceapi.Tracer + counter metricapi.Int64Counter } func (c *client) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) error { @@ -104,11 +108,12 @@ func (c *client) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) er attribute.Bool("success", success), attribute.String("state", state), } + c.counter.Add(ctx, int64(count), metricapi.WithAttributes(attrs...)) span.SetAttributes(append(attrs, attribute.Int("num_spans", count))...) if err == nil { span.SetStatus(otelcodes.Ok, state) } else { - span.SetStatus(otelcodes.Error, state) + span.SetStatus(otelcodes.Error, err.Error()) } return err } @@ -201,7 +206,10 @@ func WithTLSSetting(tlss configtls.ClientConfig) Option { } func NewExporter(ctx context.Context, cfg Config, opts ...func(options *ExporterOptions)) (trace.SpanExporter, error) { - options := ExporterOptions{} + options := ExporterOptions{ + MeterProvider: nil, + TracerProvider: nil, + } for _, opt := range opts { opt(&options) } @@ -214,16 +222,31 @@ func NewExporter(ctx context.Context, cfg Config, opts ...func(options *Exporter c.settings.ID = component.NewID(component.MustNewType("otel_sdk_trace_otlp")) } - err := internal.ConfigureSelfTelemetry( - "lightstep-go/sdk/trace", - cfg.SelfSpans, - cfg.SelfMetrics, - &c.settings, - &c.tracer, - nil, - ) - if err != nil { + if options.MeterProvider == nil { + if cfg.SelfMetrics { + options.MeterProvider = otel.GetMeterProvider() + } else { + options.MeterProvider = metricnoop.NewMeterProvider() + } + } + if options.TracerProvider == nil { + if cfg.SelfSpans { + options.TracerProvider = otel.GetTracerProvider() + } else { + options.TracerProvider = tracenoop.NewTracerProvider() + } + } + + if settings, tracer, counter, err := + internal.ConfigureSelfTelemetry( + "lightstep-go/sdk/trace", + options.TracerProvider, + options.MeterProvider); err != nil { return nil, err + } else { + c.settings.TelemetrySettings = settings + c.tracer = tracer + c.counter = counter } exp, err := otelarrowexporter.NewFactory().CreateTracesExporter(ctx, c.settings, &cfg.Exporter)