fix: Print response body on failure if debug is set to true

This commit is contained in:
TwiN 2023-03-14 20:02:31 -04:00
parent f8f61deb2c
commit 038c8c8d8e
8 changed files with 78 additions and 84 deletions

View File

@ -33,7 +33,7 @@ const (
// Values that could replace the placeholder: 1, 500, 1000, ... // Values that could replace the placeholder: 1, 500, 1000, ...
ResponseTimePlaceholder = "[RESPONSE_TIME]" 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"}}, ... // Values that could replace the placeholder: {}, {"data":{"name":"john"}}, ...
BodyPlaceholder = "[BODY]" BodyPlaceholder = "[BODY]"
@ -232,7 +232,7 @@ func isEqual(first, second string) bool {
func sanitizeAndResolve(elements []string, result *Result) ([]string, []string) { func sanitizeAndResolve(elements []string, result *Result) ([]string, []string) {
parameters := make([]string, len(elements)) parameters := make([]string, len(elements))
resolvedParameters := 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 { for i, element := range elements {
element = strings.TrimSpace(element) element = strings.TrimSpace(element)
parameters[i] = element parameters[i] = element
@ -266,7 +266,7 @@ func sanitizeAndResolve(elements []string, result *Result) ([]string, []string)
checkingForExistence = true checkingForExistence = true
element = strings.TrimSuffix(strings.TrimPrefix(element, HasFunctionPrefix), FunctionSuffix) 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 checkingForExistence {
if err != nil { if err != nil {
element = "false" element = "false"

View File

@ -5,7 +5,7 @@ import "testing"
func BenchmarkCondition_evaluateWithBodyStringAny(b *testing.B) { func BenchmarkCondition_evaluateWithBodyStringAny(b *testing.B) {
condition := Condition("[BODY].name == any(john.doe, jane.doe)") condition := Condition("[BODY].name == any(john.doe, jane.doe)")
for n := 0; n < b.N; n++ { 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) condition.evaluate(result, false)
} }
b.ReportAllocs() b.ReportAllocs()
@ -14,7 +14,7 @@ func BenchmarkCondition_evaluateWithBodyStringAny(b *testing.B) {
func BenchmarkCondition_evaluateWithBodyStringAnyFailure(b *testing.B) { func BenchmarkCondition_evaluateWithBodyStringAnyFailure(b *testing.B) {
condition := Condition("[BODY].name == any(john.doe, jane.doe)") condition := Condition("[BODY].name == any(john.doe, jane.doe)")
for n := 0; n < b.N; n++ { 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) condition.evaluate(result, false)
} }
b.ReportAllocs() b.ReportAllocs()
@ -23,7 +23,7 @@ func BenchmarkCondition_evaluateWithBodyStringAnyFailure(b *testing.B) {
func BenchmarkCondition_evaluateWithBodyString(b *testing.B) { func BenchmarkCondition_evaluateWithBodyString(b *testing.B) {
condition := Condition("[BODY].name == john.doe") condition := Condition("[BODY].name == john.doe")
for n := 0; n < b.N; n++ { 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) condition.evaluate(result, false)
} }
b.ReportAllocs() b.ReportAllocs()
@ -32,7 +32,7 @@ func BenchmarkCondition_evaluateWithBodyString(b *testing.B) {
func BenchmarkCondition_evaluateWithBodyStringFailure(b *testing.B) { func BenchmarkCondition_evaluateWithBodyStringFailure(b *testing.B) {
condition := Condition("[BODY].name == john.doe") condition := Condition("[BODY].name == john.doe")
for n := 0; n < b.N; n++ { 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) condition.evaluate(result, false)
} }
b.ReportAllocs() b.ReportAllocs()
@ -41,7 +41,7 @@ func BenchmarkCondition_evaluateWithBodyStringFailure(b *testing.B) {
func BenchmarkCondition_evaluateWithBodyStringFailureInvalidPath(b *testing.B) { func BenchmarkCondition_evaluateWithBodyStringFailureInvalidPath(b *testing.B) {
condition := Condition("[BODY].user.name == bob.doe") condition := Condition("[BODY].user.name == bob.doe")
for n := 0; n < b.N; n++ { 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) condition.evaluate(result, false)
} }
b.ReportAllocs() b.ReportAllocs()
@ -50,7 +50,7 @@ func BenchmarkCondition_evaluateWithBodyStringFailureInvalidPath(b *testing.B) {
func BenchmarkCondition_evaluateWithBodyStringLen(b *testing.B) { func BenchmarkCondition_evaluateWithBodyStringLen(b *testing.B) {
condition := Condition("len([BODY].name) == 8") condition := Condition("len([BODY].name) == 8")
for n := 0; n < b.N; n++ { 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) condition.evaluate(result, false)
} }
b.ReportAllocs() b.ReportAllocs()
@ -59,7 +59,7 @@ func BenchmarkCondition_evaluateWithBodyStringLen(b *testing.B) {
func BenchmarkCondition_evaluateWithBodyStringLenFailure(b *testing.B) { func BenchmarkCondition_evaluateWithBodyStringLenFailure(b *testing.B) {
condition := Condition("len([BODY].name) == 8") condition := Condition("len([BODY].name) == 8")
for n := 0; n < b.N; n++ { 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) condition.evaluate(result, false)
} }
b.ReportAllocs() b.ReportAllocs()

View File

@ -178,140 +178,140 @@ func TestCondition_evaluate(t *testing.T) {
{ {
Name: "body", Name: "body",
Condition: Condition("[BODY] == test"), Condition: Condition("[BODY] == test"),
Result: &Result{body: []byte("test")}, Result: &Result{Body: []byte("test")},
ExpectedSuccess: true, ExpectedSuccess: true,
ExpectedOutput: "[BODY] == test", ExpectedOutput: "[BODY] == test",
}, },
{ {
Name: "body-numerical-equal", Name: "body-numerical-equal",
Condition: Condition("[BODY] == 123"), Condition: Condition("[BODY] == 123"),
Result: &Result{body: []byte("123")}, Result: &Result{Body: []byte("123")},
ExpectedSuccess: true, ExpectedSuccess: true,
ExpectedOutput: "[BODY] == 123", ExpectedOutput: "[BODY] == 123",
}, },
{ {
Name: "body-numerical-less-than", Name: "body-numerical-less-than",
Condition: Condition("[BODY] < 124"), Condition: Condition("[BODY] < 124"),
Result: &Result{body: []byte("123")}, Result: &Result{Body: []byte("123")},
ExpectedSuccess: true, ExpectedSuccess: true,
ExpectedOutput: "[BODY] < 124", ExpectedOutput: "[BODY] < 124",
}, },
{ {
Name: "body-numerical-greater-than", Name: "body-numerical-greater-than",
Condition: Condition("[BODY] > 122"), Condition: Condition("[BODY] > 122"),
Result: &Result{body: []byte("123")}, Result: &Result{Body: []byte("123")},
ExpectedSuccess: true, ExpectedSuccess: true,
ExpectedOutput: "[BODY] > 122", ExpectedOutput: "[BODY] > 122",
}, },
{ {
Name: "body-numerical-greater-than-failure", Name: "body-numerical-greater-than-failure",
Condition: Condition("[BODY] > 123"), Condition: Condition("[BODY] > 123"),
Result: &Result{body: []byte("100")}, Result: &Result{Body: []byte("100")},
ExpectedSuccess: false, ExpectedSuccess: false,
ExpectedOutput: "[BODY] (100) > 123", ExpectedOutput: "[BODY] (100) > 123",
}, },
{ {
Name: "body-jsonpath", Name: "body-jsonpath",
Condition: Condition("[BODY].status == UP"), Condition: Condition("[BODY].status == UP"),
Result: &Result{body: []byte("{\"status\":\"UP\"}")}, Result: &Result{Body: []byte("{\"status\":\"UP\"}")},
ExpectedSuccess: true, ExpectedSuccess: true,
ExpectedOutput: "[BODY].status == UP", ExpectedOutput: "[BODY].status == UP",
}, },
{ {
Name: "body-jsonpath-complex", Name: "body-jsonpath-complex",
Condition: Condition("[BODY].data.name == john"), 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, ExpectedSuccess: true,
ExpectedOutput: "[BODY].data.name == john", ExpectedOutput: "[BODY].data.name == john",
}, },
{ {
Name: "body-jsonpath-complex-invalid", Name: "body-jsonpath-complex-invalid",
Condition: Condition("[BODY].data.name == john"), Condition: Condition("[BODY].data.name == john"),
Result: &Result{body: []byte("{\"data\": {\"id\": 1}}")}, Result: &Result{Body: []byte("{\"data\": {\"id\": 1}}")},
ExpectedSuccess: false, ExpectedSuccess: false,
ExpectedOutput: "[BODY].data.name (INVALID) == john", ExpectedOutput: "[BODY].data.name (INVALID) == john",
}, },
{ {
Name: "body-jsonpath-complex-len-invalid", Name: "body-jsonpath-complex-len-invalid",
Condition: Condition("len([BODY].data.name) == john"), Condition: Condition("len([BODY].data.name) == john"),
Result: &Result{body: []byte("{\"data\": {\"id\": 1}}")}, Result: &Result{Body: []byte("{\"data\": {\"id\": 1}}")},
ExpectedSuccess: false, ExpectedSuccess: false,
ExpectedOutput: "len([BODY].data.name) (INVALID) == john", ExpectedOutput: "len([BODY].data.name) (INVALID) == john",
}, },
{ {
Name: "body-jsonpath-double-placeholder", Name: "body-jsonpath-double-placeholder",
Condition: Condition("[BODY].user.firstName != [BODY].user.lastName"), 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, ExpectedSuccess: true,
ExpectedOutput: "[BODY].user.firstName != [BODY].user.lastName", ExpectedOutput: "[BODY].user.firstName != [BODY].user.lastName",
}, },
{ {
Name: "body-jsonpath-double-placeholder-failure", Name: "body-jsonpath-double-placeholder-failure",
Condition: Condition("[BODY].user.firstName == [BODY].user.lastName"), 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, ExpectedSuccess: false,
ExpectedOutput: "[BODY].user.firstName (john) == [BODY].user.lastName (doe)", ExpectedOutput: "[BODY].user.firstName (john) == [BODY].user.lastName (doe)",
}, },
{ {
Name: "body-jsonpath-when-body-is-array", Name: "body-jsonpath-when-body-is-array",
Condition: Condition("[BODY][0].id == 1"), Condition: Condition("[BODY][0].id == 1"),
Result: &Result{body: []byte("[{\"id\": 1}, {\"id\": 2}]")}, Result: &Result{Body: []byte("[{\"id\": 1}, {\"id\": 2}]")},
ExpectedSuccess: true, ExpectedSuccess: true,
ExpectedOutput: "[BODY][0].id == 1", ExpectedOutput: "[BODY][0].id == 1",
}, },
{ {
Name: "body-jsonpath-when-body-is-array-but-actual-body-is-not", Name: "body-jsonpath-when-body-is-array-but-actual-body-is-not",
Condition: Condition("[BODY][0].name == test"), 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, ExpectedSuccess: false,
ExpectedOutput: "[BODY][0].name (INVALID) == test", ExpectedOutput: "[BODY][0].name (INVALID) == test",
}, },
{ {
Name: "body-jsonpath-complex-int", Name: "body-jsonpath-complex-int",
Condition: Condition("[BODY].data.id == 1"), Condition: Condition("[BODY].data.id == 1"),
Result: &Result{body: []byte("{\"data\": {\"id\": 1}}")}, Result: &Result{Body: []byte("{\"data\": {\"id\": 1}}")},
ExpectedSuccess: true, ExpectedSuccess: true,
ExpectedOutput: "[BODY].data.id == 1", ExpectedOutput: "[BODY].data.id == 1",
}, },
{ {
Name: "body-jsonpath-complex-array-int", Name: "body-jsonpath-complex-array-int",
Condition: Condition("[BODY].data[1].id == 2"), 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, ExpectedSuccess: true,
ExpectedOutput: "[BODY].data[1].id == 2", ExpectedOutput: "[BODY].data[1].id == 2",
}, },
{ {
Name: "body-jsonpath-complex-int-using-greater-than", Name: "body-jsonpath-complex-int-using-greater-than",
Condition: Condition("[BODY].data.id > 0"), Condition: Condition("[BODY].data.id > 0"),
Result: &Result{body: []byte("{\"data\": {\"id\": 1}}")}, Result: &Result{Body: []byte("{\"data\": {\"id\": 1}}")},
ExpectedSuccess: true, ExpectedSuccess: true,
ExpectedOutput: "[BODY].data.id > 0", ExpectedOutput: "[BODY].data.id > 0",
}, },
{ {
Name: "body-jsonpath-complex-int-using-greater-than-failure", Name: "body-jsonpath-complex-int-using-greater-than-failure",
Condition: Condition("[BODY].data.id > 5"), Condition: Condition("[BODY].data.id > 5"),
Result: &Result{body: []byte("{\"data\": {\"id\": 1}}")}, Result: &Result{Body: []byte("{\"data\": {\"id\": 1}}")},
ExpectedSuccess: false, ExpectedSuccess: false,
ExpectedOutput: "[BODY].data.id (1) > 5", 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 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"), Condition: Condition("[BODY].balance > 100"),
Result: &Result{body: []byte(`{"balance": "123.40000000000005"}`)}, Result: &Result{Body: []byte(`{"balance": "123.40000000000005"}`)},
ExpectedSuccess: true, ExpectedSuccess: true,
ExpectedOutput: "[BODY].balance > 100", ExpectedOutput: "[BODY].balance > 100",
}, },
{ {
Name: "body-jsonpath-complex-int-using-less-than", Name: "body-jsonpath-complex-int-using-less-than",
Condition: Condition("[BODY].data.id < 5"), Condition: Condition("[BODY].data.id < 5"),
Result: &Result{body: []byte("{\"data\": {\"id\": 2}}")}, Result: &Result{Body: []byte("{\"data\": {\"id\": 2}}")},
ExpectedSuccess: true, ExpectedSuccess: true,
ExpectedOutput: "[BODY].data.id < 5", ExpectedOutput: "[BODY].data.id < 5",
}, },
{ {
Name: "body-jsonpath-complex-int-using-less-than-failure", Name: "body-jsonpath-complex-int-using-less-than-failure",
Condition: Condition("[BODY].data.id < 5"), Condition: Condition("[BODY].data.id < 5"),
Result: &Result{body: []byte("{\"data\": {\"id\": 10}}")}, Result: &Result{Body: []byte("{\"data\": {\"id\": 10}}")},
ExpectedSuccess: false, ExpectedSuccess: false,
ExpectedOutput: "[BODY].data.id (10) < 5", ExpectedOutput: "[BODY].data.id (10) < 5",
}, },
@ -378,84 +378,84 @@ func TestCondition_evaluate(t *testing.T) {
{ {
Name: "len-body-jsonpath-complex", Name: "len-body-jsonpath-complex",
Condition: Condition("len([BODY].data.name) == 4"), Condition: Condition("len([BODY].data.name) == 4"),
Result: &Result{body: []byte("{\"data\": {\"name\": \"john\"}}")}, Result: &Result{Body: []byte("{\"data\": {\"name\": \"john\"}}")},
ExpectedSuccess: true, ExpectedSuccess: true,
ExpectedOutput: "len([BODY].data.name) == 4", ExpectedOutput: "len([BODY].data.name) == 4",
}, },
{ {
Name: "len-body-array", Name: "len-body-array",
Condition: Condition("len([BODY]) == 3"), 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, ExpectedSuccess: true,
ExpectedOutput: "len([BODY]) == 3", ExpectedOutput: "len([BODY]) == 3",
}, },
{ {
Name: "len-body-keyed-array", Name: "len-body-keyed-array",
Condition: Condition("len([BODY].data) == 3"), 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, ExpectedSuccess: true,
ExpectedOutput: "len([BODY].data) == 3", ExpectedOutput: "len([BODY].data) == 3",
}, },
{ {
Name: "len-body-array-invalid", Name: "len-body-array-invalid",
Condition: Condition("len([BODY].data) == 8"), Condition: Condition("len([BODY].data) == 8"),
Result: &Result{body: []byte("{\"name\": \"john.doe\"}")}, Result: &Result{Body: []byte("{\"name\": \"john.doe\"}")},
ExpectedSuccess: false, ExpectedSuccess: false,
ExpectedOutput: "len([BODY].data) (INVALID) == 8", ExpectedOutput: "len([BODY].data) (INVALID) == 8",
}, },
{ {
Name: "len-body-string", Name: "len-body-string",
Condition: Condition("len([BODY]) == 8"), Condition: Condition("len([BODY]) == 8"),
Result: &Result{body: []byte("john.doe")}, Result: &Result{Body: []byte("john.doe")},
ExpectedSuccess: true, ExpectedSuccess: true,
ExpectedOutput: "len([BODY]) == 8", ExpectedOutput: "len([BODY]) == 8",
}, },
{ {
Name: "len-body-keyed-string", Name: "len-body-keyed-string",
Condition: Condition("len([BODY].name) == 8"), Condition: Condition("len([BODY].name) == 8"),
Result: &Result{body: []byte("{\"name\": \"john.doe\"}")}, Result: &Result{Body: []byte("{\"name\": \"john.doe\"}")},
ExpectedSuccess: true, ExpectedSuccess: true,
ExpectedOutput: "len([BODY].name) == 8", ExpectedOutput: "len([BODY].name) == 8",
}, },
{ {
Name: "len-body-keyed-int", Name: "len-body-keyed-int",
Condition: Condition("len([BODY].age) == 2"), Condition: Condition("len([BODY].age) == 2"),
Result: &Result{body: []byte(`{"age":18}`)}, Result: &Result{Body: []byte(`{"age":18}`)},
ExpectedSuccess: true, ExpectedSuccess: true,
ExpectedOutput: "len([BODY].age) == 2", ExpectedOutput: "len([BODY].age) == 2",
}, },
{ {
Name: "len-body-keyed-bool", Name: "len-body-keyed-bool",
Condition: Condition("len([BODY].adult) == 4"), Condition: Condition("len([BODY].adult) == 4"),
Result: &Result{body: []byte(`{"adult":true}`)}, Result: &Result{Body: []byte(`{"adult":true}`)},
ExpectedSuccess: true, ExpectedSuccess: true,
ExpectedOutput: "len([BODY].adult) == 4", ExpectedOutput: "len([BODY].adult) == 4",
}, },
{ {
Name: "len-body-object-inside-array", Name: "len-body-object-inside-array",
Condition: Condition("len([BODY][0]) == 23"), Condition: Condition("len([BODY][0]) == 23"),
Result: &Result{body: []byte(`[{"age":18,"adult":true}]`)}, Result: &Result{Body: []byte(`[{"age":18,"adult":true}]`)},
ExpectedSuccess: true, ExpectedSuccess: true,
ExpectedOutput: "len([BODY][0]) == 23", ExpectedOutput: "len([BODY][0]) == 23",
}, },
{ {
Name: "len-body-object-keyed-int-inside-array", Name: "len-body-object-keyed-int-inside-array",
Condition: Condition("len([BODY][0].age) == 2"), 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, ExpectedSuccess: true,
ExpectedOutput: "len([BODY][0].age) == 2", ExpectedOutput: "len([BODY][0].age) == 2",
}, },
{ {
Name: "len-body-keyed-bool-inside-array", Name: "len-body-keyed-bool-inside-array",
Condition: Condition("len([BODY][0].adult) == 4"), 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, ExpectedSuccess: true,
ExpectedOutput: "len([BODY][0].adult) == 4", ExpectedOutput: "len([BODY][0].adult) == 4",
}, },
{ {
Name: "len-body-object", Name: "len-body-object",
Condition: Condition("len([BODY]) == 20"), Condition: Condition("len([BODY]) == 20"),
Result: &Result{body: []byte("{\"name\": \"john.doe\"}")}, Result: &Result{Body: []byte("{\"name\": \"john.doe\"}")},
ExpectedSuccess: true, ExpectedSuccess: true,
ExpectedOutput: "len([BODY]) == 20", ExpectedOutput: "len([BODY]) == 20",
}, },
@ -463,49 +463,49 @@ func TestCondition_evaluate(t *testing.T) {
{ {
Name: "pat-body-1", Name: "pat-body-1",
Condition: Condition("[BODY] == pat(*john*)"), Condition: Condition("[BODY] == pat(*john*)"),
Result: &Result{body: []byte("{\"name\": \"john.doe\"}")}, Result: &Result{Body: []byte("{\"name\": \"john.doe\"}")},
ExpectedSuccess: true, ExpectedSuccess: true,
ExpectedOutput: "[BODY] == pat(*john*)", ExpectedOutput: "[BODY] == pat(*john*)",
}, },
{ {
Name: "pat-body-2", Name: "pat-body-2",
Condition: Condition("[BODY].name == pat(john*)"), Condition: Condition("[BODY].name == pat(john*)"),
Result: &Result{body: []byte("{\"name\": \"john.doe\"}")}, Result: &Result{Body: []byte("{\"name\": \"john.doe\"}")},
ExpectedSuccess: true, ExpectedSuccess: true,
ExpectedOutput: "[BODY].name == pat(john*)", ExpectedOutput: "[BODY].name == pat(john*)",
}, },
{ {
Name: "pat-body-failure", Name: "pat-body-failure",
Condition: Condition("[BODY].name == pat(bob*)"), Condition: Condition("[BODY].name == pat(bob*)"),
Result: &Result{body: []byte("{\"name\": \"john.doe\"}")}, Result: &Result{Body: []byte("{\"name\": \"john.doe\"}")},
ExpectedSuccess: false, ExpectedSuccess: false,
ExpectedOutput: "[BODY].name (john.doe) == pat(bob*)", ExpectedOutput: "[BODY].name (john.doe) == pat(bob*)",
}, },
{ {
Name: "pat-body-html", Name: "pat-body-html",
Condition: Condition("[BODY] == pat(*<div id=\"user\">john.doe</div>*)"), Condition: Condition("[BODY] == pat(*<div id=\"user\">john.doe</div>*)"),
Result: &Result{body: []byte(`<!DOCTYPE html><html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /></head><body><div id="user">john.doe</div></body></html>`)}, Result: &Result{Body: []byte(`<!DOCTYPE html><html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /></head><body><div id="user">john.doe</div></body></html>`)},
ExpectedSuccess: true, ExpectedSuccess: true,
ExpectedOutput: "[BODY] == pat(*<div id=\"user\">john.doe</div>*)", ExpectedOutput: "[BODY] == pat(*<div id=\"user\">john.doe</div>*)",
}, },
{ {
Name: "pat-body-html-failure", Name: "pat-body-html-failure",
Condition: Condition("[BODY] == pat(*<div id=\"user\">john.doe</div>*)"), Condition: Condition("[BODY] == pat(*<div id=\"user\">john.doe</div>*)"),
Result: &Result{body: []byte(`<!DOCTYPE html><html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /></head><body><div id="user">jane.doe</div></body></html>`)}, Result: &Result{Body: []byte(`<!DOCTYPE html><html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /></head><body><div id="user">jane.doe</div></body></html>`)},
ExpectedSuccess: false, ExpectedSuccess: false,
ExpectedOutput: "[BODY] (<!DOCTYPE html><html lang...(truncated)) == pat(*<div id=\"user\">john.doe</div>*)", ExpectedOutput: "[BODY] (<!DOCTYPE html><html lang...(truncated)) == pat(*<div id=\"user\">john.doe</div>*)",
}, },
{ {
Name: "pat-body-html-failure-alt", Name: "pat-body-html-failure-alt",
Condition: Condition("pat(*<div id=\"user\">john.doe</div>*) == [BODY]"), Condition: Condition("pat(*<div id=\"user\">john.doe</div>*) == [BODY]"),
Result: &Result{body: []byte(`<!DOCTYPE html><html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /></head><body><div id="user">jane.doe</div></body></html>`)}, Result: &Result{Body: []byte(`<!DOCTYPE html><html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /></head><body><div id="user">jane.doe</div></body></html>`)},
ExpectedSuccess: false, ExpectedSuccess: false,
ExpectedOutput: "pat(*<div id=\"user\">john.doe</div>*) == [BODY] (<!DOCTYPE html><html lang...(truncated))", ExpectedOutput: "pat(*<div id=\"user\">john.doe</div>*) == [BODY] (<!DOCTYPE html><html lang...(truncated))",
}, },
{ {
Name: "pat-body-in-array", Name: "pat-body-in-array",
Condition: Condition("[BODY].data == pat(*Whatever*)"), Condition: Condition("[BODY].data == pat(*Whatever*)"),
Result: &Result{body: []byte("{\"data\": [\"hello\", \"world\", \"Whatever\"]}")}, Result: &Result{Body: []byte("{\"data\": [\"hello\", \"world\", \"Whatever\"]}")},
ExpectedSuccess: true, ExpectedSuccess: true,
ExpectedOutput: "[BODY].data == pat(*Whatever*)", ExpectedOutput: "[BODY].data == pat(*Whatever*)",
}, },
@ -541,21 +541,21 @@ func TestCondition_evaluate(t *testing.T) {
{ {
Name: "any-body-1", Name: "any-body-1",
Condition: Condition("[BODY].name == any(john.doe, jane.doe)"), Condition: Condition("[BODY].name == any(john.doe, jane.doe)"),
Result: &Result{body: []byte("{\"name\": \"john.doe\"}")}, Result: &Result{Body: []byte("{\"name\": \"john.doe\"}")},
ExpectedSuccess: true, ExpectedSuccess: true,
ExpectedOutput: "[BODY].name == any(john.doe, jane.doe)", ExpectedOutput: "[BODY].name == any(john.doe, jane.doe)",
}, },
{ {
Name: "any-body-2", Name: "any-body-2",
Condition: Condition("[BODY].name == any(john.doe, jane.doe)"), Condition: Condition("[BODY].name == any(john.doe, jane.doe)"),
Result: &Result{body: []byte("{\"name\": \"jane.doe\"}")}, Result: &Result{Body: []byte("{\"name\": \"jane.doe\"}")},
ExpectedSuccess: true, ExpectedSuccess: true,
ExpectedOutput: "[BODY].name == any(john.doe, jane.doe)", ExpectedOutput: "[BODY].name == any(john.doe, jane.doe)",
}, },
{ {
Name: "any-body-failure", Name: "any-body-failure",
Condition: Condition("[BODY].name == any(john.doe, jane.doe)"), Condition: Condition("[BODY].name == any(john.doe, jane.doe)"),
Result: &Result{body: []byte("{\"name\": \"bob\"}")}, Result: &Result{Body: []byte("{\"name\": \"bob\"}")},
ExpectedSuccess: false, ExpectedSuccess: false,
ExpectedOutput: "[BODY].name (bob) == any(john.doe, jane.doe)", ExpectedOutput: "[BODY].name (bob) == any(john.doe, jane.doe)",
}, },
@ -599,14 +599,14 @@ func TestCondition_evaluate(t *testing.T) {
{ {
Name: "has", Name: "has",
Condition: Condition("has([BODY].errors) == false"), Condition: Condition("has([BODY].errors) == false"),
Result: &Result{body: []byte("{}")}, Result: &Result{Body: []byte("{}")},
ExpectedSuccess: true, ExpectedSuccess: true,
ExpectedOutput: "has([BODY].errors) == false", ExpectedOutput: "has([BODY].errors) == false",
}, },
{ {
Name: "has-key-of-map", Name: "has-key-of-map",
Condition: Condition("has([BODY].article) == true"), Condition: Condition("has([BODY].article) == true"),
Result: &Result{body: []byte("{\n \"article\": {\n \"id\": 123,\n \"title\": \"Hello, world!\",\n \"author\": \"John Doe\",\n \"tags\": [\"hello\", \"world\"],\n \"content\": \"I really like Gatus!\"\n }\n}")}, Result: &Result{Body: []byte("{\n \"article\": {\n \"id\": 123,\n \"title\": \"Hello, world!\",\n \"author\": \"John Doe\",\n \"tags\": [\"hello\", \"world\"],\n \"content\": \"I really like Gatus!\"\n }\n}")},
DontResolveFailedConditions: false, DontResolveFailedConditions: false,
ExpectedSuccess: true, ExpectedSuccess: true,
ExpectedOutput: "has([BODY].article) == true", ExpectedOutput: "has([BODY].article) == true",
@ -614,14 +614,14 @@ func TestCondition_evaluate(t *testing.T) {
{ {
Name: "has-failure", Name: "has-failure",
Condition: Condition("has([BODY].errors) == false"), Condition: Condition("has([BODY].errors) == false"),
Result: &Result{body: []byte("{\"errors\": [\"1\"]}")}, Result: &Result{Body: []byte("{\"errors\": [\"1\"]}")},
ExpectedSuccess: false, ExpectedSuccess: false,
ExpectedOutput: "has([BODY].errors) (true) == false", ExpectedOutput: "has([BODY].errors) (true) == false",
}, },
{ {
Name: "has-failure-but-dont-resolve", Name: "has-failure-but-dont-resolve",
Condition: Condition("has([BODY].errors) == false"), Condition: Condition("has([BODY].errors) == false"),
Result: &Result{body: []byte("{\"errors\": [\"1\"]}")}, Result: &Result{Body: []byte("{\"errors\": [\"1\"]}")},
DontResolveFailedConditions: true, DontResolveFailedConditions: true,
ExpectedSuccess: false, ExpectedSuccess: false,
ExpectedOutput: "has([BODY].errors) == false", ExpectedOutput: "has([BODY].errors) == false",

View File

@ -61,26 +61,26 @@ func (d *DNS) query(url string, result *Result) {
switch rr.Header().Rrtype { switch rr.Header().Rrtype {
case dns.TypeA: case dns.TypeA:
if a, ok := rr.(*dns.A); ok { if a, ok := rr.(*dns.A); ok {
result.body = []byte(a.A.String()) result.Body = []byte(a.A.String())
} }
case dns.TypeAAAA: case dns.TypeAAAA:
if aaaa, ok := rr.(*dns.AAAA); ok { if aaaa, ok := rr.(*dns.AAAA); ok {
result.body = []byte(aaaa.AAAA.String()) result.Body = []byte(aaaa.AAAA.String())
} }
case dns.TypeCNAME: case dns.TypeCNAME:
if cname, ok := rr.(*dns.CNAME); ok { if cname, ok := rr.(*dns.CNAME); ok {
result.body = []byte(cname.Target) result.Body = []byte(cname.Target)
} }
case dns.TypeMX: case dns.TypeMX:
if mx, ok := rr.(*dns.MX); ok { if mx, ok := rr.(*dns.MX); ok {
result.body = []byte(mx.Mx) result.Body = []byte(mx.Mx)
} }
case dns.TypeNS: case dns.TypeNS:
if ns, ok := rr.(*dns.NS); ok { if ns, ok := rr.(*dns.NS); ok {
result.body = []byte(ns.Ns) result.Body = []byte(ns.Ns)
} }
default: default:
result.body = []byte("query type is not supported yet") result.Body = []byte("query type is not supported yet")
} }
} }
} }

View File

@ -90,12 +90,12 @@ func TestIntegrationQuery(t *testing.T) {
} }
if test.inputDNS.QueryType == "NS" { if test.inputDNS.QueryType == "NS" {
// Because there are often multiple nameservers backing a single domain, we'll only look at the suffix // Because there are often multiple nameservers backing a single domain, we'll only look at the suffix
if !pattern.Match(test.expectedBody, string(result.body)) { if !pattern.Match(test.expectedBody, string(result.Body)) {
t.Errorf("got %s, expected result %s,", string(result.body), test.expectedBody) t.Errorf("got %s, expected result %s,", string(result.Body), test.expectedBody)
} }
} else { } else {
if string(result.body) != test.expectedBody { if string(result.Body) != test.expectedBody {
t.Errorf("got %s, expected result %s,", string(result.body), test.expectedBody) t.Errorf("got %s, expected result %s,", string(result.Body), test.expectedBody)
} }
} }
}) })

View File

@ -280,8 +280,6 @@ func (endpoint *Endpoint) EvaluateHealth() *Result {
} }
} }
result.Timestamp = time.Now() result.Timestamp = time.Now()
// No need to keep the body after the endpoint has been evaluated
result.body = nil
// Clean up parameters that we don't need to keep in the results // Clean up parameters that we don't need to keep in the results
if endpoint.UIConfig.HideURL { if endpoint.UIConfig.HideURL {
for errIdx, errorString := range result.Errors { for errIdx, errorString := range result.Errors {
@ -356,9 +354,9 @@ func (endpoint *Endpoint) call(result *Result) {
} }
result.HTTPStatus = response.StatusCode result.HTTPStatus = response.StatusCode
result.Connected = response.StatusCode > 0 result.Connected = response.StatusCode > 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() { if endpoint.needsToReadBody() {
result.body, err = io.ReadAll(response.Body) result.Body, err = io.ReadAll(response.Body)
if err != nil { if err != nil {
result.AddError("error reading response body:" + err.Error()) result.AddError("error reading response body:" + err.Error())
} }
@ -387,7 +385,7 @@ func (endpoint *Endpoint) buildHTTPRequest() *http.Request {
return 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 { func (endpoint *Endpoint) needsToReadBody() bool {
for _, condition := range endpoint.Conditions { for _, condition := range endpoint.Conditions {
if condition.hasBodyPlaceholder() { if condition.hasBodyPlaceholder() {

View File

@ -44,12 +44,11 @@ type Result struct {
// DomainExpiration is the duration before the domain expires // DomainExpiration is the duration before the domain expires
DomainExpiration time.Duration `json:"-"` 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. // Note that this field is not persisted in the storage.
// This means that the call Endpoint.EvaluateHealth both populates the body (if necessary) // It is used for health evaluation as well as debugging purposes.
// and sets it to nil after the evaluation has been completed. Body []byte `json:"-"`
body []byte
} }
// AddError adds an error to the result's list of errors. // AddError adds an error to the result's list of errors.

View File

@ -65,14 +65,11 @@ func execute(endpoint *core.Endpoint, alertingConfig *alerting.Config, maintenan
metrics.PublishMetricsForEndpoint(endpoint, result) metrics.PublishMetricsForEndpoint(endpoint, result)
} }
UpdateEndpointStatuses(endpoint, result) UpdateEndpointStatuses(endpoint, result)
log.Printf( if debug && !result.Success {
"[watchdog][execute] Monitored group=%s; endpoint=%s; success=%v; errors=%d; duration=%s", 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)
endpoint.Group, } else {
endpoint.Name, 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))
result.Success, }
len(result.Errors),
result.Duration.Round(time.Millisecond),
)
if !maintenanceConfig.IsUnderMaintenance() { if !maintenanceConfig.IsUnderMaintenance() {
// TODO: Consider moving this after the monitoring lock is unlocked? I mean, how much noise can a single alerting provider cause... // 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) HandleAlerting(endpoint, result, alertingConfig, debug)