diff --git a/.gitignore b/.gitignore index 6d7bad5..2ec9320 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -gin-bin \ No newline at end of file +gin-bin +example.config.local.json \ No newline at end of file diff --git a/cachet/cachet.go b/cachet/cachet.go index 7c2b25d..852a74e 100644 --- a/cachet/cachet.go +++ b/cachet/cachet.go @@ -2,7 +2,5 @@ package cachet import "os" -// apiUrl -> https://demo.cachethq.io/api -// apiToken -> qwertyuiop -var apiUrl = os.Getenv("CACHET_API") -var apiToken = os.Getenv("CACHET_TOKEN") \ No newline at end of file +var ApiUrl = os.Getenv("CACHET_API") +var ApiToken = os.Getenv("CACHET_TOKEN") \ No newline at end of file diff --git a/cachet/config.go b/cachet/config.go new file mode 100644 index 0000000..9db7b6e --- /dev/null +++ b/cachet/config.go @@ -0,0 +1,74 @@ +package cachet + +import ( + "os" + "fmt" + "flag" + "net/url" + "net/http" + "io/ioutil" + "encoding/json" +) + +var Config CachetConfig + +type CachetConfig struct { + API_Url string `json:"api_url"` + API_Token string `json:"api_token"` + Monitors []*Monitor `json:"monitors"` +} + +func init() { + var configPath string + flag.StringVar(&configPath, "c", "/etc/cachet-monitor.config.json", "Config path") + flag.Parse() + + var data []byte + + // test if its a url + _, err := url.ParseRequestURI(configPath) + if err == nil { + // download config + response, err := http.Get(configPath) + if err != nil { + fmt.Printf("Cannot download network config: %v\n", err) + os.Exit(1) + } + + defer response.Body.Close() + + data, _ = ioutil.ReadAll(response.Body) + + fmt.Println("Downloaded network configuration.") + } else { + data, err = ioutil.ReadFile(configPath) + if err != nil { + fmt.Println("Config file '" + configPath + "' missing!") + os.Exit(1) + } + } + + err = json.Unmarshal(data, &Config) + + if err != nil { + fmt.Println("Cannot parse config!") + os.Exit(1) + } + + if len(os.Getenv("CACHET_API")) > 0 { + Config.API_Url = os.Getenv("CACHET_API") + } + if len(os.Getenv("CACHET_TOKEN")) > 0 { + Config.API_Token = os.Getenv("CACHET_TOKEN") + } + + if len(Config.API_Token) == 0 || len(Config.API_Url) == 0 { + fmt.Printf("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") + os.Exit(1) + } + + if len(Config.Monitors) == 0 { + fmt.Printf("No monitors defined!\nSee sample configuration: https://github.com/CastawayLabs/cachet-monitor/blob/master/example.config.json\n") + os.Exit(1) + } +} \ No newline at end of file diff --git a/cachet/incident.go b/cachet/incident.go index a9bc815..2d3bc64 100644 --- a/cachet/incident.go +++ b/cachet/incident.go @@ -33,9 +33,9 @@ func (incident *Incident) Send() { var req *http.Request if incident.Id == 0 { - req, err = http.NewRequest("POST", apiUrl + "/incidents", bytes.NewBuffer(jsonBytes)) + req, err = http.NewRequest("POST", Config.API_Url + "/incidents", bytes.NewBuffer(jsonBytes)) } else { - req, err = http.NewRequest("PUT", apiUrl + "/incidents/" + strconv.Itoa(incident.Id), bytes.NewBuffer(jsonBytes)) + req, err = http.NewRequest("PUT", Config.API_Url + "/incidents/" + strconv.Itoa(incident.Id), bytes.NewBuffer(jsonBytes)) } if err != nil { @@ -43,7 +43,7 @@ func (incident *Incident) Send() { } req.Header.Set("Content-Type", "application/json") - req.Header.Set("X-Cachet-Token", apiToken) + req.Header.Set("X-Cachet-Token", Config.API_Token) client := &http.Client{} resp, err := client.Do(req) diff --git a/cachet/metrics.go b/cachet/metrics.go index ac34e56..b4e6eef 100644 --- a/cachet/metrics.go +++ b/cachet/metrics.go @@ -5,38 +5,33 @@ import ( "bytes" "strconv" "net/http" - "io/ioutil" "encoding/json" ) func SendMetric(metricId int, delay int64) { - jsonBytes, err := json.Marshal(&map[string]interface{}{ + if metricId <= 0 { + return + } + + jsonBytes, _ := json.Marshal(&map[string]interface{}{ "value": delay, }) - if err != nil { - panic(err) - } - - req, err := http.NewRequest("POST", apiUrl + "/metrics/" + strconv.Itoa(metricId) + "/points", bytes.NewBuffer(jsonBytes)) - if err != nil { - panic(err) - } - - req.Header.Set("Content-Type", "application/json") - req.Header.Set("X-Cachet-Token", apiToken) client := &http.Client{} + req, _ := http.NewRequest("POST", Config.API_Url + "/metrics/" + strconv.Itoa(metricId) + "/points", bytes.NewBuffer(jsonBytes)) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Cachet-Token", Config.API_Token) + resp, err := client.Do(req) if err != nil { - panic(err) + fmt.Printf("Could not log data point!\n%v\n", err) + return } defer resp.Body.Close() - _, _ = ioutil.ReadAll(resp.Body) - // fmt.Println(strconv.Itoa(resp.StatusCode) + " " + string(body)) - if resp.StatusCode != 200 { fmt.Println("Could not log data point!") } -} +} \ No newline at end of file diff --git a/cachet/monitor.go b/cachet/monitor.go index e08cc0c..4263cc4 100644 --- a/cachet/monitor.go +++ b/cachet/monitor.go @@ -17,6 +17,7 @@ type Monitor struct { ExpectedStatusCode int `json:"expected_status_code"` History []bool `json:"-"` + LastFailReason *string `json:"-"` Incident *Incident `json:"-"` } @@ -42,6 +43,8 @@ func (monitor *Monitor) doRequest() bool { } resp, err := client.Get(monitor.Url) if err != nil { + errString := err.Error() + monitor.LastFailReason = &errString return false } @@ -73,7 +76,11 @@ func (monitor *Monitor) AnalyseData() { monitor.Incident = &Incident{ Name: monitor.Name, - Message: monitor.Name + " is unreachable.", + Message: monitor.Name + " failed", + } + + if monitor.LastFailReason != nil { + monitor.Incident.Message += "\n\n" + *monitor.LastFailReason } monitor.Incident.SetInvestigating() @@ -91,4 +98,4 @@ func (monitor *Monitor) AnalyseData() { func getMs() int64 { return time.Now().UnixNano() / int64(time.Millisecond) -} +} \ No newline at end of file diff --git a/example.config.json b/example.config.json new file mode 100644 index 0000000..737a2b1 --- /dev/null +++ b/example.config.json @@ -0,0 +1,14 @@ +{ + "api_url": "https://demo.cachethq.io/api", + "api_token": "9yMHsdioQosnyVK4iCVR", + "monitors": [ + { + "name": "nodegear frontend", + "url": "https://nodegear.io/ping", + "metric_id": 1, + "threshold": 80, + "component_id": null, + "expected_status_code": 200 + } + ] +} \ No newline at end of file diff --git a/main.go b/main.go index bb44ac3..2d5ebf2 100644 --- a/main.go +++ b/main.go @@ -7,24 +7,9 @@ import ( ) func main() { - monitors := []*cachet.Monitor{ - /*&cachet.Monitor{ - Name: "nodegear frontend", - Url: "https://nodegear.io/ping", - MetricId: 1, - Threshold: 80.0, - ExpectedStatusCode: 200, - },*/ - &cachet.Monitor{ - Name: "local test server", - Url: "http://localhost:1337", - Threshold: 80.0, - ExpectedStatusCode: 200, - }, - } - - fmt.Printf("Starting %d monitors:\n", len(monitors)) - for _, monitor := range monitors { + fmt.Printf("API: %s\n", cachet.Config.API_Url) + fmt.Printf("Starting %d monitors:\n", len(cachet.Config.Monitors)) + for _, monitor := range cachet.Config.Monitors { fmt.Printf(" %s: GET %s & Expect HTTP %d\n", monitor.Name, monitor.Url, monitor.ExpectedStatusCode) if monitor.MetricId > 0 { fmt.Printf(" - Logs lag to metric id: %d\n", monitor.MetricId) @@ -35,8 +20,8 @@ func main() { ticker := time.NewTicker(time.Second) for _ = range ticker.C { - for _, monitor := range monitors { + for _, monitor := range cachet.Config.Monitors { go monitor.Run() } } -} +} \ No newline at end of file diff --git a/readme.md b/readme.md index a42c417..23b8086 100644 --- a/readme.md +++ b/readme.md @@ -6,9 +6,16 @@ This is a monitoring plugin for CachetHQ. How to run: ----------- +Example: + 1. Set up [Go](https://golang.org) 2. `go install github.com/castawaylabs/cachet-monitor` -3. `cachet-monitor` +3. `cachet-monitor -c https://raw.githubusercontent.com/CastawayLabs/cachet-monitor/master/example.config.json` + +Production: + +1. Download the example config and save to `/etc/cachet-monitor.config.json` +2. Run in background: `nohup cachet-monitor 2>&1 > /var/log/cachet-monitor.log &` Environment variables: ----------------------