@@ -37,9 +37,12 @@ type Exporter interface {
3737 // UpdateTarget updates the targets field
3838 UpdateTarget ([]Target )
3939 // SetJobFilters sets the jobFilters field
40- SetJobFilters ([]string )
40+ SetJobFilters ([]string ) error
4141 // DropErrorMetrics resets the scrape_errors_total metric
4242 DropErrorMetrics ()
43+ // FilterScrapeErrorsTotal filters the scrape_errors_total metric family to only include metrics for the jobs in
44+ // the jobFilters list.
45+ FilterScrapeErrorsTotal ([]* dto.MetricFamily ) []* dto.MetricFamily
4346}
4447
4548type exporter struct {
@@ -67,7 +70,8 @@ func NewExporter(configFile string) (Exporter, error) {
6770
6871 var targets []Target
6972 if c .Target != nil {
70- target , err := NewTarget ("" , c .Target .Name , "" , string (c .Target .DSN ), c .Target .Collectors (), nil , c .Globals , c .Target .EnablePing )
73+ target , err := NewTarget ("" , c .Target .Name , "" , string (c .Target .DSN ),
74+ c .Target .Collectors (), nil , c .Globals , c .Target .EnablePing )
7175 if err != nil {
7276 return nil , err
7377 }
@@ -91,7 +95,7 @@ func NewExporter(configFile string) (Exporter, error) {
9195 return & exporter {
9296 config : c ,
9397 targets : targets ,
94- jobFilters : [] string {} ,
98+ jobFilters : nil ,
9599 ctx : context .Background (),
96100 }, nil
97101}
@@ -181,6 +185,7 @@ func (e *exporter) Gather() ([]*dto.MetricFamily, error) {
181185 for _ , mf := range dtoMetricFamilies {
182186 result = append (result , mf )
183187 }
188+
184189 return result , errs
185190}
186191
@@ -192,13 +197,49 @@ func (e *exporter) filterTargets(jf []string) {
192197 filteredTargets = append (filteredTargets , target )
193198 }
194199 }
195- if len (filteredTargets ) == 0 {
196- slog .Error ("No targets found for job filters. Nothing to scrape." )
197- }
198200 e .targets = filteredTargets
199201 }
200202}
201203
204+ // FilterScrapeErrorsTotal filters the scrape_errors_total metric family to only include metrics for the jobs in the
205+ // jobFilters list. If jobFilters is empty it returns the original metric families unmodified.
206+ func (e * exporter ) FilterScrapeErrorsTotal (mfs []* dto.MetricFamily ) []* dto.MetricFamily {
207+ if len (e .jobFilters ) == 0 {
208+ return mfs
209+ }
210+
211+ result := make ([]* dto.MetricFamily , 0 , len (mfs ))
212+ for _ , mf := range mfs {
213+ if mf .GetName () != "scrape_errors_total" {
214+ result = append (result , mf )
215+ continue
216+ }
217+
218+ // Filter metrics in this family to only include those with a job label in the jobFilters list.
219+ filtered := make ([]* dto.Metric , 0 , len (mf .Metric ))
220+ for _ , metric := range mf .Metric {
221+ for _ , label := range metric .Label {
222+ if label .GetName () == "job" {
223+ if slices .Contains (e .jobFilters , label .GetValue ()) {
224+ filtered = append (filtered , metric )
225+ }
226+ break
227+ }
228+ }
229+ }
230+
231+ if len (filtered ) > 0 {
232+ result = append (result , & dto.MetricFamily {
233+ Name : mf .Name ,
234+ Help : mf .Help ,
235+ Type : mf .Type ,
236+ Metric : filtered ,
237+ })
238+ }
239+ }
240+ return result
241+ }
242+
202243// Config implements Exporter.
203244func (e * exporter ) Config () * config.Config {
204245 return e .config
@@ -210,8 +251,31 @@ func (e *exporter) UpdateTarget(target []Target) {
210251}
211252
212253// SetJobFilters implements Exporter.
213- func (e * exporter ) SetJobFilters (filters []string ) {
254+ func (e * exporter ) SetJobFilters (filters []string ) error {
255+ // If the filters list contains a single empty string, treat it as no filters.
256+ if len (filters ) == 0 || (len (filters ) == 1 && filters [0 ] == "" ) {
257+ slog .Debug ("Received empty job filter, treating as no filters" )
258+ e .jobFilters = nil
259+ return nil
260+ }
261+
262+ // Single target mode has no jobs - filters are not applicable
263+ if len (e .config .Jobs ) == 0 {
264+ slog .Warn ("Job filters are not applicable in single target mode, ignoring" , "filters" , filters )
265+ e .jobFilters = nil
266+ return nil
267+ }
268+
269+ for _ , name := range filters {
270+ if ! slices .ContainsFunc (e .config .Jobs , func (j * config.JobConfig ) bool {
271+ return j .Name == name
272+ }) {
273+ return fmt .Errorf ("invalid job name: %s" , name )
274+ }
275+ }
276+
214277 e .jobFilters = filters
278+ return nil
215279}
216280
217281// DropErrorMetrics implements Exporter.
@@ -233,8 +297,12 @@ func registerScrapeErrorMetric() *prometheus.CounterVec {
233297// split comma separated list of key=value pairs and return a map of key value pairs
234298func parseContextLog (list string ) map [string ]string {
235299 m := make (map [string ]string )
236- for _ , item := range strings .Split (list , "," ) {
300+ for item := range strings .SplitSeq (list , "," ) {
237301 parts := strings .SplitN (item , "=" , 2 )
302+ if len (parts ) != 2 {
303+ slog .Warn ("Invalid context log item, ignoring" , "item" , item )
304+ continue
305+ }
238306 m [parts [0 ]] = parts [1 ]
239307 }
240308 return m
@@ -243,8 +311,5 @@ func parseContextLog(list string) map[string]string {
243311// TrimMissingCtx trims the leading comma and space from the log context string.
244312// Leading comma appears when previous parameter is undefined, which is a side-effect of running in single target mode.
245313func TrimMissingCtx (logContext string ) string {
246- if strings .HasPrefix (logContext , "," ) {
247- logContext = strings .TrimLeft (logContext , ", " )
248- }
249- return logContext
314+ return strings .TrimPrefix (logContext , ", " )
250315}
0 commit comments