From 36bf22859940cb95c0ea10e8a6e2ffea15fe74eb Mon Sep 17 00:00:00 2001 From: Matej Kramny Date: Sat, 4 Feb 2017 18:23:53 -0800 Subject: [PATCH] a compiling proof of concept - abstract type - http, tcp, icmp & dns monitor types - unmarshal from json into any monitor type --- api.go | 53 +++++++++++- cli/main.go | 117 ++++++++++++++++---------- config.go | 32 ++++--- dns.go | 3 + example.config.json | 19 ++--- example.config.yml | 16 ---- http.go | 133 ++++++++++++++++++++++-------- icmp.go | 3 + incident.go | 8 +- monitor.go | 197 +++++++++++++------------------------------- tcp.go | 13 +++ template.go | 6 ++ 12 files changed, 342 insertions(+), 258 deletions(-) create mode 100644 dns.go delete mode 100644 example.config.yml create mode 100644 icmp.go create mode 100644 tcp.go create mode 100644 template.go diff --git a/api.go b/api.go index 417ad69..9fedb9a 100644 --- a/api.go +++ b/api.go @@ -1,7 +1,56 @@ package cachet +import ( + "bytes" + "crypto/tls" + "errors" + "io/ioutil" + "net/http" +) + type CachetAPI struct { - Url string `json:"api_url"` - Token string `json:"api_token"` + URL string `json:"url"` + Token string `json:"token"` Insecure bool `json:"insecure"` } + +func (api CachetAPI) Ping() error { + resp, _, err := api.NewRequest("GET", "/ping", nil) + if err != nil { + return err + } + + if resp.StatusCode != 200 { + return errors.New("API Responded with non-200 status code") + } + + return nil +} + +func (api CachetAPI) NewRequest(requestType, url string, reqBody []byte) (*http.Response, []byte, error) { + req, err := http.NewRequest(requestType, api.URL+url, bytes.NewBuffer(reqBody)) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Cachet-Token", api.Token) + + transport := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + } + if api.Insecure { + transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + } + + client := &http.Client{ + Transport: transport, + } + + res, err := client.Do(req) + if err != nil { + return nil, []byte{}, err + } + + defer res.Body.Close() + body, _ := ioutil.ReadAll(res.Body) + + return res, body, nil +} diff --git a/cli/main.go b/cli/main.go index 57b2e5d..b2723c2 100644 --- a/cli/main.go +++ b/cli/main.go @@ -10,12 +10,9 @@ import ( "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" ) const usage = `cachet-monitor @@ -26,19 +23,19 @@ Usage: cachet-monitor print-config Arguments: - PATH path to config.yml + PATH path to config.json 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" + cachet-monitor -c /root/cachet-monitor.json + cachet-monitor -c /root/cachet-monitor.json --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 + -c PATH.json --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 @@ -59,43 +56,50 @@ func main() { logrus.SetOutput(getLogger(arguments["--log"])) if len(os.Getenv("CACHET_API")) > 0 { - cfg.APIUrl = os.Getenv("CACHET_API") + cfg.API.URL = os.Getenv("CACHET_API") } if len(os.Getenv("CACHET_TOKEN")) > 0 { - cfg.APIToken = os.Getenv("CACHET_TOKEN") + cfg.API.Token = os.Getenv("CACHET_TOKEN") } if len(os.Getenv("CACHET_DEV")) > 0 { logrus.SetLevel(logrus.DebugLevel) } - if err := cfg.ValidateConfiguration(); err != nil { - panic(err) + if valid := cfg.Validate(); !valid { + logrus.Errorf("Invalid configuration") + os.Exit(1) } - logrus.Infof("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.API.URL, len(cfg.Monitors)) + logrus.Infof("Pinging cachet") + if err := cfg.API.Ping(); err != nil { + logrus.Warnf("Cannot ping cachet!\n%v", err) + } wg := &sync.WaitGroup{} - for _, mon := range cfg.Monitors { + for _, monitorInterface := range cfg.Monitors { + mon := monitorInterface.GetMonitor() l := logrus.WithFields(logrus.Fields{ "name": mon.Name, - "interval": mon.CheckInterval, - "method": mon.Method, - "url": mon.URL, - "timeout": mon.HttpTimeout, + "interval": mon.Interval, + "target": mon.Target, + "timeout": mon.Timeout, }) l.Info(" Starting monitor") // print features - if len(mon.HttpHeaders) > 0 { - for _, h := range mon.HttpHeaders { - logrus.Infof(" - HTTP-Header '%s' '%s'", h.Name, h.Value) + if mon.Type == "http" { + httpMonitor := monitorInterface.(*cachet.HTTPMonitor) + + for k, v := range httpMonitor.Headers { + logrus.Infof(" - HTTP-Header '%s' '%s'", k, v) + } + if httpMonitor.ExpectedStatusCode > 0 { + l.Infof(" - Expect HTTP %d", httpMonitor.ExpectedStatusCode) + } + if len(httpMonitor.ExpectedBody) > 0 { + l.Infof(" - Expect Body to match \"%v\"", httpMonitor.ExpectedBody) } - } - if mon.ExpectedStatusCode > 0 { - l.Infof(" - Expect HTTP %d", mon.ExpectedStatusCode) - } - if len(mon.ExpectedBody) > 0 { - l.Infof(" - Expect Body to match \"%v\"", mon.ExpectedBody) } if mon.MetricID > 0 { l.Infof(" - Log lag to metric id %d\n", mon.MetricID) @@ -113,23 +117,24 @@ func main() { logrus.Warnf("Abort: Waiting monitors to finish") for _, mon := range cfg.Monitors { - mon.Stop() + mon.(*cachet.AbstractMonitor).Stop() } wg.Wait() } -func getLogger(logPath *string) *os.File { - if logPath == nil || len(*logPath) == 0 { +func getLogger(logPath interface{}) *os.File { + if logPath == nil || len(logPath.(string)) == 0 { return os.Stdout } - if file, err := os.Create(logPath); err != nil { + file, err := os.Create(logPath.(string)) + if err != nil { logrus.Errorf("Unable to open file '%v' for logging: \n%v", logPath, err) os.Exit(1) - } else { - return file } + + return file } func getConfiguration(path string) (*cachet.CachetMonitor, error) { @@ -157,15 +162,43 @@ func getConfiguration(path string) (*cachet.CachetMonitor, error) { } } - // 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) + if err = json.Unmarshal(data, &cfg); err != nil { + logrus.Warnf("Unable to parse configuration file") } - if err != nil { - logrus.Warnf("Unable to parse configuration file") + cfg.Monitors = make([]cachet.MonitorInterface, len(cfg.RawMonitors)) + for index, rawMonitor := range cfg.RawMonitors { + var abstract cachet.AbstractMonitor + if err := json.Unmarshal(rawMonitor, &abstract); err != nil { + logrus.Errorf("Unable to unmarshal monitor (index: %d): %v", index, err) + continue + } + + var t cachet.MonitorInterface + var err error + + switch abstract.Type { + case "http", "": + var s cachet.HTTPMonitor + err = json.Unmarshal(rawMonitor, &s) + t = &s + case "dns": + // t = cachet.DNSMonitor + case "icmp": + // t = cachet.ICMPMonitor + case "tcp": + // t = cachet.TCPMonitor + default: + logrus.Errorf("Invalid monitor type (index: %d) %v", index, abstract.Type) + continue + } + + if err != nil { + logrus.Errorf("Unable to unmarshal monitor to type (index: %d): %v", index, err) + continue + } + + cfg.Monitors[index] = t } return &cfg, err diff --git a/config.go b/config.go index bcf99bd..0d4c161 100644 --- a/config.go +++ b/config.go @@ -1,27 +1,35 @@ package cachet import ( + "fmt" "net" "os" + "time" + + "encoding/json" "github.com/Sirupsen/logrus" ) type CachetMonitor struct { - Name string `json:"system_name"` - API CachetAPI `json:"api"` - Monitors []*Monitor `json:"monitors"` + SystemName string `json:"system_name"` + API CachetAPI `json:"api"` + RawMonitors []json.RawMessage `json:"monitors"` + + Monitors []MonitorInterface `json:"-"` } +// Validate configuration func (cfg *CachetMonitor) Validate() bool { valid := true - if len(cfg.Name) == 0 { + if len(cfg.SystemName) == 0 { // get hostname - cfg.Name = getHostname() + cfg.SystemName = getHostname() } - if len(cfg.API.Token) == 0 || len(cfg.API.Url) == 0 { + fmt.Println(cfg.API) + 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 } @@ -32,7 +40,7 @@ func (cfg *CachetMonitor) Validate() bool { } for _, monitor := range cfg.Monitors { - if err := monitor.Validate(); !valid { + if errs := monitor.Validate(); len(errs) > 0 { valid = false } } @@ -48,11 +56,13 @@ func getHostname() string { } addrs, err := net.InterfaceAddrs() - if err != nil { + if err != nil || len(addrs) == 0 { return "unknown" } - for _, addr := range addrs { - return addr.String() - } + return addrs[0].String() +} + +func getMs() int64 { + return time.Now().UnixNano() / int64(time.Millisecond) } diff --git a/dns.go b/dns.go new file mode 100644 index 0000000..40825b1 --- /dev/null +++ b/dns.go @@ -0,0 +1,3 @@ +package cachet + +type DNSMonitor struct{} diff --git a/example.config.json b/example.config.json index 6400991..10347e1 100644 --- a/example.config.json +++ b/example.config.json @@ -1,21 +1,20 @@ { - "api_url": "https://demo.cachethq.io/api/v1", - "api_token": "9yMHsdioQosnyVK4iCVR", - "insecure_api": true, + "api": { + "url": "https://demo.cachethq.io/api/v1", + "token": "9yMHsdioQosnyVK4iCVR", + "insecure": true + }, "monitors": [ { "name": "google", - "url": "https://google.com", + "url": "https://google.com", "threshold": 80, "component_id": 1, "interval": 10, "timeout": 5, - "headers": [ - { - "header": "Authorization", - "value": "Basic " - } - ], + "headers": { + "Authorization": "Basic " + }, "expected_status_code": 200, "strict_tls": true } diff --git a/example.config.yml b/example.config.yml deleted file mode 100644 index 50c0909..0000000 --- a/example.config.yml +++ /dev/null @@ -1,16 +0,0 @@ -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 a401b1d..6f06d3e 100644 --- a/http.go +++ b/http.go @@ -1,63 +1,127 @@ package cachet import ( - "bytes" "crypto/tls" - "encoding/json" - "fmt" "io/ioutil" "net/http" "regexp" "strconv" + "strings" "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"` +type HTTPMonitor struct { + *AbstractMonitor + + Method string `json:"method"` + ExpectedStatusCode int `json:"expected_status_code"` + Headers map[string]string `json:"headers"` - // 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)) - - req.Header.Set("Content-Type", "application/json") - req.Header.Set("X-Cachet-Token", monitor.APIToken) - - client := &http.Client{} - if monitor.InsecureAPI == true { +func (monitor *HTTPMonitor) do() bool { + client := &http.Client{ + Timeout: time.Duration(monitor.Timeout * time.Second), + } + if monitor.Strict == false { client.Transport = &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - Proxy: http.ProxyFromEnvironment, } } - res, err := client.Do(req) - if err != nil { - return nil, []byte{}, err + req, err := http.NewRequest(monitor.Method, monitor.Target, nil) + for k, v := range monitor.Headers { + req.Header.Add(k, v) } - defer res.Body.Close() - body, _ := ioutil.ReadAll(res.Body) + resp, err := client.Do(req) + if err != nil { + monitor.lastFailReason = err.Error() - return res, body, nil + return false + } + + defer resp.Body.Close() + + if monitor.ExpectedStatusCode > 0 && resp.StatusCode != monitor.ExpectedStatusCode { + monitor.lastFailReason = "Unexpected response code: " + strconv.Itoa(resp.StatusCode) + ". Expected " + strconv.Itoa(monitor.ExpectedStatusCode) + + return false + } + + if monitor.bodyRegexp != nil { + // check body + responseBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + monitor.lastFailReason = err.Error() + + return false + } + + match := monitor.bodyRegexp.Match(responseBody) + if !match { + monitor.lastFailReason = "Unexpected body: " + string(responseBody) + ". Expected to match " + monitor.ExpectedBody + } + + return match + } + + return true +} + +func (monitor *HTTPMonitor) Validate() []string { + errs := []string{} + if len(monitor.ExpectedBody) > 0 { + exp, err := regexp.Compile(monitor.ExpectedBody) + if err != nil { + errs = append(errs, "Regexp compilation failure: "+err.Error()) + } else { + monitor.bodyRegexp = exp + } + } + + if len(monitor.ExpectedBody) == 0 && monitor.ExpectedStatusCode == 0 { + errs = append(errs, "Both 'expected_body' and 'expected_status_code' fields empty") + } + + if monitor.Interval < 1 { + monitor.Interval = DefaultInterval + } + + if monitor.Timeout < 1 { + monitor.Timeout = DefaultTimeout + } + + monitor.Method = strings.ToUpper(monitor.Method) + switch monitor.Method { + case "GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD": + break + case "": + monitor.Method = "GET" + default: + errs = append(errs, "Unsupported check method: "+monitor.Method) + } + + if monitor.ComponentID == 0 && monitor.MetricID == 0 { + errs = append(errs, "component_id & metric_id are unset") + } + + if monitor.Threshold <= 0 { + monitor.Threshold = 100 + } + + return errs +} + +func (mon *HTTPMonitor) GetMonitor() *AbstractMonitor { + return mon.AbstractMonitor } // SendMetric sends lag metric point -func (monitor *Monitor) SendMetric(delay int64) error { +/*func (monitor *Monitor) SendMetric(delay int64) error { if monitor.MetricID == 0 { return nil } @@ -73,7 +137,4 @@ func (monitor *Monitor) SendMetric(delay int64) error { return nil } - -func getMs() int64 { - return time.Now().UnixNano() / int64(time.Millisecond) -} +*/ diff --git a/icmp.go b/icmp.go new file mode 100644 index 0000000..de12ea3 --- /dev/null +++ b/icmp.go @@ -0,0 +1,3 @@ +package cachet + +type ICMPMonitor struct{} diff --git a/incident.go b/incident.go index 3cbb3c4..fd22b67 100644 --- a/incident.go +++ b/incident.go @@ -4,6 +4,8 @@ import ( "encoding/json" "fmt" "strconv" + + "github.com/Sirupsen/logrus" ) // Incident Cachet data model @@ -33,7 +35,7 @@ func (incident *Incident) Send(cfg *CachetMonitor) error { } if err != nil { - cfg.Logger.Printf("cannot fetch component: %v", err) + logrus.Warnf("cannot fetch component: %v", err) } case 4: // fixed @@ -49,7 +51,7 @@ func (incident *Incident) Send(cfg *CachetMonitor) error { jsonBytes, _ := json.Marshal(incident) - resp, body, err := cfg.makeRequest(requestType, requestURL, jsonBytes) + resp, body, err := cfg.API.NewRequest(requestType, requestURL, jsonBytes) if err != nil { return err } @@ -72,7 +74,7 @@ func (incident *Incident) Send(cfg *CachetMonitor) error { } func (incident *Incident) GetComponentStatus(cfg *CachetMonitor) (int, error) { - resp, body, err := cfg.makeRequest("GET", "/components/"+strconv.Itoa(incident.ComponentID), nil) + resp, body, err := cfg.API.NewRequest("GET", "/components/"+strconv.Itoa(incident.ComponentID), nil) if err != nil { return 0, err } diff --git a/monitor.go b/monitor.go index e8db9f1..fd1fa94 100644 --- a/monitor.go +++ b/monitor.go @@ -1,46 +1,48 @@ package cachet import ( - "crypto/tls" - "errors" - "fmt" - "io/ioutil" - "net/http" - "regexp" - "strconv" - "strings" "sync" "time" + + "github.com/Sirupsen/logrus" ) -const DefaultInterval = 60 -const DefaultTimeout = 1 +const DefaultInterval = time.Second * 60 +const DefaultTimeout = time.Second const DefaultTimeFormat = "15:04:05 Jan 2 MST" +const HistorySize = 10 -type HttpHeader struct { - Name string `json:"header"` - Value string `json:"value"` +type MonitorInterface interface { + do() bool + Validate() []string + GetMonitor() *AbstractMonitor } -// Monitor data model -type Monitor struct { - Name string `json:"name"` - URL string `json:"url"` - Method string `json:"method"` - StrictTLS bool `json:"strict_tls"` - CheckInterval time.Duration `json:"interval"` - HttpTimeout time.Duration `json:"timeout"` - HttpHeaders []*HttpHeader `json:"headers"` +// AbstractMonitor data model +type AbstractMonitor struct { + Name string `json:"name"` + Target string `json:"target"` + + // (default)http, tcp, dns, icmp + Type string `json:"type"` + + // defaults true + Strict bool `json:"strict"` + + Interval time.Duration `json:"interval"` + Timeout time.Duration `json:"timeout"` MetricID int `json:"metric_id"` ComponentID int `json:"component_id"` + // Templating stuff + Template struct { + Investigating MessageTemplate `json:"investigating"` + Fixed MessageTemplate `json:"fixed"` + } `json:"template"` + // Threshold = percentage - Threshold float32 `json:"threshold"` - ExpectedStatusCode int `json:"expected_status_code"` - // compiled to Regexp - ExpectedBody string `json:"expected_body"` - bodyRegexp *regexp.Regexp + Threshold float32 `json:"threshold"` history []bool lastFailReason string @@ -51,13 +53,23 @@ type Monitor struct { stopC chan bool } -func (mon *Monitor) Start(cfg *CachetMonitor, wg *sync.WaitGroup) { +func (mon *AbstractMonitor) do() bool { + return true +} +func (mon *AbstractMonitor) Validate() []string { + return []string{} +} +func (mon *AbstractMonitor) GetMonitor() *AbstractMonitor { + return mon +} + +func (mon *AbstractMonitor) Start(cfg *CachetMonitor, wg *sync.WaitGroup) { wg.Add(1) mon.config = cfg mon.stopC = make(chan bool) mon.Tick() - ticker := time.NewTicker(mon.CheckInterval * time.Second) + ticker := time.NewTicker(mon.Interval * time.Second) for { select { case <-ticker.C: @@ -69,7 +81,7 @@ func (mon *Monitor) Start(cfg *CachetMonitor, wg *sync.WaitGroup) { } } -func (monitor *Monitor) Stop() { +func (monitor *AbstractMonitor) Stop() { if monitor.Stopped() { return } @@ -77,7 +89,7 @@ func (monitor *Monitor) Stop() { close(monitor.stopC) } -func (monitor *Monitor) Stopped() bool { +func (monitor *AbstractMonitor) Stopped() bool { select { case <-monitor.stopC: return true @@ -86,77 +98,29 @@ func (monitor *Monitor) Stopped() bool { } } -func (monitor *Monitor) Tick() { +func (monitor *AbstractMonitor) Tick() { reqStart := getMs() - isUp := monitor.doRequest() + up := monitor.do() lag := getMs() - reqStart - if len(monitor.history) == 9 { - monitor.config.Logger.Printf("%v is now saturated\n", monitor.Name) + if len(monitor.history) == HistorySize-1 { + logrus.Warnf("%v is now saturated\n", monitor.Name) } - if len(monitor.history) >= 10 { - monitor.history = monitor.history[len(monitor.history)-9:] + if len(monitor.history) >= HistorySize { + monitor.history = monitor.history[len(monitor.history)-(HistorySize-1):] } - monitor.history = append(monitor.history, isUp) + monitor.history = append(monitor.history, up) monitor.AnalyseData() - if isUp == true && monitor.MetricID > 0 { - monitor.SendMetric(lag) + // report lag + if up && monitor.MetricID > 0 { + logrus.Infof("%v", lag) + // monitor.SendMetric(lag) } } -func (monitor *Monitor) doRequest() bool { - client := &http.Client{ - Timeout: time.Duration(monitor.HttpTimeout * time.Second), - } - if monitor.StrictTLS == false { - client.Transport = &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - } - - req, err := http.NewRequest(monitor.Method, monitor.URL, nil) - for _, h := range monitor.HttpHeaders { - req.Header.Add(h.Name, h.Value) - } - - resp, err := client.Do(req) - if err != nil { - monitor.lastFailReason = err.Error() - - return false - } - - defer resp.Body.Close() - - if monitor.ExpectedStatusCode > 0 && resp.StatusCode != monitor.ExpectedStatusCode { - monitor.lastFailReason = "Unexpected response code: " + strconv.Itoa(resp.StatusCode) + ". Expected " + strconv.Itoa(monitor.ExpectedStatusCode) - - return false - } - - if monitor.bodyRegexp != nil { - // check body - responseBody, err := ioutil.ReadAll(resp.Body) - if err != nil { - monitor.lastFailReason = err.Error() - - return false - } - - match := monitor.bodyRegexp.Match(responseBody) - if !match { - monitor.lastFailReason = "Unexpected body: " + string(responseBody) + ". Expected to match " + monitor.ExpectedBody - } - - return match - } - - return true -} - // AnalyseData decides if the monitor is statistically up or down and creates / resolves an incident -func (monitor *Monitor) AnalyseData() { +func (monitor *AbstractMonitor) AnalyseData() { // look at the past few incidents numDown := 0 for _, wasUp := range monitor.history { @@ -166,7 +130,7 @@ func (monitor *Monitor) AnalyseData() { } t := (float32(numDown) / float32(len(monitor.history))) * 100 - monitor.config.Logger.Printf("%s %.2f%%/%.2f%% down at %v\n", monitor.Name, t, monitor.Threshold, time.Now().UnixNano()/int64(time.Second)) + logrus.Printf("%s %.2f%%/%.2f%% down at %v\n", monitor.Name, t, monitor.Threshold, time.Now().UnixNano()/int64(time.Second)) if len(monitor.history) != 10 { // not saturated @@ -186,16 +150,16 @@ func (monitor *Monitor) AnalyseData() { } // is down, create an incident - monitor.config.Logger.Printf("%v creating incident. Monitor is down: %v", monitor.Name, monitor.lastFailReason) + logrus.Printf("%v creating incident. Monitor is down: %v", monitor.Name, monitor.lastFailReason) // set investigating status monitor.incident.SetInvestigating() // create/update incident if err := monitor.incident.Send(monitor.config); err != nil { - monitor.config.Logger.Printf("Error sending incident: %v\n", err) + logrus.Printf("Error sending incident: %v\n", err) } } else if t < monitor.Threshold && monitor.incident != nil { // was down, created an incident, its now ok, make it resolved. - monitor.config.Logger.Printf("%v resolved downtime incident", monitor.Name) + logrus.Printf("%v resolved downtime incident", monitor.Name) // resolve incident monitor.incident.Message = "\n**Resolved** - " + time.Now().Format(DefaultTimeFormat) + "\n\n - - - \n\n" + monitor.incident.Message @@ -206,46 +170,3 @@ func (monitor *Monitor) AnalyseData() { monitor.incident = nil } } - -func (monitor *Monitor) Validate() error { - if len(monitor.ExpectedBody) > 0 { - exp, err := regexp.Compile(monitor.ExpectedBody) - if err != nil { - return err - } - - monitor.bodyRegexp = exp - } - - if len(monitor.ExpectedBody) == 0 && monitor.ExpectedStatusCode == 0 { - return errors.New("Nothing to check, both 'expected_body' and 'expected_status_code' fields empty") - } - - if monitor.CheckInterval < 1 { - monitor.CheckInterval = DefaultInterval - } - - if monitor.HttpTimeout < 1 { - monitor.HttpTimeout = DefaultTimeout - } - - monitor.Method = strings.ToUpper(monitor.Method) - switch monitor.Method { - case "GET", "POST", "DELETE", "OPTIONS", "HEAD": - break - case "": - monitor.Method = "GET" - default: - return fmt.Errorf("Unsupported check method: %v", monitor.Method) - } - - if monitor.ComponentID == 0 && monitor.MetricID == 0 { - return errors.New("component_id & metric_id are unset") - } - - if monitor.Threshold <= 0 { - monitor.Threshold = 100 - } - - return nil -} diff --git a/tcp.go b/tcp.go new file mode 100644 index 0000000..7be5db0 --- /dev/null +++ b/tcp.go @@ -0,0 +1,13 @@ +package cachet + +type TCPMonitor struct { + // same as output from net.JoinHostPort + // defaults to parsed config from /etc/resolv.conf when empty + DNSServer string + + // Will be converted to FQDN + Domain string + Type string + // expected answers (regex) + Expect []string +} diff --git a/template.go b/template.go new file mode 100644 index 0000000..11ef04c --- /dev/null +++ b/template.go @@ -0,0 +1,6 @@ +package cachet + +type MessageTemplate struct { + Subject string `json:"subject"` + Message string `json:"message"` +}