diff --git a/clients/pkg/promtail/promtail.go b/clients/pkg/promtail/promtail.go index 73e52f21703e..65b543c05e84 100644 --- a/clients/pkg/promtail/promtail.go +++ b/clients/pkg/promtail/promtail.go @@ -18,6 +18,7 @@ import ( "github.com/grafana/loki/v3/clients/pkg/promtail/api" "github.com/grafana/loki/v3/clients/pkg/promtail/client" "github.com/grafana/loki/v3/clients/pkg/promtail/config" + "github.com/grafana/loki/v3/clients/pkg/promtail/scrapeconfig" "github.com/grafana/loki/v3/clients/pkg/promtail/server" "github.com/grafana/loki/v3/clients/pkg/promtail/targets" "github.com/grafana/loki/v3/clients/pkg/promtail/targets/target" @@ -139,10 +140,16 @@ func (p *Promtail) reloadConfig(cfg *config.Config) error { } cfg.Setup(p.logger) + var err error + err = scrapeconfig.ValidateJobName(cfg.ScrapeConfig) + if err != nil { + return err + } + if cfg.LimitsConfig.ReadlineRateEnabled { stages.SetReadLineRateLimiter(cfg.LimitsConfig.ReadlineRate, cfg.LimitsConfig.ReadlineBurst, cfg.LimitsConfig.ReadlineRateDrop) } - var err error + // entryHandlers contains all sinks were scraped log entries should get to var entryHandlers = []api.EntryHandler{} diff --git a/clients/pkg/promtail/scrapeconfig/scrapeconfig.go b/clients/pkg/promtail/scrapeconfig/scrapeconfig.go index b2466b83791e..67b4f0898034 100644 --- a/clients/pkg/promtail/scrapeconfig/scrapeconfig.go +++ b/clients/pkg/promtail/scrapeconfig/scrapeconfig.go @@ -1,6 +1,7 @@ package scrapeconfig import ( + "errors" "fmt" "reflect" "time" @@ -26,6 +27,7 @@ import ( "github.com/prometheus/prometheus/discovery/triton" "github.com/prometheus/prometheus/discovery/zookeeper" "github.com/prometheus/prometheus/model/relabel" + "github.com/prometheus/prometheus/util/strutil" "github.com/grafana/loki/v3/clients/pkg/logentry/stages" "github.com/grafana/loki/v3/clients/pkg/promtail/discovery/consulagent" @@ -484,3 +486,22 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { return nil } + +func ValidateJobName(scrapeConfigs []Config) error { + jobNames := map[string]struct{}{} + for i, cfg := range scrapeConfigs { + if cfg.JobName == "" { + return errors.New("`job_name` must be defined for the scrape_config with a " + + "unique name, " + + "at least one scrape_config has no `job_name` defined") + } + if _, ok := jobNames[cfg.JobName]; ok { + return fmt.Errorf("`job_name` must be unique for each scrape_config, "+ + "a duplicate `job_name` of %s was found", cfg.JobName) + } + jobNames[cfg.JobName] = struct{}{} + + scrapeConfigs[i].JobName = strutil.SanitizeLabelName(cfg.JobName) + } + return nil +} diff --git a/clients/pkg/promtail/scrapeconfig/scrapeconfig_test.go b/clients/pkg/promtail/scrapeconfig/scrapeconfig_test.go index f8898d86aa59..f098aae9a639 100644 --- a/clients/pkg/promtail/scrapeconfig/scrapeconfig_test.go +++ b/clients/pkg/promtail/scrapeconfig/scrapeconfig_test.go @@ -142,3 +142,68 @@ func TestLoadConfig(t *testing.T) { require.NotZero(t, len(config.PipelineStages)) } + +func Test_validateJobName(t *testing.T) { + tests := []struct { + name string + configs []Config + // Only validated against the first job in the provided scrape configs + expectedJob string + wantErr bool + }{ + { + name: "valid with spaces removed", + configs: []Config{ + { + JobName: "jobby job job", + }, + }, + wantErr: false, + expectedJob: "jobby_job_job", + }, + { + name: "missing job", + configs: []Config{ + {}, + }, + wantErr: true, + }, + { + name: "duplicate job", + configs: []Config{ + { + JobName: "job1", + }, + { + JobName: "job1", + }, + }, + wantErr: true, + }, + { + name: "validate with special characters", + configs: []Config{ + { + JobName: "job$1-2!3@4*job", + }, + }, + wantErr: false, + expectedJob: "job_1_2_3_4_job", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateJobName(tt.configs) + if (err != nil) != tt.wantErr { + t.Errorf("validateJobName() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr { + if tt.configs[0].JobName != tt.expectedJob { + t.Errorf("Expected to find a job with name %v but did not find it", tt.expectedJob) + return + } + } + }) + } +}