From d1a1de9bcc3c6d6a3f4a25107c35d6d6c97f856f Mon Sep 17 00:00:00 2001 From: davidnewhall2 Date: Mon, 9 Dec 2019 00:45:46 -0800 Subject: [PATCH] Prevent poller from dying from an error --- integrations/promunifi/Gopkg.lock | 2 +- integrations/promunifi/pkg/poller/config.go | 47 ++++++++++++------- integrations/promunifi/pkg/poller/helpers.go | 11 +---- integrations/promunifi/pkg/poller/influx.go | 2 +- .../promunifi/pkg/poller/prometheus.go | 5 +- integrations/promunifi/pkg/poller/start.go | 39 ++++----------- integrations/promunifi/pkg/poller/unifi.go | 8 ++-- 7 files changed, 50 insertions(+), 64 deletions(-) diff --git a/integrations/promunifi/Gopkg.lock b/integrations/promunifi/Gopkg.lock index 08130d43..2d8bf8a7 100644 --- a/integrations/promunifi/Gopkg.lock +++ b/integrations/promunifi/Gopkg.lock @@ -103,7 +103,7 @@ name = "golang.org/x/sys" packages = ["windows"] pruneopts = "UT" - revision = "ce4227a45e2eb77e5c847278dcc6a626742e2945" + revision = "eeba5f6aabab6d6594a9191d6bfeaca5fa6a8248" [[projects]] digest = "1:87738e338f505d3e3be1f80d36b53f3c4e73be9b7ad4ccae46abbe9ef04f3f71" diff --git a/integrations/promunifi/pkg/poller/config.go b/integrations/promunifi/pkg/poller/config.go index fd914f9f..41847831 100644 --- a/integrations/promunifi/pkg/poller/config.go +++ b/integrations/promunifi/pkg/poller/config.go @@ -1,5 +1,13 @@ package poller +/* + I consider this file the pinacle example of how to allow a Go application to be configured from a file. + You can put your configuration into any file format: XML, YAML, JSON, TOML, and you can override any + struct member using an environment variable. The Duration type is also supported. All of the Config{} + and Duration{} types and methods are reusable in other projects. Just adjust the data in the struct to + meet your app's needs. See the New() procedure and Start() method in start.go for example usage. +*/ + import ( "encoding/json" "encoding/xml" @@ -41,12 +49,11 @@ const ENVConfigPrefix = "UP_" // UnifiPoller contains the application startup data, and auth info for UniFi & Influx. type UnifiPoller struct { - Influx *influxunifi.InfluxUnifi - Unifi *unifi.Unifi - Flag *Flag - Config *Config - errorCount int - LastCheck time.Time + Influx *influxunifi.InfluxUnifi + Unifi *unifi.Unifi + Flag *Flag + Config *Config + LastCheck time.Time } // Flag represents the CLI args available and their settings. @@ -110,44 +117,50 @@ func (c *Config) ParseFile(configFile string) error { // ParseENV copies environment variables into configuration values. // This is useful for Docker users that find it easier to pass ENV variables // than a specific configuration file. Uses reflection to find struct tags. +// This method uses the json struct tag member to match environment variables. +// Use a custom tag name by changing "json" below, but that's overkill for this app. func (c *Config) ParseENV() error { - t := reflect.TypeOf(Config{}) // Get tag names from the Config struct. - // Loop each Config struct member; get reflect tag & env var value; update config. - for i := 0; i < t.NumField(); i++ { + t := reflect.TypeOf(*c) // Get "types" from the Config struct. + for i := 0; i < t.NumField(); i++ { // Loop each Config struct member tag := t.Field(i).Tag.Get("json") // Get the ENV variable name from "json" struct tag tag = strings.Split(strings.ToUpper(tag), ",")[0] // Capitalize and remove ,omitempty suffix env := os.Getenv(ENVConfigPrefix + tag) // Then pull value from OS. - if tag == "" || env == "" { - continue // Skip if either are empty. + if tag == "" || env == "" { // Skip if either are empty. + continue } // Reflect and update the u.Config struct member at position i. - switch c := reflect.ValueOf(c).Elem().Field(i); c.Type().String() { + switch field := reflect.ValueOf(c).Elem().Field(i); field.Type().String() { // Handle each member type appropriately (differently). case "string": // This is a reflect package method to update a struct member by index. - c.SetString(env) + field.SetString(env) + case "int": val, err := strconv.Atoi(env) if err != nil { return fmt.Errorf("%s: %v", tag, err) } - c.Set(reflect.ValueOf(val)) + field.Set(reflect.ValueOf(val)) + case "[]string": - c.Set(reflect.ValueOf(strings.Split(env, ","))) + field.Set(reflect.ValueOf(strings.Split(env, ","))) + case path.Base(t.PkgPath()) + ".Duration": val, err := time.ParseDuration(env) if err != nil { return fmt.Errorf("%s: %v", tag, err) } - c.Set(reflect.ValueOf(Duration{val})) + field.Set(reflect.ValueOf(Duration{val})) + case "bool": val, err := strconv.ParseBool(env) if err != nil { return fmt.Errorf("%s: %v", tag, err) } - c.SetBool(val) + field.SetBool(val) } + // Add more types here if more types are added to the config struct. } return nil diff --git a/integrations/promunifi/pkg/poller/helpers.go b/integrations/promunifi/pkg/poller/helpers.go index 71421b16..92acd223 100644 --- a/integrations/promunifi/pkg/poller/helpers.go +++ b/integrations/promunifi/pkg/poller/helpers.go @@ -8,15 +8,6 @@ import ( const callDepth = 2 -// LogError logs an error and increments the error counter. -// Should be used in the poller loop. -func (u *UnifiPoller) LogError(err error, prefix string) { - if err != nil { - u.errorCount++ - _ = log.Output(callDepth, fmt.Sprintf("[ERROR] %v: %v", prefix, err)) - } -} - // StringInSlice returns true if a string is in a slice. func StringInSlice(str string, slice []string) bool { for _, s := range slice { @@ -41,7 +32,7 @@ func (u *UnifiPoller) LogDebugf(m string, v ...interface{}) { } } -// LogErrorf prints an error log entry. This is used for external library logging. +// LogErrorf prints an error log entry. func (u *UnifiPoller) LogErrorf(m string, v ...interface{}) { _ = log.Output(callDepth, fmt.Sprintf("[ERROR] "+m, v...)) } diff --git a/integrations/promunifi/pkg/poller/influx.go b/integrations/promunifi/pkg/poller/influx.go index 2abd83ad..c1fef118 100644 --- a/integrations/promunifi/pkg/poller/influx.go +++ b/integrations/promunifi/pkg/poller/influx.go @@ -42,7 +42,7 @@ func (u *UnifiPoller) CollectAndProcess() error { u.AugmentMetrics(metrics) report, err := u.Influx.ReportMetrics(metrics) if err != nil { - u.LogError(err, "processing metrics") + u.LogErrorf("processing metrics: %v", err) return err } u.LogInfluxReport(report) diff --git a/integrations/promunifi/pkg/poller/prometheus.go b/integrations/promunifi/pkg/poller/prometheus.go index 45dd1176..944e4c34 100644 --- a/integrations/promunifi/pkg/poller/prometheus.go +++ b/integrations/promunifi/pkg/poller/prometheus.go @@ -15,7 +15,7 @@ const oneDecimalPoint = 10 // RunPrometheus starts the web server and registers the collector. func (u *UnifiPoller) RunPrometheus() error { - u.Logf("Exporting Measurements at https://%s/metrics for Prometheus", u.Config.HTTPListen) + u.Logf("Exporting Measurements for Prometheus at https://%s/metrics", u.Config.HTTPListen) http.Handle("/metrics", promhttp.Handler()) prometheus.MustRegister(promunifi.NewUnifiCollector(promunifi.UnifiCollectorCnfg{ Namespace: strings.Replace(u.Config.Namespace, "-", "", -1), @@ -34,8 +34,9 @@ func (u *UnifiPoller) ExportMetrics() (*metrics.Metrics, error) { if err != nil { u.LogErrorf("collecting metrics: %v", err) u.Logf("Re-authenticating to UniFi Controller") + if err := u.Unifi.Login(); err != nil { - u.LogError(err, "re-authenticating") + u.LogErrorf("re-authenticating: %v", err) return nil, err } diff --git a/integrations/promunifi/pkg/poller/start.go b/integrations/promunifi/pkg/poller/start.go index 1e1719c8..a4953475 100644 --- a/integrations/promunifi/pkg/poller/start.go +++ b/integrations/promunifi/pkg/poller/start.go @@ -100,55 +100,36 @@ func (u *UnifiPoller) Run() error { switch strings.ToLower(u.Config.Mode) { default: - return u.PollController() + u.PollController() + return nil case "influxlambda", "lambdainflux", "lambda_influx", "influx_lambda": u.LastCheck = time.Now() return u.CollectAndProcess() + case "both": + go u.PollController() + fallthrough case "prometheus", "exporter": return u.RunPrometheus() - case "both": - return u.RunBoth() } } -// RunBoth starts the prometheus exporter and influxdb exporter at the same time. -// This will likely double the amount of polls your controller receives. -func (u *UnifiPoller) RunBoth() error { - e := make(chan error) - defer close(e) - go func() { - e <- u.RunPrometheus() - }() - go func() { - e <- u.PollController() - }() - // If either method returns an error (even nil), bail out. - return <-e -} - // PollController runs forever, polling UniFi and pushing to InfluxDB // This is started by Run() or RunBoth() after everything checks out. -func (u *UnifiPoller) PollController() error { +func (u *UnifiPoller) PollController() { interval := u.Config.Interval.Round(time.Second) - log.Printf("[INFO] Everything checks out! Poller started, interval: %v", interval) + log.Printf("[INFO] Everything checks out! Poller started, InfluxDB interval: %v", interval) ticker := time.NewTicker(interval) - defer ticker.Stop() - for u.LastCheck = range ticker.C { // Some users need to re-auth every interval because the cookie times out. if u.Config.ReAuth { u.LogDebugf("Re-authenticating to UniFi Controller") if err := u.Unifi.Login(); err != nil { - return err + u.LogErrorf("%v", err) + continue } } if err := u.CollectAndProcess(); err != nil { - return err - } - // check for errors from the unifi polls. - if u.errorCount > 0 { - return fmt.Errorf("too many errors, stopping poller") + u.LogErrorf("%v", err) } } - return nil } diff --git a/integrations/promunifi/pkg/poller/unifi.go b/integrations/promunifi/pkg/poller/unifi.go index 73708243..5873ffa0 100644 --- a/integrations/promunifi/pkg/poller/unifi.go +++ b/integrations/promunifi/pkg/poller/unifi.go @@ -67,16 +67,16 @@ func (u *UnifiPoller) CollectMetrics() (*metrics.Metrics, error) { var err error // Get the sites we care about. m.Sites, err = u.GetFilteredSites() - u.LogError(err, "unifi.GetSites()") + u.LogErrorf("unifi.GetSites(): %v", err) if u.Config.SaveIDS { m.IDSList, err = u.Unifi.GetIDS(m.Sites, time.Now().Add(u.Config.Interval.Duration), time.Now()) - u.LogError(err, "unifi.GetIDS()") + u.LogErrorf("unifi.GetIDS(): %v", err) } // Get all the points. m.Clients, err = u.Unifi.GetClients(m.Sites) - u.LogError(err, "unifi.GetClients()") + u.LogErrorf("unifi.GetClients(): %v", err) m.Devices, err = u.Unifi.GetDevices(m.Sites) - u.LogError(err, "unifi.GetDevices()") + u.LogErrorf("unifi.GetDevices(): %v", err) return m, err }