Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da92907873 | ||
|
|
937b136e60 | ||
|
|
12db0d7c40 | ||
|
|
f50589e3c4 | ||
|
|
98221626d3 | ||
|
|
60e30da7e5 | ||
|
|
b05ae1c2d2 | ||
|
|
3b309500c3 | ||
|
|
bf2fbcb395 | ||
|
|
fb90a0b299 |
@@ -186,7 +186,7 @@
|
|||||||
same "printed page" as the copyright notice for easier
|
same "printed page" as the copyright notice for easier
|
||||||
identification within third-party archives.
|
identification within third-party archives.
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
Copyright 2020 TwinProduction
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
78
README.md
78
README.md
@@ -40,32 +40,35 @@ Note that you can also add environment variables in the your configuration file
|
|||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
| Parameter | Description | Default |
|
| Parameter | Description | Default |
|
||||||
| ----------------------- | ------------------------------------------------------ | -------------- |
|
| ----------------------- | --------------------------------------------------------------- | -------------- |
|
||||||
| `metrics` | Whether to expose metrics at /metrics | `false` |
|
| `metrics` | Whether to expose metrics at /metrics | `false` |
|
||||||
| `services[].name` | Name of the service. Can be anything. | Required `""` |
|
| `services[].name` | Name of the service. Can be anything. | Required `""` |
|
||||||
| `services[].url` | URL to send the request to | Required `""` |
|
| `services[].url` | URL to send the request to | Required `""` |
|
||||||
| `services[].conditions` | Conditions used to determine the health of the service | `[]` |
|
| `services[].conditions` | Conditions used to determine the health of the service | `[]` |
|
||||||
| `services[].interval` | Duration to wait between every status check | `10s` |
|
| `services[].interval` | Duration to wait between every status check | `10s` |
|
||||||
| `services[].method` | Request method | `GET` |
|
| `services[].method` | Request method | `GET` |
|
||||||
| `services[].body` | Request body | `""` |
|
| `services[].graphql` | Whether to wrap the body in a query param (`{"query":"$body"}`) | `false` |
|
||||||
| `services[].headers` | Request headers | `{}` |
|
| `services[].body` | Request body | `""` |
|
||||||
|
| `services[].headers` | Request headers | `{}` |
|
||||||
|
|
||||||
|
|
||||||
### Conditions
|
### Conditions
|
||||||
|
|
||||||
Here are some examples of conditions you can use:
|
Here are some examples of conditions you can use:
|
||||||
|
|
||||||
| Condition | Description | Passing values | Failing values |
|
| Condition | Description | Passing values | Failing values |
|
||||||
| ------------------------------------- | ----------------------------------------- | ------------------------ | ----------------------- |
|
| -----------------------------| ------------------------------------------------------- | ------------------------ | ----------------------- |
|
||||||
| `[STATUS] == 200` | Status must be equal to 200 | 200 | 201, 404, 500 |
|
| `[STATUS] == 200` | Status must be equal to 200 | 200 | 201, 404, 500 |
|
||||||
| `[STATUS] < 300` | Status must lower than 300 | 200, 201, 299 | 301, 302, 400, 500 |
|
| `[STATUS] < 300` | Status must lower than 300 | 200, 201, 299 | 301, 302, 400, 500 |
|
||||||
| `[STATUS] <= 299` | Status must be less than or equal to 299 | 200, 201, 299 | 301, 302, 400, 500 |
|
| `[STATUS] <= 299` | Status must be less than or equal to 299 | 200, 201, 299 | 301, 302, 400, 500 |
|
||||||
| `[STATUS] > 400` | Status must be greater than 400 | 401, 402, 403, 404 | 200, 201, 300, 400 |
|
| `[STATUS] > 400` | Status must be greater than 400 | 401, 402, 403, 404 | 200, 201, 300, 400 |
|
||||||
| `[RESPONSE_TIME] < 500` | Response time must be below 500ms | 100ms, 200ms, 300ms | 500ms, 1500ms |
|
| `[RESPONSE_TIME] < 500` | Response time must be below 500ms | 100ms, 200ms, 300ms | 500ms, 1500ms |
|
||||||
| `[BODY] == 1` | The body must be equal to 1 | 1 | literally anything else |
|
| `[BODY] == 1` | The body must be equal to 1 | 1 | literally anything else |
|
||||||
| `[BODY].data.id == 1` | The jsonpath `$.data.id` is equal to 1 | `{"data":{"id":1}}` | literally anything else |
|
| `[BODY].data.id == 1` | The jsonpath `$.data.id` is equal to 1 | `{"data":{"id":1}}` | literally anything else |
|
||||||
| `[BODY].data[0].id == 1` | The jsonpath `$.data[0].id` is equal to 1 | `{"data":[{"id":1}]}` | literally anything else |
|
| `[BODY].data[0].id == 1` | The jsonpath `$.data[0].id` is equal to 1 | `{"data":[{"id":1}]}` | literally anything else |
|
||||||
|
| `len([BODY].data) > 0` | Array at jsonpath `$.data` has less than 5 elements | `{"data":[{"id":1}]}` | `{"data":[{"id":1}]}` |
|
||||||
|
| `len([BODY].name) == 8` | String at jsonpath `$.name` has a length of 8 | `{"name":"john.doe"}` | `{"name":"bob"}` |
|
||||||
|
|
||||||
**NOTE**: `[BODY]` with JSON path (i.e. `[BODY].id == 1`) is currently in BETA. For the most part, the only thing that doesn't work is arrays.
|
**NOTE**: `[BODY]` with JSON path (i.e. `[BODY].id == 1`) is currently in BETA. For the most part, the only thing that doesn't work is arrays.
|
||||||
|
|
||||||
@@ -95,3 +98,38 @@ go test ./... -mod vendor
|
|||||||
## Using in Production
|
## Using in Production
|
||||||
|
|
||||||
See the [example](example) folder.
|
See the [example](example) folder.
|
||||||
|
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
### Sending a GraphQL request
|
||||||
|
|
||||||
|
By setting `services[].graphql` to true, the body will automatically be wrapped by the standard GraphQL `query` parameter.
|
||||||
|
|
||||||
|
For instance, the following configuration:
|
||||||
|
```
|
||||||
|
services:
|
||||||
|
- name: filter users by gender
|
||||||
|
url: http://localhost:8080/playground
|
||||||
|
method: POST
|
||||||
|
graphql: true
|
||||||
|
body: |
|
||||||
|
{
|
||||||
|
user(gender: "female") {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
gender
|
||||||
|
avatar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
headers:
|
||||||
|
Content-Type: application/json
|
||||||
|
conditions:
|
||||||
|
- "[STATUS] == 200"
|
||||||
|
- "[BODY].data.user[0].gender == female"
|
||||||
|
```
|
||||||
|
|
||||||
|
will send a `POST` request to `http://localhost:8080/playground` with the following body:
|
||||||
|
```json
|
||||||
|
{"query":" {\n user(gender: \"female\") {\n id\n name\n gender\n avatar\n }\n }"}
|
||||||
|
```
|
||||||
@@ -14,8 +14,4 @@ services:
|
|||||||
- "[STATUS] == 200"
|
- "[STATUS] == 200"
|
||||||
- "[BODY].id == 24"
|
- "[BODY].id == 24"
|
||||||
- "[BODY].tags[0] == spring"
|
- "[BODY].tags[0] == spring"
|
||||||
- name: example
|
- "len([BODY].tags) > 0"
|
||||||
url: https://example.org/
|
|
||||||
interval: 30s
|
|
||||||
conditions:
|
|
||||||
- "[STATUS] == 200"
|
|
||||||
@@ -9,10 +9,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
const (
|
||||||
Metrics bool `yaml:"metrics"`
|
DefaultConfigurationFilePath = "config/config.yaml"
|
||||||
Services []*core.Service `yaml:"services"`
|
)
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrNoServiceInConfig = errors.New("configuration file should contain at least 1 service")
|
ErrNoServiceInConfig = errors.New("configuration file should contain at least 1 service")
|
||||||
@@ -21,6 +20,11 @@ var (
|
|||||||
config *Config
|
config *Config
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Metrics bool `yaml:"metrics"`
|
||||||
|
Services []*core.Service `yaml:"services"`
|
||||||
|
}
|
||||||
|
|
||||||
func Get() *Config {
|
func Get() *Config {
|
||||||
if config == nil {
|
if config == nil {
|
||||||
panic(ErrConfigNotLoaded)
|
panic(ErrConfigNotLoaded)
|
||||||
@@ -43,7 +47,7 @@ func Load(configFile string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LoadDefaultConfiguration() error {
|
func LoadDefaultConfiguration() error {
|
||||||
err := Load("config/config.yaml")
|
err := Load(DefaultConfigurationFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == ErrConfigFileNotFound {
|
if err == ErrConfigFileNotFound {
|
||||||
return Load("config/config.yml")
|
return Load("config/config.yml")
|
||||||
|
|||||||
@@ -41,10 +41,10 @@ func (c *Condition) evaluate(result *Result) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
conditionToDisplay := condition
|
conditionToDisplay := condition
|
||||||
// If the condition isn't a success, return the resolved condition
|
// If the condition isn't a success, return what the resolved condition was too
|
||||||
if !success {
|
if !success {
|
||||||
log.Printf("[Condition][evaluate] Condition '%s' did not succeed because '%s' is false", condition, resolvedCondition)
|
log.Printf("[Condition][evaluate] Condition '%s' did not succeed because '%s' is false", condition, resolvedCondition)
|
||||||
conditionToDisplay = resolvedCondition
|
conditionToDisplay = fmt.Sprintf("%s (%s)", condition, resolvedCondition)
|
||||||
}
|
}
|
||||||
result.ConditionResults = append(result.ConditionResults, &ConditionResult{Condition: conditionToDisplay, Success: success})
|
result.ConditionResults = append(result.ConditionResults, &ConditionResult{Condition: conditionToDisplay, Success: success})
|
||||||
return success
|
return success
|
||||||
|
|||||||
@@ -166,3 +166,21 @@ func TestCondition_evaluateWithBodyJsonPathComplexIntFailureUsingLessThan(t *tes
|
|||||||
t.Errorf("Condition '%s' should have been a failure", condition)
|
t.Errorf("Condition '%s' should have been a failure", condition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCondition_evaluateWithBodySliceLength(t *testing.T) {
|
||||||
|
condition := Condition("len([BODY].data) == 3")
|
||||||
|
result := &Result{Body: []byte("{\"data\": [{\"id\": 1}, {\"id\": 2}, {\"id\": 3}]}")}
|
||||||
|
condition.evaluate(result)
|
||||||
|
if !result.ConditionResults[0].Success {
|
||||||
|
t.Errorf("Condition '%s' should have been a success", condition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCondition_evaluateWithBodyStringLength(t *testing.T) {
|
||||||
|
condition := Condition("len([BODY].name) == 8")
|
||||||
|
result := &Result{Body: []byte("{\"name\": \"john.doe\"}")}
|
||||||
|
condition.evaluate(result)
|
||||||
|
if !result.ConditionResults[0].Success {
|
||||||
|
t.Errorf("Condition '%s' should have been a success", condition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package core
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/TwinProduction/gatus/client"
|
"github.com/TwinProduction/gatus/client"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@@ -21,6 +22,7 @@ type Service struct {
|
|||||||
Url string `yaml:"url"`
|
Url string `yaml:"url"`
|
||||||
Method string `yaml:"method,omitempty"`
|
Method string `yaml:"method,omitempty"`
|
||||||
Body string `yaml:"body,omitempty"`
|
Body string `yaml:"body,omitempty"`
|
||||||
|
GraphQL bool `yaml:"graphql,omitempty"`
|
||||||
Headers map[string]string `yaml:"headers,omitempty"`
|
Headers map[string]string `yaml:"headers,omitempty"`
|
||||||
Interval time.Duration `yaml:"interval,omitempty"`
|
Interval time.Duration `yaml:"interval,omitempty"`
|
||||||
Conditions []*Condition `yaml:"conditions"`
|
Conditions []*Condition `yaml:"conditions"`
|
||||||
@@ -102,7 +104,17 @@ func (service *Service) call(result *Result) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (service *Service) buildRequest() *http.Request {
|
func (service *Service) buildRequest() *http.Request {
|
||||||
request, _ := http.NewRequest(service.Method, service.Url, bytes.NewBuffer([]byte(service.Body)))
|
var bodyBuffer *bytes.Buffer
|
||||||
|
if service.GraphQL {
|
||||||
|
graphQlBody := map[string]string{
|
||||||
|
"query": service.Body,
|
||||||
|
}
|
||||||
|
body, _ := json.Marshal(graphQlBody)
|
||||||
|
bodyBuffer = bytes.NewBuffer(body)
|
||||||
|
} else {
|
||||||
|
bodyBuffer = bytes.NewBuffer([]byte(service.Body))
|
||||||
|
}
|
||||||
|
request, _ := http.NewRequest(service.Method, service.Url, bodyBuffer)
|
||||||
for k, v := range service.Headers {
|
for k, v := range service.Headers {
|
||||||
request.Header.Set(k, v)
|
request.Header.Set(k, v)
|
||||||
}
|
}
|
||||||
|
|||||||
18
core/util.go
18
core/util.go
@@ -18,6 +18,7 @@ const (
|
|||||||
|
|
||||||
func sanitizeAndResolve(list []string, result *Result) []string {
|
func sanitizeAndResolve(list []string, result *Result) []string {
|
||||||
var sanitizedList []string
|
var sanitizedList []string
|
||||||
|
body := strings.TrimSpace(string(result.Body))
|
||||||
for _, element := range list {
|
for _, element := range list {
|
||||||
element = strings.TrimSpace(element)
|
element = strings.TrimSpace(element)
|
||||||
switch strings.ToUpper(element) {
|
switch strings.ToUpper(element) {
|
||||||
@@ -28,16 +29,25 @@ func sanitizeAndResolve(list []string, result *Result) []string {
|
|||||||
case ResponseTimePlaceHolder:
|
case ResponseTimePlaceHolder:
|
||||||
element = strconv.Itoa(int(result.Duration.Milliseconds()))
|
element = strconv.Itoa(int(result.Duration.Milliseconds()))
|
||||||
case BodyPlaceHolder:
|
case BodyPlaceHolder:
|
||||||
element = string(result.Body)
|
element = body
|
||||||
default:
|
default:
|
||||||
// if starts with BodyPlaceHolder, then evaluate json path
|
// if starts with BodyPlaceHolder, then evaluate json path
|
||||||
if strings.HasPrefix(element, BodyPlaceHolder) {
|
if strings.Contains(element, BodyPlaceHolder) {
|
||||||
resolvedElement, err := jsonpath.Eval(strings.Replace(element, fmt.Sprintf("%s.", BodyPlaceHolder), "", 1), result.Body)
|
wantLength := false
|
||||||
|
if strings.HasPrefix(element, "len(") && strings.HasSuffix(element, ")") {
|
||||||
|
wantLength = true
|
||||||
|
element = strings.TrimSuffix(strings.TrimPrefix(element, "len("), ")")
|
||||||
|
}
|
||||||
|
resolvedElement, resolvedElementLength, err := jsonpath.Eval(strings.Replace(element, fmt.Sprintf("%s.", BodyPlaceHolder), "", 1), result.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.Errors = append(result.Errors, err.Error())
|
result.Errors = append(result.Errors, err.Error())
|
||||||
element = fmt.Sprintf("%s %s", element, InvalidConditionElementSuffix)
|
element = fmt.Sprintf("%s %s", element, InvalidConditionElementSuffix)
|
||||||
} else {
|
} else {
|
||||||
element = resolvedElement
|
if wantLength {
|
||||||
|
element = fmt.Sprintf("%d", resolvedElementLength)
|
||||||
|
} else {
|
||||||
|
element = resolvedElement
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,26 +8,30 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Eval is a half-baked json path implementation that needs some love
|
// Eval is a half-baked json path implementation that needs some love
|
||||||
func Eval(path string, b []byte) (string, error) {
|
func Eval(path string, b []byte) (string, int, error) {
|
||||||
var object interface{}
|
var object interface{}
|
||||||
err := json.Unmarshal(b, &object)
|
err := json.Unmarshal(b, &object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Try to unmarshal it into an array instead
|
// Try to unmarshal it into an array instead
|
||||||
return "", err
|
return "", 0, err
|
||||||
}
|
}
|
||||||
return walk(path, object)
|
return walk(path, object)
|
||||||
}
|
}
|
||||||
|
|
||||||
func walk(path string, object interface{}) (string, error) {
|
func walk(path string, object interface{}) (string, int, error) {
|
||||||
keys := strings.Split(path, ".")
|
keys := strings.Split(path, ".")
|
||||||
currentKey := keys[0]
|
currentKey := keys[0]
|
||||||
switch value := extractValue(currentKey, object).(type) {
|
switch value := extractValue(currentKey, object).(type) {
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
return walk(strings.Replace(path, fmt.Sprintf("%s.", currentKey), "", 1), value)
|
return walk(strings.Replace(path, fmt.Sprintf("%s.", currentKey), "", 1), value)
|
||||||
|
case string:
|
||||||
|
return value, len(value), nil
|
||||||
|
case []interface{}:
|
||||||
|
return fmt.Sprintf("%v", value), len(value), nil
|
||||||
case interface{}:
|
case interface{}:
|
||||||
return fmt.Sprintf("%v", value), nil
|
return fmt.Sprintf("%v", value), 1, nil
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("couldn't walk through '%s' because type was '%T', but expected 'map[string]interface{}'", currentKey, value)
|
return "", 0, fmt.Errorf("couldn't walk through '%s' because type was '%T', but expected 'map[string]interface{}'", currentKey, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +56,9 @@ func extractValue(currentKey string, value interface{}) interface{} {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if value == nil || value.(map[string]interface{})[currentKey] == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
// if currentKey contains both a key and an index (i.e. data[0])
|
// if currentKey contains both a key and an index (i.e. data[0])
|
||||||
array := value.(map[string]interface{})[currentKey].([]interface{})
|
array := value.(map[string]interface{})[currentKey].([]interface{})
|
||||||
if len(array) > arrayIndex {
|
if len(array) > arrayIndex {
|
||||||
|
|||||||
@@ -8,10 +8,13 @@ func TestEval(t *testing.T) {
|
|||||||
|
|
||||||
expectedOutput := "value"
|
expectedOutput := "value"
|
||||||
|
|
||||||
output, err := Eval(path, []byte(data))
|
output, outputLength, err := Eval(path, []byte(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("Didn't expect any error, but got", err)
|
t.Error("Didn't expect any error, but got", err)
|
||||||
}
|
}
|
||||||
|
if outputLength != len(expectedOutput) {
|
||||||
|
t.Errorf("Expected output length to be %v, but was %v", len(expectedOutput), outputLength)
|
||||||
|
}
|
||||||
if output != expectedOutput {
|
if output != expectedOutput {
|
||||||
t.Errorf("Expected output to be %v, but was %v", expectedOutput, output)
|
t.Errorf("Expected output to be %v, but was %v", expectedOutput, output)
|
||||||
}
|
}
|
||||||
@@ -23,7 +26,7 @@ func TestEvalWithLongSimpleWalk(t *testing.T) {
|
|||||||
|
|
||||||
expectedOutput := "value"
|
expectedOutput := "value"
|
||||||
|
|
||||||
output, err := Eval(path, []byte(data))
|
output, _, err := Eval(path, []byte(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("Didn't expect any error, but got", err)
|
t.Error("Didn't expect any error, but got", err)
|
||||||
}
|
}
|
||||||
@@ -38,10 +41,11 @@ func TestEvalWithArrayOfMaps(t *testing.T) {
|
|||||||
|
|
||||||
expectedOutput := "2"
|
expectedOutput := "2"
|
||||||
|
|
||||||
output, err := Eval(path, []byte(data))
|
output, _, err := Eval(path, []byte(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("Didn't expect any error, but got", err)
|
t.Error("Didn't expect any error, but got", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if output != expectedOutput {
|
if output != expectedOutput {
|
||||||
t.Errorf("Expected output to be %v, but was %v", expectedOutput, output)
|
t.Errorf("Expected output to be %v, but was %v", expectedOutput, output)
|
||||||
}
|
}
|
||||||
@@ -53,7 +57,7 @@ func TestEvalWithArrayOfValues(t *testing.T) {
|
|||||||
|
|
||||||
expectedOutput := "1"
|
expectedOutput := "1"
|
||||||
|
|
||||||
output, err := Eval(path, []byte(data))
|
output, _, err := Eval(path, []byte(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("Didn't expect any error, but got", err)
|
t.Error("Didn't expect any error, but got", err)
|
||||||
}
|
}
|
||||||
@@ -68,7 +72,7 @@ func TestEvalWithRootArrayOfValues(t *testing.T) {
|
|||||||
|
|
||||||
expectedOutput := "2"
|
expectedOutput := "2"
|
||||||
|
|
||||||
output, err := Eval(path, []byte(data))
|
output, _, err := Eval(path, []byte(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("Didn't expect any error, but got", err)
|
t.Error("Didn't expect any error, but got", err)
|
||||||
}
|
}
|
||||||
@@ -83,7 +87,7 @@ func TestEvalWithRootArrayOfMaps(t *testing.T) {
|
|||||||
|
|
||||||
expectedOutput := "1"
|
expectedOutput := "1"
|
||||||
|
|
||||||
output, err := Eval(path, []byte(data))
|
output, _, err := Eval(path, []byte(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("Didn't expect any error, but got", err)
|
t.Error("Didn't expect any error, but got", err)
|
||||||
}
|
}
|
||||||
@@ -96,7 +100,7 @@ func TestEvalWithRootArrayOfMapsUsingInvalidArrayIndex(t *testing.T) {
|
|||||||
path := "[5].id"
|
path := "[5].id"
|
||||||
data := `[{"id": 1}, {"id": 2}]`
|
data := `[{"id": 1}, {"id": 2}]`
|
||||||
|
|
||||||
_, err := Eval(path, []byte(data))
|
_, _, err := Eval(path, []byte(data))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Should've returned an error, but didn't")
|
t.Error("Should've returned an error, but didn't")
|
||||||
}
|
}
|
||||||
@@ -108,7 +112,7 @@ func TestEvalWithLongWalkAndArray(t *testing.T) {
|
|||||||
|
|
||||||
expectedOutput := "1"
|
expectedOutput := "1"
|
||||||
|
|
||||||
output, err := Eval(path, []byte(data))
|
output, _, err := Eval(path, []byte(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("Didn't expect any error, but got", err)
|
t.Error("Didn't expect any error, but got", err)
|
||||||
}
|
}
|
||||||
@@ -123,7 +127,7 @@ func TestEvalWithNestedArray(t *testing.T) {
|
|||||||
|
|
||||||
expectedOutput := "7"
|
expectedOutput := "7"
|
||||||
|
|
||||||
output, err := Eval(path, []byte(data))
|
output, _, err := Eval(path, []byte(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("Didn't expect any error, but got", err)
|
t.Error("Didn't expect any error, but got", err)
|
||||||
}
|
}
|
||||||
@@ -138,7 +142,7 @@ func TestEvalWithMapOfNestedArray(t *testing.T) {
|
|||||||
|
|
||||||
expectedOutput := "e"
|
expectedOutput := "e"
|
||||||
|
|
||||||
output, err := Eval(path, []byte(data))
|
output, _, err := Eval(path, []byte(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("Didn't expect any error, but got", err)
|
t.Error("Didn't expect any error, but got", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,6 @@
|
|||||||
html {
|
html {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
body {
|
|
||||||
min-height: 100%;
|
|
||||||
}
|
|
||||||
#results div.container:first-child {
|
#results div.container:first-child {
|
||||||
border-top-left-radius: 3px;
|
border-top-left-radius: 3px;
|
||||||
border-top-right-radius: 3px;
|
border-top-right-radius: 3px;
|
||||||
@@ -150,7 +147,7 @@
|
|||||||
if (!userClickedStatus) {
|
if (!userClickedStatus) {
|
||||||
timerHandler = setTimeout(function () {
|
timerHandler = setTimeout(function () {
|
||||||
$("#tooltip").hide();
|
$("#tooltip").hide();
|
||||||
}, 2000);
|
}, 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,10 +190,10 @@
|
|||||||
output += ""
|
output += ""
|
||||||
+ "<div class='container py-3 border-left border-right border-top border-black'>"
|
+ "<div class='container py-3 border-left border-right border-top border-black'>"
|
||||||
+ " <div class='row mb-2'>"
|
+ " <div class='row mb-2'>"
|
||||||
+ " <div class='col-8'>"
|
+ " <div class='col-10'>"
|
||||||
+ " <span class='font-weight-bold'>" + serviceName + "</span> <span class='text-secondary font-weight-lighter'>- " + hostname + "</span>"
|
+ " <span class='font-weight-bold'>" + serviceName + "</span> <span class='text-secondary font-weight-lighter'>- " + hostname + "</span>"
|
||||||
+ " </div>"
|
+ " </div>"
|
||||||
+ " <div class='col-4 text-right'>"
|
+ " <div class='col-2 text-right'>"
|
||||||
+ " <span class='font-weight-lighter'>" + (minResponseTime === maxResponseTime ? minResponseTime : (minResponseTime + "-" + maxResponseTime)) + "ms</span>"
|
+ " <span class='font-weight-lighter'>" + (minResponseTime === maxResponseTime ? minResponseTime : (minResponseTime + "-" + maxResponseTime)) + "ms</span>"
|
||||||
+ " </div>"
|
+ " </div>"
|
||||||
+ " </div>"
|
+ " </div>"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package watchdog
|
package watchdog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/TwinProduction/gatus/config"
|
"github.com/TwinProduction/gatus/config"
|
||||||
"github.com/TwinProduction/gatus/core"
|
"github.com/TwinProduction/gatus/core"
|
||||||
"github.com/TwinProduction/gatus/metric"
|
"github.com/TwinProduction/gatus/metric"
|
||||||
@@ -39,11 +40,16 @@ func monitor(service *core.Service) {
|
|||||||
serviceResults[service.Name] = serviceResults[service.Name][1:]
|
serviceResults[service.Name] = serviceResults[service.Name][1:]
|
||||||
}
|
}
|
||||||
rwLock.Unlock()
|
rwLock.Unlock()
|
||||||
|
var extra string
|
||||||
|
if !result.Success {
|
||||||
|
extra = fmt.Sprintf("responseBody=%s", result.Body)
|
||||||
|
}
|
||||||
log.Printf(
|
log.Printf(
|
||||||
"[watchdog][Monitor] Finished monitoring serviceName=%s; errors=%d; requestDuration=%s",
|
"[watchdog][Monitor] Finished monitoring serviceName=%s; errors=%d; requestDuration=%s; %s",
|
||||||
service.Name,
|
service.Name,
|
||||||
len(result.Errors),
|
len(result.Errors),
|
||||||
result.Duration.Round(time.Millisecond),
|
result.Duration.Round(time.Millisecond),
|
||||||
|
extra,
|
||||||
)
|
)
|
||||||
log.Printf("[watchdog][Monitor] Waiting interval=%s before monitoring serviceName=%s", service.Interval, service.Name)
|
log.Printf("[watchdog][Monitor] Waiting interval=%s before monitoring serviceName=%s", service.Interval, service.Name)
|
||||||
time.Sleep(service.Interval)
|
time.Sleep(service.Interval)
|
||||||
|
|||||||
Reference in New Issue
Block a user