2626 scrapeErrorsMetric * prometheus.CounterVec
2727)
2828
29- // Exporter is a prometheus.Gatherer that gathers SQL metrics from targets and merges them with the default registry.
29+ // Exporter is a prometheus.Gatherer that gathers SQL metrics from targets and merges them with the custom registry.
3030type Exporter interface {
3131 prometheus.Gatherer
3232
@@ -50,11 +50,12 @@ type exporter struct {
5050 targets []Target
5151 jobFilters []string
5252
53- ctx context.Context
53+ ctx context.Context
54+ registry prometheus.Registerer
5455}
5556
5657// NewExporter returns a new Exporter with the provided config.
57- func NewExporter (configFile string ) (Exporter , error ) {
58+ func NewExporter (configFile string , registry prometheus. Registerer ) (Exporter , error ) {
5859 c , err := config .Load (configFile )
5960 if err != nil {
6061 return nil , err
@@ -90,13 +91,17 @@ func NewExporter(configFile string) (Exporter, error) {
9091 }
9192 }
9293
93- scrapeErrorsMetric = registerScrapeErrorMetric ()
94+ scrapeErrorsMetric , err = registerScrapeErrorMetric (registry )
95+ if err != nil {
96+ return nil , err
97+ }
9498
9599 return & exporter {
96100 config : c ,
97101 targets : targets ,
98102 jobFilters : nil ,
99103 ctx : context .Background (),
104+ registry : registry ,
100105 }, nil
101106}
102107
@@ -106,26 +111,28 @@ func (e *exporter) WithContext(ctx context.Context) Exporter {
106111 targets : e .targets ,
107112 jobFilters : e .jobFilters ,
108113 ctx : ctx ,
114+ registry : e .registry ,
109115 }
110116}
111117
112- // Gather implements prometheus.Gatherer.
118+ // Gather implements prometheus.Gatherer. Should be called with a context-aware Exporter returned by WithContext() to
119+ // ensure the context is respected by collectors.
113120func (e * exporter ) Gather () ([]* dto.MetricFamily , error ) {
114121 var (
115122 metricChan = make (chan Metric , capMetricChan )
116123 errs prometheus.MultiError
117124 )
118125
119- // Filter out jobs that are not in the jobFilters list
120- e . filterTargets ( e . jobFilters )
126+ // Take a local snapshot to avoid mutating e.targets while collectors are running.
127+ targets := e . filteredTargets ( )
121128
122- if len (e . targets ) == 0 {
129+ if len (targets ) == 0 {
123130 return nil , errors .New ("no targets found" )
124131 }
125132
126133 var wg sync.WaitGroup
127- wg .Add (len (e . targets ))
128- for _ , t := range e . targets {
134+ wg .Add (len (targets ))
135+ for _ , t := range targets {
129136 go func (target Target ) {
130137 defer wg .Done ()
131138 target .Collect (e .ctx , metricChan )
@@ -189,16 +196,18 @@ func (e *exporter) Gather() ([]*dto.MetricFamily, error) {
189196 return result , errs
190197}
191198
192- func (e * exporter ) filterTargets (jf []string ) {
193- if len (jf ) > 0 {
194- var filteredTargets []Target
195- for _ , target := range e .targets {
196- if slices .Contains (jf , target .JobGroup ()) {
197- filteredTargets = append (filteredTargets , target )
198- }
199+ func (e * exporter ) filteredTargets () []Target {
200+ if len (e .jobFilters ) == 0 {
201+ return e .targets
202+ }
203+
204+ var filtered []Target
205+ for _ , target := range e .targets {
206+ if slices .Contains (e .jobFilters , target .JobGroup ()) {
207+ filtered = append (filtered , target )
199208 }
200- e .targets = filteredTargets
201209 }
210+ return filtered
202211}
203212
204213// FilterScrapeErrorsTotal filters the scrape_errors_total metric family to only include metrics for the jobs in the
@@ -285,13 +294,22 @@ func (e *exporter) DropErrorMetrics() {
285294}
286295
287296// registerScrapeErrorMetric registers the metrics for the exporter itself.
288- func registerScrapeErrorMetric () * prometheus.CounterVec {
297+ func registerScrapeErrorMetric (registry prometheus. Registerer ) ( * prometheus.CounterVec , error ) {
289298 scrapeErrors := prometheus .NewCounterVec (prometheus.CounterOpts {
290299 Name : "scrape_errors_total" ,
291300 Help : "Total number of scrape errors per job, target, collector and query" ,
292301 }, svcMetricLabels )
293- SvcRegistry .MustRegister (scrapeErrors )
294- return scrapeErrors
302+
303+ if err := registry .Register (scrapeErrors ); err != nil {
304+ var alreadyRegisteredErr prometheus.AlreadyRegisteredError
305+ if errors .As (err , & alreadyRegisteredErr ) {
306+ slog .Debug ("scrape_errors_total metric already registered, using existing metric" )
307+ return alreadyRegisteredErr .ExistingCollector .(* prometheus.CounterVec ), nil
308+ }
309+ slog .Error ("failed to register scrape_errors_total metric" , "error" , err )
310+ return nil , err
311+ }
312+ return scrapeErrors , nil
295313}
296314
297315// split comma separated list of key=value pairs and return a map of key value pairs
0 commit comments