From c04128ce36087c78f5eda511ca05514ade17ea39 Mon Sep 17 00:00:00 2001
From: Matej Kramny <matejkramny@gmail.com>
Date: Mon, 6 Feb 2017 23:05:39 -0800
Subject: [PATCH] compile message for Fixed status

- better logging
---
 api.go         |  2 ++
 config.go      |  2 +-
 config_test.go | 15 +++++++++++++++
 http.go        | 19 ++++++++-----------
 monitor.go     | 38 ++++++++++++++++++++++++++++----------
 readme.md      | 24 ++++++++++++++++++++++++
 template.go    |  3 ++-
 7 files changed, 80 insertions(+), 23 deletions(-)
 create mode 100644 config_test.go

diff --git a/api.go b/api.go
index c6787f8..588c447 100644
--- a/api.go
+++ b/api.go
@@ -22,6 +22,7 @@ type CachetResponse struct {
 	Data json.RawMessage `json:"data"`
 }
 
+// TODO: test
 func (api CachetAPI) Ping() error {
 	resp, _, err := api.NewRequest("GET", "/ping", nil)
 	if err != nil {
@@ -50,6 +51,7 @@ func (api CachetAPI) SendMetric(id int, lag int64) {
 	}
 }
 
+// TODO: test
 // NewRequest wraps http.NewRequest
 func (api CachetAPI) NewRequest(requestType, url string, reqBody []byte) (*http.Response, CachetResponse, error) {
 	req, err := http.NewRequest(requestType, api.URL+url, bytes.NewBuffer(reqBody))
diff --git a/config.go b/config.go
index 9849e69..af2a05c 100644
--- a/config.go
+++ b/config.go
@@ -76,7 +76,7 @@ func GetMonitorType(t string) string {
 		return "http"
 	}
 
-	return t
+	return strings.ToLower(t)
 }
 
 func getTemplateData(monitor *AbstractMonitor) map[string]interface{} {
diff --git a/config_test.go b/config_test.go
new file mode 100644
index 0000000..a24d49e
--- /dev/null
+++ b/config_test.go
@@ -0,0 +1,15 @@
+package cachet
+
+import (
+	"testing"
+)
+
+func TestGetMonitorType(t *testing.T) {
+	if monType := GetMonitorType(""); monType != "http" {
+		t.Error("monitor type `` should default to http")
+	}
+
+	if mt := GetMonitorType("HTTP"); mt != "http" {
+		t.Error("does not return correct monitor type")
+	}
+}
diff --git a/http.go b/http.go
index 53067d6..27c8b3a 100644
--- a/http.go
+++ b/http.go
@@ -8,14 +8,12 @@ import (
 	"strconv"
 	"strings"
 	"time"
-
-	"github.com/Sirupsen/logrus"
 )
 
 // Investigating template
 var defaultHTTPInvestigatingTpl = MessageTemplate{
 	Subject: `{{ .Monitor.Name }} - {{ .SystemName }}`,
-	Message: `{{ .Monitor.Name }} check **failed** - {{ .now }}
+	Message: `{{ .Monitor.Name }} check **failed** (server time: {{ .now }})
 
 {{ .FailReason }}`,
 }
@@ -42,6 +40,7 @@ type HTTPMonitor struct {
 	bodyRegexp   *regexp.Regexp
 }
 
+// TODO: test
 func (monitor *HTTPMonitor) test() bool {
 	req, err := http.NewRequest(monitor.Method, monitor.Target, nil)
 	for k, v := range monitor.Headers {
@@ -64,35 +63,33 @@ func (monitor *HTTPMonitor) test() bool {
 	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)
+		monitor.lastFailReason = "Expected HTTP response status: " + strconv.Itoa(monitor.ExpectedStatusCode) + ", got: " + strconv.Itoa(resp.StatusCode)
 		return false
 	}
 
 	if monitor.bodyRegexp != nil {
-		// check body
+		// check response 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
+		if !monitor.bodyRegexp.Match(responseBody) {
+			monitor.lastFailReason = "Unexpected body: " + string(responseBody) + ".\nExpected to match: " + monitor.ExpectedBody
+			return false
 		}
-
-		return match
 	}
 
 	return true
 }
 
+// TODO: test
 func (mon *HTTPMonitor) Validate() []string {
 	mon.Template.Investigating.SetDefault(defaultHTTPInvestigatingTpl)
 	mon.Template.Fixed.SetDefault(defaultHTTPFixedTpl)
 
 	errs := mon.AbstractMonitor.Validate()
-	logrus.Warnf("%#v", mon.Template.Investigating)
 
 	if len(mon.ExpectedBody) > 0 {
 		exp, err := regexp.Compile(mon.ExpectedBody)
diff --git a/monitor.go b/monitor.go
index 93493de..5cc3384 100644
--- a/monitor.go
+++ b/monitor.go
@@ -48,7 +48,12 @@ type AbstractMonitor struct {
 	Threshold      float32
 	ThresholdCount bool `mapstructure:"threshold_count"`
 
+	// lag / average(lagHistory) * 100 = percentage above average lag
+	// PerformanceThreshold sets the % limit above which this monitor will trigger degraded-performance
+	PerformanceThreshold float32
+
 	history        []bool
+	lagHistory     []float32
 	lastFailReason string
 	incident       *Incident
 	config         *CachetMonitor
@@ -136,6 +141,7 @@ func (mon *AbstractMonitor) ClockStop() {
 
 func (mon *AbstractMonitor) test() bool { return false }
 
+// TODO: test
 func (mon *AbstractMonitor) tick(iface MonitorInterface) {
 	reqStart := getMs()
 	up := iface.test()
@@ -161,6 +167,7 @@ func (mon *AbstractMonitor) tick(iface MonitorInterface) {
 	}
 }
 
+// TODO: test
 // AnalyseData decides if the monitor is statistically up or down and creates / resolves an incident
 func (mon *AbstractMonitor) AnalyseData() {
 	// look at the past few incidents
@@ -172,10 +179,16 @@ func (mon *AbstractMonitor) AnalyseData() {
 	}
 
 	t := (float32(numDown) / float32(len(mon.history))) * 100
-	if mon.ThresholdCount {
-		logrus.Printf("%s %d/%d down at %v", mon.Name, numDown, int(mon.Threshold), time.Now().Format(mon.config.DateFormat))
+	l := logrus.WithFields(logrus.Fields{
+		"monitor": mon.Name,
+		"time":    time.Now().Format(mon.config.DateFormat),
+	})
+	if numDown == 0 {
+		l.Printf("monitor is up")
+	} else if mon.ThresholdCount {
+		l.Printf("monitor down %d/%d", numDown, int(mon.Threshold))
 	} else {
-		logrus.Printf("%s %.2f%%/%.2f%% down at %v", mon.Name, t, mon.Threshold, time.Now().Format(mon.config.DateFormat))
+		l.Printf("monitor down %.2f%%/%.2f%%", t, mon.Threshold)
 	}
 
 	histSize := HistorySize
@@ -204,12 +217,12 @@ func (mon *AbstractMonitor) AnalyseData() {
 		}
 
 		// is down, create an incident
-		logrus.Warnf("%v: creating incident. Monitor is down: %v", mon.Name, mon.lastFailReason)
+		l.Warnf("creating incident. Monitor is down: %v", mon.lastFailReason)
 		// set investigating status
 		mon.incident.SetInvestigating()
 		// create/update incident
 		if err := mon.incident.Send(mon.config); err != nil {
-			logrus.Printf("Error sending incident: %v\n", err)
+			l.Printf("Error sending incident: %v", err)
 		}
 
 		return
@@ -220,15 +233,20 @@ func (mon *AbstractMonitor) AnalyseData() {
 		return
 	}
 
-	logrus.Warnf("Resolving incident")
-
 	// was down, created an incident, its now ok, make it resolved.
-	logrus.Printf("%v resolved downtime incident", mon.Name)
+	l.Warn("Resolving incident")
 
 	// resolve incident
-	mon.incident.Message = "\n**Resolved** - " + time.Now().Format(mon.config.DateFormat) + "\n\n - - - \n\n" + mon.incident.Message
+	tplData := getTemplateData(mon)
+	tplData["incident"] = mon.incident
+
+	subject, message := mon.Template.Fixed.Exec(tplData)
+	mon.incident.Name = subject
+	mon.incident.Message = message
 	mon.incident.SetFixed()
-	mon.incident.Send(mon.config)
+	if err := mon.incident.Send(mon.config); err != nil {
+		l.Printf("Error sending incident: %v", err)
+	}
 
 	mon.lastFailReason = ""
 	mon.incident = nil
diff --git a/readme.md b/readme.md
index 1ccad03..a77d037 100644
--- a/readme.md
+++ b/readme.md
@@ -97,3 +97,27 @@ Package usage
 When using `cachet-monitor` as a package in another program, you should follow what `cli/main.go` does. It is important to call `ValidateConfiguration` on `CachetMonitor` and all the monitors inside.
 
 [API Documentation](https://godoc.org/github.com/CastawayLabs/cachet-monitor)
+
+## License
+
+MIT License
+
+Copyright (c) 2016 Castaway Labs LLC
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/template.go b/template.go
index 31a09b0..495796a 100644
--- a/template.go
+++ b/template.go
@@ -22,6 +22,7 @@ func (t *MessageTemplate) SetDefault(d MessageTemplate) {
 	}
 }
 
+// TODO: test
 func (t *MessageTemplate) Compile() error {
 	var err error
 
@@ -29,7 +30,7 @@ func (t *MessageTemplate) Compile() error {
 		t.subjectTpl, err = compileTemplate(t.Subject)
 	}
 
-	if err != nil && len(t.Message) > 0 {
+	if err == nil && len(t.Message) > 0 {
 		t.messageTpl, err = compileTemplate(t.Message)
 	}
 
-- 
GitLab