From 038c8c8d8e312c18dcbaeabc6d20b449cf3ad50f Mon Sep 17 00:00:00 2001 From: TwiN Date: Tue, 14 Mar 2023 20:02:31 -0400 Subject: [PATCH] fix: Print response body on failure if debug is set to true --- core/condition.go | 6 +-- core/condition_bench_test.go | 14 +++--- core/condition_test.go | 92 ++++++++++++++++++------------------ core/dns.go | 12 ++--- core/dns_test.go | 8 ++-- core/endpoint.go | 8 ++-- core/result.go | 9 ++-- watchdog/watchdog.go | 13 ++--- 8 files changed, 78 insertions(+), 84 deletions(-) diff --git a/core/condition.go b/core/condition.go index bca75760..6b11d8e9 100644 --- a/core/condition.go +++ b/core/condition.go @@ -33,7 +33,7 @@ const ( // Values that could replace the placeholder: 1, 500, 1000, ... ResponseTimePlaceholder = "[RESPONSE_TIME]" - // BodyPlaceholder is a placeholder for the body of the response + // BodyPlaceholder is a placeholder for the Body of the response // // Values that could replace the placeholder: {}, {"data":{"name":"john"}}, ... BodyPlaceholder = "[BODY]" @@ -232,7 +232,7 @@ func isEqual(first, second string) bool { func sanitizeAndResolve(elements []string, result *Result) ([]string, []string) { parameters := make([]string, len(elements)) resolvedParameters := make([]string, len(elements)) - body := strings.TrimSpace(string(result.body)) + body := strings.TrimSpace(string(result.Body)) for i, element := range elements { element = strings.TrimSpace(element) parameters[i] = element @@ -266,7 +266,7 @@ func sanitizeAndResolve(elements []string, result *Result) ([]string, []string) checkingForExistence = true element = strings.TrimSuffix(strings.TrimPrefix(element, HasFunctionPrefix), FunctionSuffix) } - resolvedElement, resolvedElementLength, err := jsonpath.Eval(strings.TrimPrefix(strings.TrimPrefix(element, BodyPlaceholder), "."), result.body) + resolvedElement, resolvedElementLength, err := jsonpath.Eval(strings.TrimPrefix(strings.TrimPrefix(element, BodyPlaceholder), "."), result.Body) if checkingForExistence { if err != nil { element = "false" diff --git a/core/condition_bench_test.go b/core/condition_bench_test.go index 70e10d47..0126871f 100644 --- a/core/condition_bench_test.go +++ b/core/condition_bench_test.go @@ -5,7 +5,7 @@ import "testing" func BenchmarkCondition_evaluateWithBodyStringAny(b *testing.B) { condition := Condition("[BODY].name == any(john.doe, jane.doe)") for n := 0; n < b.N; n++ { - result := &Result{body: []byte("{\"name\": \"john.doe\"}")} + result := &Result{Body: []byte("{\"name\": \"john.doe\"}")} condition.evaluate(result, false) } b.ReportAllocs() @@ -14,7 +14,7 @@ func BenchmarkCondition_evaluateWithBodyStringAny(b *testing.B) { func BenchmarkCondition_evaluateWithBodyStringAnyFailure(b *testing.B) { condition := Condition("[BODY].name == any(john.doe, jane.doe)") for n := 0; n < b.N; n++ { - result := &Result{body: []byte("{\"name\": \"bob.doe\"}")} + result := &Result{Body: []byte("{\"name\": \"bob.doe\"}")} condition.evaluate(result, false) } b.ReportAllocs() @@ -23,7 +23,7 @@ func BenchmarkCondition_evaluateWithBodyStringAnyFailure(b *testing.B) { func BenchmarkCondition_evaluateWithBodyString(b *testing.B) { condition := Condition("[BODY].name == john.doe") for n := 0; n < b.N; n++ { - result := &Result{body: []byte("{\"name\": \"john.doe\"}")} + result := &Result{Body: []byte("{\"name\": \"john.doe\"}")} condition.evaluate(result, false) } b.ReportAllocs() @@ -32,7 +32,7 @@ func BenchmarkCondition_evaluateWithBodyString(b *testing.B) { func BenchmarkCondition_evaluateWithBodyStringFailure(b *testing.B) { condition := Condition("[BODY].name == john.doe") for n := 0; n < b.N; n++ { - result := &Result{body: []byte("{\"name\": \"bob.doe\"}")} + result := &Result{Body: []byte("{\"name\": \"bob.doe\"}")} condition.evaluate(result, false) } b.ReportAllocs() @@ -41,7 +41,7 @@ func BenchmarkCondition_evaluateWithBodyStringFailure(b *testing.B) { func BenchmarkCondition_evaluateWithBodyStringFailureInvalidPath(b *testing.B) { condition := Condition("[BODY].user.name == bob.doe") for n := 0; n < b.N; n++ { - result := &Result{body: []byte("{\"name\": \"bob.doe\"}")} + result := &Result{Body: []byte("{\"name\": \"bob.doe\"}")} condition.evaluate(result, false) } b.ReportAllocs() @@ -50,7 +50,7 @@ func BenchmarkCondition_evaluateWithBodyStringFailureInvalidPath(b *testing.B) { func BenchmarkCondition_evaluateWithBodyStringLen(b *testing.B) { condition := Condition("len([BODY].name) == 8") for n := 0; n < b.N; n++ { - result := &Result{body: []byte("{\"name\": \"john.doe\"}")} + result := &Result{Body: []byte("{\"name\": \"john.doe\"}")} condition.evaluate(result, false) } b.ReportAllocs() @@ -59,7 +59,7 @@ func BenchmarkCondition_evaluateWithBodyStringLen(b *testing.B) { func BenchmarkCondition_evaluateWithBodyStringLenFailure(b *testing.B) { condition := Condition("len([BODY].name) == 8") for n := 0; n < b.N; n++ { - result := &Result{body: []byte("{\"name\": \"bob.doe\"}")} + result := &Result{Body: []byte("{\"name\": \"bob.doe\"}")} condition.evaluate(result, false) } b.ReportAllocs() diff --git a/core/condition_test.go b/core/condition_test.go index 25976d6a..b3cdad56 100644 --- a/core/condition_test.go +++ b/core/condition_test.go @@ -178,140 +178,140 @@ func TestCondition_evaluate(t *testing.T) { { Name: "body", Condition: Condition("[BODY] == test"), - Result: &Result{body: []byte("test")}, + Result: &Result{Body: []byte("test")}, ExpectedSuccess: true, ExpectedOutput: "[BODY] == test", }, { Name: "body-numerical-equal", Condition: Condition("[BODY] == 123"), - Result: &Result{body: []byte("123")}, + Result: &Result{Body: []byte("123")}, ExpectedSuccess: true, ExpectedOutput: "[BODY] == 123", }, { Name: "body-numerical-less-than", Condition: Condition("[BODY] < 124"), - Result: &Result{body: []byte("123")}, + Result: &Result{Body: []byte("123")}, ExpectedSuccess: true, ExpectedOutput: "[BODY] < 124", }, { Name: "body-numerical-greater-than", Condition: Condition("[BODY] > 122"), - Result: &Result{body: []byte("123")}, + Result: &Result{Body: []byte("123")}, ExpectedSuccess: true, ExpectedOutput: "[BODY] > 122", }, { Name: "body-numerical-greater-than-failure", Condition: Condition("[BODY] > 123"), - Result: &Result{body: []byte("100")}, + Result: &Result{Body: []byte("100")}, ExpectedSuccess: false, ExpectedOutput: "[BODY] (100) > 123", }, { Name: "body-jsonpath", Condition: Condition("[BODY].status == UP"), - Result: &Result{body: []byte("{\"status\":\"UP\"}")}, + Result: &Result{Body: []byte("{\"status\":\"UP\"}")}, ExpectedSuccess: true, ExpectedOutput: "[BODY].status == UP", }, { Name: "body-jsonpath-complex", Condition: Condition("[BODY].data.name == john"), - Result: &Result{body: []byte("{\"data\": {\"id\": 1, \"name\": \"john\"}}")}, + Result: &Result{Body: []byte("{\"data\": {\"id\": 1, \"name\": \"john\"}}")}, ExpectedSuccess: true, ExpectedOutput: "[BODY].data.name == john", }, { Name: "body-jsonpath-complex-invalid", Condition: Condition("[BODY].data.name == john"), - Result: &Result{body: []byte("{\"data\": {\"id\": 1}}")}, + Result: &Result{Body: []byte("{\"data\": {\"id\": 1}}")}, ExpectedSuccess: false, ExpectedOutput: "[BODY].data.name (INVALID) == john", }, { Name: "body-jsonpath-complex-len-invalid", Condition: Condition("len([BODY].data.name) == john"), - Result: &Result{body: []byte("{\"data\": {\"id\": 1}}")}, + Result: &Result{Body: []byte("{\"data\": {\"id\": 1}}")}, ExpectedSuccess: false, ExpectedOutput: "len([BODY].data.name) (INVALID) == john", }, { Name: "body-jsonpath-double-placeholder", Condition: Condition("[BODY].user.firstName != [BODY].user.lastName"), - Result: &Result{body: []byte("{\"user\": {\"firstName\": \"john\", \"lastName\": \"doe\"}}")}, + Result: &Result{Body: []byte("{\"user\": {\"firstName\": \"john\", \"lastName\": \"doe\"}}")}, ExpectedSuccess: true, ExpectedOutput: "[BODY].user.firstName != [BODY].user.lastName", }, { Name: "body-jsonpath-double-placeholder-failure", Condition: Condition("[BODY].user.firstName == [BODY].user.lastName"), - Result: &Result{body: []byte("{\"user\": {\"firstName\": \"john\", \"lastName\": \"doe\"}}")}, + Result: &Result{Body: []byte("{\"user\": {\"firstName\": \"john\", \"lastName\": \"doe\"}}")}, ExpectedSuccess: false, ExpectedOutput: "[BODY].user.firstName (john) == [BODY].user.lastName (doe)", }, { Name: "body-jsonpath-when-body-is-array", Condition: Condition("[BODY][0].id == 1"), - Result: &Result{body: []byte("[{\"id\": 1}, {\"id\": 2}]")}, + Result: &Result{Body: []byte("[{\"id\": 1}, {\"id\": 2}]")}, ExpectedSuccess: true, ExpectedOutput: "[BODY][0].id == 1", }, { Name: "body-jsonpath-when-body-is-array-but-actual-body-is-not", Condition: Condition("[BODY][0].name == test"), - Result: &Result{body: []byte("{\"statusCode\": 500, \"message\": \"Internal Server Error\"}")}, + Result: &Result{Body: []byte("{\"statusCode\": 500, \"message\": \"Internal Server Error\"}")}, ExpectedSuccess: false, ExpectedOutput: "[BODY][0].name (INVALID) == test", }, { Name: "body-jsonpath-complex-int", Condition: Condition("[BODY].data.id == 1"), - Result: &Result{body: []byte("{\"data\": {\"id\": 1}}")}, + Result: &Result{Body: []byte("{\"data\": {\"id\": 1}}")}, ExpectedSuccess: true, ExpectedOutput: "[BODY].data.id == 1", }, { Name: "body-jsonpath-complex-array-int", Condition: Condition("[BODY].data[1].id == 2"), - Result: &Result{body: []byte("{\"data\": [{\"id\": 1}, {\"id\": 2}, {\"id\": 3}]}")}, + Result: &Result{Body: []byte("{\"data\": [{\"id\": 1}, {\"id\": 2}, {\"id\": 3}]}")}, ExpectedSuccess: true, ExpectedOutput: "[BODY].data[1].id == 2", }, { Name: "body-jsonpath-complex-int-using-greater-than", Condition: Condition("[BODY].data.id > 0"), - Result: &Result{body: []byte("{\"data\": {\"id\": 1}}")}, + Result: &Result{Body: []byte("{\"data\": {\"id\": 1}}")}, ExpectedSuccess: true, ExpectedOutput: "[BODY].data.id > 0", }, { Name: "body-jsonpath-complex-int-using-greater-than-failure", Condition: Condition("[BODY].data.id > 5"), - Result: &Result{body: []byte("{\"data\": {\"id\": 1}}")}, + Result: &Result{Body: []byte("{\"data\": {\"id\": 1}}")}, ExpectedSuccess: false, ExpectedOutput: "[BODY].data.id (1) > 5", }, { Name: "body-jsonpath-float-using-greater-than-issue433", // As of v5.3.1, Gatus will convert a float to an int. We're losing precision, but it's better than just returning 0 Condition: Condition("[BODY].balance > 100"), - Result: &Result{body: []byte(`{"balance": "123.40000000000005"}`)}, + Result: &Result{Body: []byte(`{"balance": "123.40000000000005"}`)}, ExpectedSuccess: true, ExpectedOutput: "[BODY].balance > 100", }, { Name: "body-jsonpath-complex-int-using-less-than", Condition: Condition("[BODY].data.id < 5"), - Result: &Result{body: []byte("{\"data\": {\"id\": 2}}")}, + Result: &Result{Body: []byte("{\"data\": {\"id\": 2}}")}, ExpectedSuccess: true, ExpectedOutput: "[BODY].data.id < 5", }, { Name: "body-jsonpath-complex-int-using-less-than-failure", Condition: Condition("[BODY].data.id < 5"), - Result: &Result{body: []byte("{\"data\": {\"id\": 10}}")}, + Result: &Result{Body: []byte("{\"data\": {\"id\": 10}}")}, ExpectedSuccess: false, ExpectedOutput: "[BODY].data.id (10) < 5", }, @@ -378,84 +378,84 @@ func TestCondition_evaluate(t *testing.T) { { Name: "len-body-jsonpath-complex", Condition: Condition("len([BODY].data.name) == 4"), - Result: &Result{body: []byte("{\"data\": {\"name\": \"john\"}}")}, + Result: &Result{Body: []byte("{\"data\": {\"name\": \"john\"}}")}, ExpectedSuccess: true, ExpectedOutput: "len([BODY].data.name) == 4", }, { Name: "len-body-array", Condition: Condition("len([BODY]) == 3"), - Result: &Result{body: []byte("[{\"id\": 1}, {\"id\": 2}, {\"id\": 3}]")}, + Result: &Result{Body: []byte("[{\"id\": 1}, {\"id\": 2}, {\"id\": 3}]")}, ExpectedSuccess: true, ExpectedOutput: "len([BODY]) == 3", }, { Name: "len-body-keyed-array", Condition: Condition("len([BODY].data) == 3"), - Result: &Result{body: []byte("{\"data\": [{\"id\": 1}, {\"id\": 2}, {\"id\": 3}]}")}, + Result: &Result{Body: []byte("{\"data\": [{\"id\": 1}, {\"id\": 2}, {\"id\": 3}]}")}, ExpectedSuccess: true, ExpectedOutput: "len([BODY].data) == 3", }, { Name: "len-body-array-invalid", Condition: Condition("len([BODY].data) == 8"), - Result: &Result{body: []byte("{\"name\": \"john.doe\"}")}, + Result: &Result{Body: []byte("{\"name\": \"john.doe\"}")}, ExpectedSuccess: false, ExpectedOutput: "len([BODY].data) (INVALID) == 8", }, { Name: "len-body-string", Condition: Condition("len([BODY]) == 8"), - Result: &Result{body: []byte("john.doe")}, + Result: &Result{Body: []byte("john.doe")}, ExpectedSuccess: true, ExpectedOutput: "len([BODY]) == 8", }, { Name: "len-body-keyed-string", Condition: Condition("len([BODY].name) == 8"), - Result: &Result{body: []byte("{\"name\": \"john.doe\"}")}, + Result: &Result{Body: []byte("{\"name\": \"john.doe\"}")}, ExpectedSuccess: true, ExpectedOutput: "len([BODY].name) == 8", }, { Name: "len-body-keyed-int", Condition: Condition("len([BODY].age) == 2"), - Result: &Result{body: []byte(`{"age":18}`)}, + Result: &Result{Body: []byte(`{"age":18}`)}, ExpectedSuccess: true, ExpectedOutput: "len([BODY].age) == 2", }, { Name: "len-body-keyed-bool", Condition: Condition("len([BODY].adult) == 4"), - Result: &Result{body: []byte(`{"adult":true}`)}, + Result: &Result{Body: []byte(`{"adult":true}`)}, ExpectedSuccess: true, ExpectedOutput: "len([BODY].adult) == 4", }, { Name: "len-body-object-inside-array", Condition: Condition("len([BODY][0]) == 23"), - Result: &Result{body: []byte(`[{"age":18,"adult":true}]`)}, + Result: &Result{Body: []byte(`[{"age":18,"adult":true}]`)}, ExpectedSuccess: true, ExpectedOutput: "len([BODY][0]) == 23", }, { Name: "len-body-object-keyed-int-inside-array", Condition: Condition("len([BODY][0].age) == 2"), - Result: &Result{body: []byte(`[{"age":18,"adult":true}]`)}, + Result: &Result{Body: []byte(`[{"age":18,"adult":true}]`)}, ExpectedSuccess: true, ExpectedOutput: "len([BODY][0].age) == 2", }, { Name: "len-body-keyed-bool-inside-array", Condition: Condition("len([BODY][0].adult) == 4"), - Result: &Result{body: []byte(`[{"age":18,"adult":true}]`)}, + Result: &Result{Body: []byte(`[{"age":18,"adult":true}]`)}, ExpectedSuccess: true, ExpectedOutput: "len([BODY][0].adult) == 4", }, { Name: "len-body-object", Condition: Condition("len([BODY]) == 20"), - Result: &Result{body: []byte("{\"name\": \"john.doe\"}")}, + Result: &Result{Body: []byte("{\"name\": \"john.doe\"}")}, ExpectedSuccess: true, ExpectedOutput: "len([BODY]) == 20", }, @@ -463,49 +463,49 @@ func TestCondition_evaluate(t *testing.T) { { Name: "pat-body-1", Condition: Condition("[BODY] == pat(*john*)"), - Result: &Result{body: []byte("{\"name\": \"john.doe\"}")}, + Result: &Result{Body: []byte("{\"name\": \"john.doe\"}")}, ExpectedSuccess: true, ExpectedOutput: "[BODY] == pat(*john*)", }, { Name: "pat-body-2", Condition: Condition("[BODY].name == pat(john*)"), - Result: &Result{body: []byte("{\"name\": \"john.doe\"}")}, + Result: &Result{Body: []byte("{\"name\": \"john.doe\"}")}, ExpectedSuccess: true, ExpectedOutput: "[BODY].name == pat(john*)", }, { Name: "pat-body-failure", Condition: Condition("[BODY].name == pat(bob*)"), - Result: &Result{body: []byte("{\"name\": \"john.doe\"}")}, + Result: &Result{Body: []byte("{\"name\": \"john.doe\"}")}, ExpectedSuccess: false, ExpectedOutput: "[BODY].name (john.doe) == pat(bob*)", }, { Name: "pat-body-html", Condition: Condition("[BODY] == pat(*
john.doe
*)"), - Result: &Result{body: []byte(`
john.doe
`)}, + Result: &Result{Body: []byte(`
john.doe
`)}, ExpectedSuccess: true, ExpectedOutput: "[BODY] == pat(*
john.doe
*)", }, { Name: "pat-body-html-failure", Condition: Condition("[BODY] == pat(*
john.doe
*)"), - Result: &Result{body: []byte(`
jane.doe
`)}, + Result: &Result{Body: []byte(`
jane.doe
`)}, ExpectedSuccess: false, ExpectedOutput: "[BODY] (john.doe*)", }, { Name: "pat-body-html-failure-alt", Condition: Condition("pat(*
john.doe
*) == [BODY]"), - Result: &Result{body: []byte(`
jane.doe
`)}, + Result: &Result{Body: []byte(`
jane.doe
`)}, ExpectedSuccess: false, ExpectedOutput: "pat(*
john.doe
*) == [BODY] ( 0 - // Only read the body if there's a condition that uses the BodyPlaceholder + // Only read the Body if there's a condition that uses the BodyPlaceholder if endpoint.needsToReadBody() { - result.body, err = io.ReadAll(response.Body) + result.Body, err = io.ReadAll(response.Body) if err != nil { result.AddError("error reading response body:" + err.Error()) } @@ -387,7 +385,7 @@ func (endpoint *Endpoint) buildHTTPRequest() *http.Request { return request } -// needsToReadBody checks if there's any condition that requires the response body to be read +// needsToReadBody checks if there's any condition that requires the response Body to be read func (endpoint *Endpoint) needsToReadBody() bool { for _, condition := range endpoint.Conditions { if condition.hasBodyPlaceholder() { diff --git a/core/result.go b/core/result.go index 680653d7..ddbfad2e 100644 --- a/core/result.go +++ b/core/result.go @@ -44,12 +44,11 @@ type Result struct { // DomainExpiration is the duration before the domain expires DomainExpiration time.Duration `json:"-"` - // body is the response body + // Body is the response body // - // Note that this variable is only used during the evaluation of an Endpoint's health. - // This means that the call Endpoint.EvaluateHealth both populates the body (if necessary) - // and sets it to nil after the evaluation has been completed. - body []byte + // Note that this field is not persisted in the storage. + // It is used for health evaluation as well as debugging purposes. + Body []byte `json:"-"` } // AddError adds an error to the result's list of errors. diff --git a/watchdog/watchdog.go b/watchdog/watchdog.go index ab849511..8b987247 100644 --- a/watchdog/watchdog.go +++ b/watchdog/watchdog.go @@ -65,14 +65,11 @@ func execute(endpoint *core.Endpoint, alertingConfig *alerting.Config, maintenan metrics.PublishMetricsForEndpoint(endpoint, result) } UpdateEndpointStatuses(endpoint, result) - log.Printf( - "[watchdog][execute] Monitored group=%s; endpoint=%s; success=%v; errors=%d; duration=%s", - endpoint.Group, - endpoint.Name, - result.Success, - len(result.Errors), - result.Duration.Round(time.Millisecond), - ) + if debug && !result.Success { + log.Printf("[watchdog][execute] Monitored group=%s; endpoint=%s; success=%v; errors=%d; duration=%s; body=%s", endpoint.Group, endpoint.Name, result.Success, len(result.Errors), result.Duration.Round(time.Millisecond), result.Body) + } else { + log.Printf("[watchdog][execute] Monitored group=%s; endpoint=%s; success=%v; errors=%d; duration=%s", endpoint.Group, endpoint.Name, result.Success, len(result.Errors), result.Duration.Round(time.Millisecond)) + } if !maintenanceConfig.IsUnderMaintenance() { // TODO: Consider moving this after the monitoring lock is unlocked? I mean, how much noise can a single alerting provider cause... HandleAlerting(endpoint, result, alertingConfig, debug)