Prevent poller from dying from an error
This commit is contained in:
parent
b64179cc07
commit
d1a1de9bcc
|
|
@ -103,7 +103,7 @@
|
||||||
name = "golang.org/x/sys"
|
name = "golang.org/x/sys"
|
||||||
packages = ["windows"]
|
packages = ["windows"]
|
||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "ce4227a45e2eb77e5c847278dcc6a626742e2945"
|
revision = "eeba5f6aabab6d6594a9191d6bfeaca5fa6a8248"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:87738e338f505d3e3be1f80d36b53f3c4e73be9b7ad4ccae46abbe9ef04f3f71"
|
digest = "1:87738e338f505d3e3be1f80d36b53f3c4e73be9b7ad4ccae46abbe9ef04f3f71"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,13 @@
|
||||||
package poller
|
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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
|
@ -41,12 +49,11 @@ const ENVConfigPrefix = "UP_"
|
||||||
|
|
||||||
// UnifiPoller contains the application startup data, and auth info for UniFi & Influx.
|
// UnifiPoller contains the application startup data, and auth info for UniFi & Influx.
|
||||||
type UnifiPoller struct {
|
type UnifiPoller struct {
|
||||||
Influx *influxunifi.InfluxUnifi
|
Influx *influxunifi.InfluxUnifi
|
||||||
Unifi *unifi.Unifi
|
Unifi *unifi.Unifi
|
||||||
Flag *Flag
|
Flag *Flag
|
||||||
Config *Config
|
Config *Config
|
||||||
errorCount int
|
LastCheck time.Time
|
||||||
LastCheck time.Time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flag represents the CLI args available and their settings.
|
// 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.
|
// ParseENV copies environment variables into configuration values.
|
||||||
// This is useful for Docker users that find it easier to pass ENV variables
|
// 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.
|
// 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 {
|
func (c *Config) ParseENV() error {
|
||||||
t := reflect.TypeOf(Config{}) // Get tag names from the Config struct.
|
t := reflect.TypeOf(*c) // Get "types" from the Config struct.
|
||||||
// Loop each Config struct member; get reflect tag & env var value; update config.
|
for i := 0; i < t.NumField(); i++ { // Loop each Config struct member
|
||||||
for i := 0; i < t.NumField(); i++ {
|
|
||||||
tag := t.Field(i).Tag.Get("json") // Get the ENV variable name from "json" struct tag
|
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
|
tag = strings.Split(strings.ToUpper(tag), ",")[0] // Capitalize and remove ,omitempty suffix
|
||||||
env := os.Getenv(ENVConfigPrefix + tag) // Then pull value from OS.
|
env := os.Getenv(ENVConfigPrefix + tag) // Then pull value from OS.
|
||||||
if tag == "" || env == "" {
|
if tag == "" || env == "" { // Skip if either are empty.
|
||||||
continue // Skip if either are empty.
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reflect and update the u.Config struct member at position i.
|
// 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).
|
// Handle each member type appropriately (differently).
|
||||||
case "string":
|
case "string":
|
||||||
// This is a reflect package method to update a struct member by index.
|
// This is a reflect package method to update a struct member by index.
|
||||||
c.SetString(env)
|
field.SetString(env)
|
||||||
|
|
||||||
case "int":
|
case "int":
|
||||||
val, err := strconv.Atoi(env)
|
val, err := strconv.Atoi(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %v", tag, err)
|
return fmt.Errorf("%s: %v", tag, err)
|
||||||
}
|
}
|
||||||
c.Set(reflect.ValueOf(val))
|
field.Set(reflect.ValueOf(val))
|
||||||
|
|
||||||
case "[]string":
|
case "[]string":
|
||||||
c.Set(reflect.ValueOf(strings.Split(env, ",")))
|
field.Set(reflect.ValueOf(strings.Split(env, ",")))
|
||||||
|
|
||||||
case path.Base(t.PkgPath()) + ".Duration":
|
case path.Base(t.PkgPath()) + ".Duration":
|
||||||
val, err := time.ParseDuration(env)
|
val, err := time.ParseDuration(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %v", tag, err)
|
return fmt.Errorf("%s: %v", tag, err)
|
||||||
}
|
}
|
||||||
c.Set(reflect.ValueOf(Duration{val}))
|
field.Set(reflect.ValueOf(Duration{val}))
|
||||||
|
|
||||||
case "bool":
|
case "bool":
|
||||||
val, err := strconv.ParseBool(env)
|
val, err := strconv.ParseBool(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %v", tag, err)
|
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
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,6 @@ import (
|
||||||
|
|
||||||
const callDepth = 2
|
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.
|
// StringInSlice returns true if a string is in a slice.
|
||||||
func StringInSlice(str string, slice []string) bool {
|
func StringInSlice(str string, slice []string) bool {
|
||||||
for _, s := range slice {
|
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{}) {
|
func (u *UnifiPoller) LogErrorf(m string, v ...interface{}) {
|
||||||
_ = log.Output(callDepth, fmt.Sprintf("[ERROR] "+m, v...))
|
_ = log.Output(callDepth, fmt.Sprintf("[ERROR] "+m, v...))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ func (u *UnifiPoller) CollectAndProcess() error {
|
||||||
u.AugmentMetrics(metrics)
|
u.AugmentMetrics(metrics)
|
||||||
report, err := u.Influx.ReportMetrics(metrics)
|
report, err := u.Influx.ReportMetrics(metrics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
u.LogError(err, "processing metrics")
|
u.LogErrorf("processing metrics: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
u.LogInfluxReport(report)
|
u.LogInfluxReport(report)
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ const oneDecimalPoint = 10
|
||||||
|
|
||||||
// RunPrometheus starts the web server and registers the collector.
|
// RunPrometheus starts the web server and registers the collector.
|
||||||
func (u *UnifiPoller) RunPrometheus() error {
|
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())
|
http.Handle("/metrics", promhttp.Handler())
|
||||||
prometheus.MustRegister(promunifi.NewUnifiCollector(promunifi.UnifiCollectorCnfg{
|
prometheus.MustRegister(promunifi.NewUnifiCollector(promunifi.UnifiCollectorCnfg{
|
||||||
Namespace: strings.Replace(u.Config.Namespace, "-", "", -1),
|
Namespace: strings.Replace(u.Config.Namespace, "-", "", -1),
|
||||||
|
|
@ -34,8 +34,9 @@ func (u *UnifiPoller) ExportMetrics() (*metrics.Metrics, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
u.LogErrorf("collecting metrics: %v", err)
|
u.LogErrorf("collecting metrics: %v", err)
|
||||||
u.Logf("Re-authenticating to UniFi Controller")
|
u.Logf("Re-authenticating to UniFi Controller")
|
||||||
|
|
||||||
if err := u.Unifi.Login(); err != nil {
|
if err := u.Unifi.Login(); err != nil {
|
||||||
u.LogError(err, "re-authenticating")
|
u.LogErrorf("re-authenticating: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -100,55 +100,36 @@ func (u *UnifiPoller) Run() error {
|
||||||
|
|
||||||
switch strings.ToLower(u.Config.Mode) {
|
switch strings.ToLower(u.Config.Mode) {
|
||||||
default:
|
default:
|
||||||
return u.PollController()
|
u.PollController()
|
||||||
|
return nil
|
||||||
case "influxlambda", "lambdainflux", "lambda_influx", "influx_lambda":
|
case "influxlambda", "lambdainflux", "lambda_influx", "influx_lambda":
|
||||||
u.LastCheck = time.Now()
|
u.LastCheck = time.Now()
|
||||||
return u.CollectAndProcess()
|
return u.CollectAndProcess()
|
||||||
|
case "both":
|
||||||
|
go u.PollController()
|
||||||
|
fallthrough
|
||||||
case "prometheus", "exporter":
|
case "prometheus", "exporter":
|
||||||
return u.RunPrometheus()
|
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
|
// PollController runs forever, polling UniFi and pushing to InfluxDB
|
||||||
// This is started by Run() or RunBoth() after everything checks out.
|
// 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)
|
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)
|
ticker := time.NewTicker(interval)
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for u.LastCheck = range ticker.C {
|
for u.LastCheck = range ticker.C {
|
||||||
// Some users need to re-auth every interval because the cookie times out.
|
// Some users need to re-auth every interval because the cookie times out.
|
||||||
if u.Config.ReAuth {
|
if u.Config.ReAuth {
|
||||||
u.LogDebugf("Re-authenticating to UniFi Controller")
|
u.LogDebugf("Re-authenticating to UniFi Controller")
|
||||||
if err := u.Unifi.Login(); err != nil {
|
if err := u.Unifi.Login(); err != nil {
|
||||||
return err
|
u.LogErrorf("%v", err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := u.CollectAndProcess(); err != nil {
|
if err := u.CollectAndProcess(); err != nil {
|
||||||
return err
|
u.LogErrorf("%v", err)
|
||||||
}
|
|
||||||
// check for errors from the unifi polls.
|
|
||||||
if u.errorCount > 0 {
|
|
||||||
return fmt.Errorf("too many errors, stopping poller")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,16 +67,16 @@ func (u *UnifiPoller) CollectMetrics() (*metrics.Metrics, error) {
|
||||||
var err error
|
var err error
|
||||||
// Get the sites we care about.
|
// Get the sites we care about.
|
||||||
m.Sites, err = u.GetFilteredSites()
|
m.Sites, err = u.GetFilteredSites()
|
||||||
u.LogError(err, "unifi.GetSites()")
|
u.LogErrorf("unifi.GetSites(): %v", err)
|
||||||
if u.Config.SaveIDS {
|
if u.Config.SaveIDS {
|
||||||
m.IDSList, err = u.Unifi.GetIDS(m.Sites, time.Now().Add(u.Config.Interval.Duration), time.Now())
|
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.
|
// Get all the points.
|
||||||
m.Clients, err = u.Unifi.GetClients(m.Sites)
|
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)
|
m.Devices, err = u.Unifi.GetDevices(m.Sites)
|
||||||
u.LogError(err, "unifi.GetDevices()")
|
u.LogErrorf("unifi.GetDevices(): %v", err)
|
||||||
return m, err
|
return m, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue