diff --git a/api.go b/api.go new file mode 100644 index 0000000..417ad69 --- /dev/null +++ b/api.go @@ -0,0 +1,7 @@ +package cachet + +type CachetAPI struct { + Url string `json:"api_url"` + Token string `json:"api_token"` + Insecure bool `json:"insecure"` +} diff --git a/cli/main.go b/cli/main.go index 35a4c0d..74cd08c 100644 --- a/cli/main.go +++ b/cli/main.go @@ -3,40 +3,60 @@ package main import ( "encoding/json" "errors" - "flag" - "fmt" "io/ioutil" - "log" "net/http" "net/url" "os" "os/signal" "sync" + "strings" + + "github.com/Sirupsen/logrus" cachet "github.com/castawaylabs/cachet-monitor" + docopt "github.com/docopt/docopt-go" + yaml "gopkg.in/yaml.v2" ) -var configPath string -var systemName string -var logPath string +const usage = `cachet-monitor + +Usage: + cachet-monitor (-c PATH | --config PATH) [--log=LOGPATH] [--name=NAME] + cachet-monitor -h | --help | --version + cachet-monitor print-config + +Arguments: + PATH path to config.yml + LOGPATH path to log output (defaults to STDOUT) + NAME name of this logger + +Examples: + cachet-monitor -c /root/cachet-monitor.yml + cachet-monitor -c /root/cachet-monitor.yml --log=/var/log/cachet-monitor.log --name="development machine" + +Options: + -c PATH.yml --config PATH Path to configuration file + -h --help Show this screen. + --version Show version + print-config Print example configuration + +Environment varaibles: + CACHET_API override API url from configuration + CACHET_TOKEN override API token from configuration + CACHET_DEV set to enable dev logging` func main() { - flag.StringVar(&configPath, "c", "/etc/cachet-monitor.config.json", "Config path") - flag.StringVar(&systemName, "name", "", "System Name") - flag.StringVar(&logPath, "log", "", "Log path") - flag.Parse() + arguments, _ := docopt.Parse(usage, nil, true, "cachet-monitor", false) - cfg, err := getConfiguration(configPath) + cfg, err := getConfiguration(arguments["--config"].(string)) if err != nil { - panic(err) + logrus.Panicf("Unable to start (reading config): %v", err) } - if len(systemName) > 0 { - cfg.SystemName = systemName - } - if len(logPath) > 0 { - cfg.LogPath = logPath + if name := arguments["--name"]; name != nil { + cfg.SystemName = name.(string) } + logrus.SetOutput(getLogger(arguments["--log"])) if len(os.Getenv("CACHET_API")) > 0 { cfg.APIUrl = os.Getenv("CACHET_API") @@ -44,29 +64,39 @@ func main() { if len(os.Getenv("CACHET_TOKEN")) > 0 { cfg.APIToken = os.Getenv("CACHET_TOKEN") } + if len(os.Getenv("CACHET_DEV")) > 0 { + logrus.SetLevel(logrus.DebugLevel) + } if err := cfg.ValidateConfiguration(); err != nil { panic(err) } - cfg.Logger.Printf("System: %s\nAPI: %s\nMonitors: %d\n\n", cfg.SystemName, cfg.APIUrl, len(cfg.Monitors)) + logrus.Infof("System: %s\nAPI: %s\nMonitors: %d\n\n", cfg.SystemName, cfg.APIUrl, len(cfg.Monitors)) wg := &sync.WaitGroup{} for _, mon := range cfg.Monitors { - cfg.Logger.Printf(" Starting %s: %d seconds check interval\n - %v %s (%d second/s timeout)", mon.Name, mon.CheckInterval, mon.Method, mon.URL, mon.HttpTimeout) + l := logrus.WithFields(logrus.Fields{ + "name": mon.Name, + "interval": mon.CheckInterval, + "method": mon.Method, + "url": mon.URL, + "timeout": mon.HttpTimeout, + }) + l.Info(" Starting monitor") // print features if mon.ExpectedStatusCode > 0 { - cfg.Logger.Printf(" - Expect HTTP %d", mon.ExpectedStatusCode) + l.Infof(" - Expect HTTP %d", mon.ExpectedStatusCode) } if len(mon.ExpectedBody) > 0 { - cfg.Logger.Printf(" - Expect Body to match \"%v\"", mon.ExpectedBody) + l.Infof(" - Expect Body to match \"%v\"", mon.ExpectedBody) } if mon.MetricID > 0 { - cfg.Logger.Printf(" - Log lag to metric id %d\n", mon.MetricID) + l.Infof(" - Log lag to metric id %d\n", mon.MetricID) } if mon.ComponentID > 0 { - cfg.Logger.Printf(" - Update component id %d\n\n", mon.ComponentID) + l.Infof(" - Update component id %d\n\n", mon.ComponentID) } go mon.Start(cfg, wg) @@ -76,7 +106,7 @@ func main() { signal.Notify(signals, os.Interrupt, os.Kill) <-signals - cfg.Logger.Println("Abort: Waiting monitors to finish") + logrus.Warnf("Abort: Waiting monitors to finish") for _, mon := range cfg.Monitors { mon.Stop() } @@ -84,24 +114,17 @@ func main() { wg.Wait() } -func getLogger(logPath string) *log.Logger { - var logWriter = os.Stdout - var err error - - if len(logPath) > 0 { - logWriter, err = os.Create(logPath) - if err != nil { - fmt.Printf("Unable to open file '%v' for logging\n", logPath) - os.Exit(1) - } +func getLogger(logPath *string) *os.File { + if logPath == nil || len(*logPath) == 0 { + return os.Stdout } - flags := log.Llongfile | log.Ldate | log.Ltime - if len(os.Getenv("CACHET_DEV")) > 0 { - flags = 0 + if file, err := os.Create(logPath); err != nil { + logrus.Errorf("Unable to open file '%v' for logging: \n%v", logPath, err) + os.Exit(1) + } else { + return file } - - return log.New(logWriter, "", flags) } func getConfiguration(path string) (*cachet.CachetMonitor, error) { @@ -114,26 +137,31 @@ func getConfiguration(path string) (*cachet.CachetMonitor, error) { // download config response, err := http.Get(path) if err != nil { - return nil, errors.New("Cannot download network config: " + err.Error()) + logrus.Warn("Unable to download network configuration") + return nil, err } defer response.Body.Close() data, _ = ioutil.ReadAll(response.Body) - fmt.Println("Downloaded network configuration.") + logrus.Info("Downloaded network configuration.") } else { data, err = ioutil.ReadFile(path) if err != nil { - return nil, errors.New("Config file '" + path + "' missing!") + return nil, errors.New("Unable to open file: '" + path + "'") } } - if err := json.Unmarshal(data, &cfg); err != nil { - fmt.Println(err) - return nil, errors.New("Cannot parse config!") + // test file path for yml + if strings.HasSuffix(path, ".yml") || strings.HasSuffix(path, ".yaml") { + err = yaml.Unmarshal(data, &cfg) + } else { + err = json.Unmarshal(data, &cfg) } - cfg.Logger = getLogger(cfg.LogPath) + if err != nil { + logrus.Warnf("Unable to parse configuration file") + } - return &cfg, nil + return &cfg, err } diff --git a/config.go b/config.go index 8a12419..bcf99bd 100644 --- a/config.go +++ b/config.go @@ -1,65 +1,58 @@ package cachet import ( - "errors" - "log" "net" "os" + + "github.com/Sirupsen/logrus" ) type CachetMonitor struct { - Logger *log.Logger `json:"-"` - - APIUrl string `json:"api_url"` - APIToken string `json:"api_token"` - SystemName string `json:"system_name"` - LogPath string `json:"log_path"` - InsecureAPI bool `json:"insecure_api"` - + Name string `json:"system_name"` + API CachetAPI `json:"api"` Monitors []*Monitor `json:"monitors"` } -func (cfg *CachetMonitor) ValidateConfiguration() error { - if cfg.Logger == nil { - cfg.Logger = log.New(os.Stdout, "", log.Llongfile|log.Ldate|log.Ltime) - } +func (cfg *CachetMonitor) Validate() bool { + valid := true - if len(cfg.SystemName) == 0 { + if len(cfg.Name) == 0 { // get hostname - cfg.SystemName = getHostname() + cfg.Name = getHostname() } - if len(cfg.APIToken) == 0 || len(cfg.APIUrl) == 0 { - return errors.New("API URL or API Token not set. cachet-monitor won't be able to report incidents.\n\nPlease set:\n CACHET_API and CACHET_TOKEN environment variable to override settings.\n\nGet help at https://github.com/castawaylabs/cachet-monitor\n") + if len(cfg.API.Token) == 0 || len(cfg.API.Url) == 0 { + logrus.Warnf("API URL or API Token missing.\nGet help at https://github.com/castawaylabs/cachet-monitor") + valid = false } if len(cfg.Monitors) == 0 { - return errors.New("No monitors defined!\nSee sample configuration: https://github.com/castawaylabs/cachet-monitor/blob/master/example.config.json\n") + logrus.Warnf("No monitors defined!\nSee help for example configuration") + valid = false } for _, monitor := range cfg.Monitors { - if err := monitor.ValidateConfiguration(); err != nil { - return err + if err := monitor.Validate(); !valid { + valid = false } } - return nil + return valid } // getHostname returns id of the current system func getHostname() string { hostname, err := os.Hostname() - if err != nil || len(hostname) == 0 { - addrs, err := net.InterfaceAddrs() - - if err != nil { - return "unknown" - } - - for _, addr := range addrs { - return addr.String() - } + if err == nil && len(hostname) > 0 { + return hostname } - return hostname + addrs, err := net.InterfaceAddrs() + if err != nil { + return "unknown" + } + + for _, addr := range addrs { + return addr.String() + } } diff --git a/example.config.yml b/example.config.yml new file mode 100644 index 0000000..50c0909 --- /dev/null +++ b/example.config.yml @@ -0,0 +1,16 @@ +apiurl: https://demo.cachethq.io/api/v1 +apitoken: 9yMHsdioQosnyVK4iCVR +insecureapi: true +monitors: + - name: google + type: http + url: https://google.com + stricttls: true + threshold: 80 + componentid: 1 + interval: 10 + timeout: 5 + expectedstatuscode: 200 + headers: + - name: Authorization + value: Basic \ No newline at end of file diff --git a/http.go b/http.go index 6729625..a401b1d 100644 --- a/http.go +++ b/http.go @@ -7,10 +7,30 @@ import ( "fmt" "io/ioutil" "net/http" + "regexp" "strconv" "time" ) +type HttpMonitor struct { + URL string `json:"url"` + Method string `json:"method"` + StrictTLS bool `json:"strict_tls"` + CheckInterval time.Duration `json:"interval"` + HttpTimeout time.Duration `json:"timeout"` + + // Threshold = percentage + Threshold float32 `json:"threshold"` + ExpectedStatusCode int `json:"expected_status_code"` + // compiled to Regexp + ExpectedBody string `json:"expected_body"` + bodyRegexp *regexp.Regexp +} + +type TCPMonitor struct{} +type ICMPMonitor struct{} +type DNSMonitor struct{} + func (monitor *CachetMonitor) makeRequest(requestType string, url string, reqBody []byte) (*http.Response, []byte, error) { req, err := http.NewRequest(requestType, monitor.APIUrl+url, bytes.NewBuffer(reqBody)) diff --git a/monitor.go b/monitor.go index 593bf5d..9d4b221 100644 --- a/monitor.go +++ b/monitor.go @@ -196,7 +196,7 @@ func (monitor *Monitor) AnalyseData() { } } -func (monitor *Monitor) ValidateConfiguration() error { +func (monitor *Monitor) Validate() error { if len(monitor.ExpectedBody) > 0 { exp, err := regexp.Compile(monitor.ExpectedBody) if err != nil {