Prevent poller from dying from an error

This commit is contained in:
davidnewhall2 2019-12-09 00:45:46 -08:00
parent b64179cc07
commit d1a1de9bcc
7 changed files with 50 additions and 64 deletions

View File

@ -103,7 +103,7 @@
name = "golang.org/x/sys"
packages = ["windows"]
pruneopts = "UT"
revision = "ce4227a45e2eb77e5c847278dcc6a626742e2945"
revision = "eeba5f6aabab6d6594a9191d6bfeaca5fa6a8248"
[[projects]]
digest = "1:87738e338f505d3e3be1f80d36b53f3c4e73be9b7ad4ccae46abbe9ef04f3f71"

View File

@ -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"
@ -45,7 +53,6 @@ type UnifiPoller struct {
Unifi *unifi.Unifi
Flag *Flag
Config *Config
errorCount int
LastCheck time.Time
}
@ -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

View File

@ -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...))
}

View File

@ -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)

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}