@@ -16,6 +16,7 @@ package v2
1616
1717import (
1818 "fmt"
19+ "github.com/fstab/grok_exporter/tailer/glob"
1920 "github.com/fstab/grok_exporter/template"
2021 "gopkg.in/yaml.v2"
2122 "strconv"
@@ -57,8 +58,8 @@ type GlobalConfig struct {
5758}
5859
5960type InputConfig struct {
60- Type string `yaml:",omitempty"`
61- Path string `yaml:",omitempty "`
61+ Type string `yaml:",omitempty"`
62+ PathsAndGlobs `yaml:",inline "`
6263 FailOnMissingLogfileString string `yaml:"fail_on_missing_logfile,omitempty"` // cannot use bool directly, because yaml.v2 doesn't support true as default value.
6364 FailOnMissingLogfile bool `yaml:"-"`
6465 Readall bool `yaml:",omitempty"`
@@ -76,10 +77,17 @@ type GrokConfig struct {
7677 AdditionalPatterns []string `yaml:"additional_patterns,omitempty"`
7778}
7879
80+ type PathsAndGlobs struct {
81+ Path string `yaml:",omitempty"`
82+ Paths []string `yaml:",omitempty"`
83+ Globs []glob.Glob `yaml:"-"`
84+ }
85+
7986type MetricConfig struct {
80- Type string `yaml:",omitempty"`
81- Name string `yaml:",omitempty"`
82- Help string `yaml:",omitempty"`
87+ Type string `yaml:",omitempty"`
88+ Name string `yaml:",omitempty"`
89+ Help string `yaml:",omitempty"`
90+ PathsAndGlobs `yaml:",inline"`
8391 Match string `yaml:",omitempty"`
8492 Retention time.Duration `yaml:",omitempty"` // implicitly parsed with time.ParseDuration()
8593 Value string `yaml:",omitempty"`
@@ -184,22 +192,53 @@ func (cfg *Config) validate() error {
184192 return nil
185193}
186194
195+ func validateGlobs (p * PathsAndGlobs , optional bool , prefix string ) error {
196+ if ! optional && len (p .Path ) == 0 && len (p .Paths ) == 0 {
197+ return fmt .Errorf ("%v: one of 'path' or 'paths' is required" , prefix )
198+ }
199+ if len (p .Path ) > 0 && len (p .Paths ) > 0 {
200+ return fmt .Errorf ("%v: use either 'path' or 'paths' but not both" , prefix )
201+ }
202+ if len (p .Path ) > 0 {
203+ parsedGlob , err := glob .Parse (p .Path )
204+ if err != nil {
205+ return fmt .Errorf ("%v: %v" , prefix , err )
206+ }
207+ p .Globs = []glob.Glob {parsedGlob }
208+ }
209+ if len (p .Paths ) > 0 {
210+ p .Globs = make ([]glob.Glob , 0 , len (p .Paths ))
211+ for _ , path := range p .Paths {
212+ parsedGlob , err := glob .Parse (path )
213+ if err != nil {
214+ return fmt .Errorf ("%v: %v" , prefix , err )
215+ }
216+ p .Globs = append (p .Globs , parsedGlob )
217+ }
218+ }
219+ return nil
220+ }
221+
187222func (c * InputConfig ) validate () error {
188223 var err error
189224 switch {
190225 case c .Type == inputTypeStdin :
191- if c .Path != "" {
226+ if len ( c .Path ) > 0 {
192227 return fmt .Errorf ("invalid input configuration: cannot use 'input.path' when 'input.type' is stdin" )
193228 }
229+ if len (c .Paths ) > 0 {
230+ return fmt .Errorf ("invalid input configuration: cannot use 'input.paths' when 'input.type' is stdin" )
231+ }
194232 if c .Readall {
195233 return fmt .Errorf ("invalid input configuration: cannot use 'input.readall' when 'input.type' is stdin" )
196234 }
197235 if c .PollIntervalSeconds != "" {
198236 return fmt .Errorf ("invalid input configuration: cannot use 'input.poll_interval_seconds' when 'input.type' is stdin" )
199237 }
200238 case c .Type == inputTypeFile :
201- if c .Path == "" {
202- return fmt .Errorf ("invalid input configuration: 'input.path' is required for input type \" file\" " )
239+ err = validateGlobs (& c .PathsAndGlobs , false , "invalid input configuration" )
240+ if err != nil {
241+ return err
203242 }
204243 if len (c .PollIntervalSeconds ) > 0 { // TODO: Use duration directly, as with other durations in the config file
205244 nSeconds , err := strconv .Atoi (c .PollIntervalSeconds )
@@ -215,6 +254,18 @@ func (c *InputConfig) validate() error {
215254 }
216255 }
217256 case c .Type == inputTypeWebhook :
257+ if c .Path != "" {
258+ return fmt .Errorf ("invalid input configuration: cannot use 'input.path' when 'input.type' is %v" , inputTypeWebhook )
259+ }
260+ if len (c .Paths ) > 0 {
261+ return fmt .Errorf ("invalid input configuration: cannot use 'input.paths' when 'input.type' is %v" , inputTypeWebhook )
262+ }
263+ if c .Readall {
264+ return fmt .Errorf ("invalid input configuration: cannot use 'input.readall' when 'input.type' is %v" , inputTypeWebhook )
265+ }
266+ if c .PollIntervalSeconds != "" {
267+ return fmt .Errorf ("invalid input configuration: cannot use 'input.poll_interval_seconds' when 'input.type' is %v" , inputTypeWebhook )
268+ }
218269 if c .WebhookPath == "" {
219270 return fmt .Errorf ("invalid input configuration: 'input.webhook_path' is required for input type \" webhook\" " )
220271 } else if c .WebhookPath [0 ] != '/' {
@@ -249,7 +300,8 @@ func (c *MetricsConfig) validate() error {
249300 return fmt .Errorf ("Invalid metrics configuration: 'metrics' must not be empty." )
250301 }
251302 metricNames := make (map [string ]bool )
252- for _ , metric := range * c {
303+ for i , _ := range * c {
304+ metric := & (* c )[i ] // validate modifies the metric, therefore we must use it by reference here.
253305 err := metric .validate ()
254306 if err != nil {
255307 return err
@@ -259,6 +311,14 @@ func (c *MetricsConfig) validate() error {
259311 return fmt .Errorf ("Invalid metric configuration: metric '%v' defined twice." , metric .Name )
260312 }
261313 metricNames [metric .Name ] = true
314+
315+ if len (metric .Path ) > 0 && len (metric .Paths ) > 0 {
316+ return fmt .Errorf ("invalid metric configuration: metric %v defines both path and paths, you should use either one or the other" , metric .Name )
317+ }
318+ if len (metric .Path ) > 0 {
319+ metric .Paths = []string {metric .Path }
320+ metric .Path = ""
321+ }
262322 }
263323 return nil
264324}
@@ -274,6 +334,10 @@ func (c *MetricConfig) validate() error {
274334 case c .Match == "" :
275335 return fmt .Errorf ("Invalid metric configuration: 'metrics.match' must not be empty." )
276336 }
337+ err := validateGlobs (& c .PathsAndGlobs , true , fmt .Sprintf ("invalid metric configuration: %v" , c .Name ))
338+ if err != nil {
339+ return err
340+ }
277341 var hasValue , cumulativeAllowed , bucketsAllowed , quantilesAllowed bool
278342 switch c .Type {
279343 case "counter" :
@@ -411,6 +475,16 @@ func (cfg *Config) String() string {
411475 if stripped .Server .Path == "/metrics" {
412476 stripped .Server .Path = ""
413477 }
478+ if len (stripped .Input .Paths ) == 1 {
479+ stripped .Input .Path = stripped .Input .Paths [0 ]
480+ stripped .Input .Paths = nil
481+ }
482+ for i := range stripped .Metrics {
483+ if len (stripped .Metrics [i ].Paths ) == 1 {
484+ stripped .Metrics [i ].Path = stripped .Metrics [i ].Paths [i ]
485+ stripped .Metrics [i ].Paths = nil
486+ }
487+ }
414488 return stripped .marshalToString ()
415489}
416490
0 commit comments