Add result related tags and fields to http_response (#3814)

This commit is contained in:
Germán Jaber 2018-03-08 16:55:59 -06:00 committed by Daniel Nelson
parent 8005883de8
commit 81a93fcddf
3 changed files with 496 additions and 111 deletions

View File

@ -44,18 +44,38 @@ This input plugin will test HTTP/HTTPS connections.
### Measurements & Fields:
- http_response
- response_time (float, seconds)
- http_response_code (int) #The code received
- result_type (string) # success, timeout, response_string_mismatch, connection_failed
- response_time (float, seconds) # Not set if target is unreachable for any reason
- http_response_code (int) # The HTTP code received
- result_type (string) # Legacy field mantained for backwards compatibility
- result_code (int) # Details [here](#result-tag-and-result_code-field)
### Tags:
- All measurements have the following tags:
- server
- method
- server # Server URL used
- method # HTTP method used (GET, POST, PUT, etc)
- status_code # String with the HTTP status code
- result # Details [here](#result-tag-and-result_code-field)
### Result tag and Result_code field
Upon finishing polling the target server, the plugin registers the result of the operation in the `result` tag, and adds a numeric field called `result_code` corresponding with that tag value.
This tag is used to expose network and plugin errors. HTTP errors are considered a sucessful connection by the plugin.
|Tag value |Corresponding field value|Description|
--------------------------|-------------------------|-----------|
|success | 0 |The HTTP request completed, even if the HTTP code represents an error|
|response_string_mismatch | 1 |The option `response_string_match` was used, and the body of the response didn't match the regex|
|body_read_error | 2 |The option `response_string_match` was used, but the plugin wans't able to read the body of the response. Responses with empty bodies (like 3xx, HEAD, etc) will trigger this error|
|connection_failed | 3 |Catch all for any network error not specifically handled by the plugin|
|timeout | 4 |The plugin timed out while awaiting the HTTP connection to complete|
|dns_error | 5 |There was a DNS error while attempting to connect to the host|
NOTE: The error codes are derived from the error object returned by the `net/http` Go library, so the accuracy of the errors depends on the handling of error states by the `net/http` Go library. **If a more detailed error report is required use the `log_network_errors` setting.**
### Example Output:
```
http_response,method=GET,server=http://www.github.com http_response_code=200i,response_time=6.223266528 1459419354977857955
http_response,method=GET,server=http://www.github.com,status_code="200",result="success" http_response_code=200i,response_time=6.223266528,result_type="sucess",result_code=0i 1459419354977857955
```

View File

@ -2,6 +2,7 @@ package http_response
import (
"errors"
"fmt"
"io"
"io/ioutil"
"log"
@ -9,6 +10,7 @@ import (
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"time"
@ -133,10 +135,54 @@ func (h *HTTPResponse) createHttpClient() (*http.Client, error) {
return client, nil
}
func setResult(result_string string, fields map[string]interface{}, tags map[string]string) {
result_codes := map[string]int{
"success": 0,
"response_string_mismatch": 1,
"body_read_error": 2,
"connection_failed": 3,
"timeout": 4,
"dns_error": 5,
}
tags["result"] = result_string
fields["result_type"] = result_string
fields["result_code"] = result_codes[result_string]
}
func setError(err error, fields map[string]interface{}, tags map[string]string) error {
if timeoutError, ok := err.(net.Error); ok && timeoutError.Timeout() {
setResult("timeout", fields, tags)
return timeoutError
}
urlErr, isUrlErr := err.(*url.Error)
if !isUrlErr {
return nil
}
opErr, isNetErr := (urlErr.Err).(*net.OpError)
if isNetErr {
switch e := (opErr.Err).(type) {
case (*net.DNSError):
setResult("dns_error", fields, tags)
return e
case (*net.ParseError):
// Parse error has to do with parsing of IP addresses, so we
// group it with address errors
setResult("address_error", fields, tags)
return e
}
}
return nil
}
// HTTPGather gathers all fields and returns any errors it encounters
func (h *HTTPResponse) httpGather() (map[string]interface{}, error) {
// Prepare fields
func (h *HTTPResponse) httpGather() (map[string]interface{}, map[string]string, error) {
// Prepare fields and tags
fields := make(map[string]interface{})
tags := map[string]string{"server": h.Address, "method": h.Method}
var body io.Reader
if h.Body != "" {
@ -144,7 +190,7 @@ func (h *HTTPResponse) httpGather() (map[string]interface{}, error) {
}
request, err := http.NewRequest(h.Method, h.Address, body)
if err != nil {
return nil, err
return nil, nil, err
}
for key, val := range h.Headers {
@ -157,68 +203,87 @@ func (h *HTTPResponse) httpGather() (map[string]interface{}, error) {
// Start Timer
start := time.Now()
resp, err := h.client.Do(request)
response_time := time.Since(start).Seconds()
// If an error in returned, it means we are dealing with a network error, as
// HTTP error codes do not generate errors in the net/http library
if err != nil {
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
fields["result_type"] = "timeout"
return fields, nil
// Log error
log.Printf("D! Network error while polling %s: %s", h.Address, err.Error())
// Get error details
netErr := setError(err, fields, tags)
// If recognize the returnded error, get out
if netErr != nil {
return fields, tags, nil
}
fields["result_type"] = "connection_failed"
if h.FollowRedirects {
return fields, nil
}
if urlError, ok := err.(*url.Error); ok &&
urlError.Err == ErrRedirectAttempted {
// Any error not recognized by `set_error` is considered a "connection_failed"
setResult("connection_failed", fields, tags)
// If the error is a redirect we continue processing and log the HTTP code
urlError, isUrlError := err.(*url.Error)
if !h.FollowRedirects && isUrlError && urlError.Err == ErrRedirectAttempted {
err = nil
} else {
return fields, nil
// If the error isn't a timeout or a redirect stop
// processing the request
return fields, tags, nil
}
}
if _, ok := fields["response_time"]; !ok {
fields["response_time"] = response_time
}
// This function closes the response body, as
// required by the net/http library
defer func() {
io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
}()
fields["response_time"] = time.Since(start).Seconds()
// Set log the HTTP response code
tags["status_code"] = strconv.Itoa(resp.StatusCode)
fields["http_response_code"] = resp.StatusCode
// Check the response for a regex match.
if h.ResponseStringMatch != "" {
// Compile once and reuse
if h.compiledStringMatch == nil {
h.compiledStringMatch = regexp.MustCompile(h.ResponseStringMatch)
if err != nil {
log.Printf("E! Failed to compile regular expression %s : %s", h.ResponseStringMatch, err)
fields["result_type"] = "response_string_mismatch"
return fields, nil
}
}
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Printf("E! Failed to read body of HTTP Response : %s", err)
fields["result_type"] = "response_string_mismatch"
log.Printf("D! Failed to read body of HTTP Response : %s", err)
setResult("body_read_error", fields, tags)
fields["response_string_match"] = 0
return fields, nil
return fields, tags, nil
}
if h.compiledStringMatch.Match(bodyBytes) {
fields["result_type"] = "success"
setResult("success", fields, tags)
fields["response_string_match"] = 1
} else {
fields["result_type"] = "response_string_mismatch"
setResult("response_string_mismatch", fields, tags)
fields["response_string_match"] = 0
}
} else {
fields["result_type"] = "success"
setResult("success", fields, tags)
}
return fields, nil
return fields, tags, nil
}
// Gather gets all metric fields and tags and returns any errors it encounters
func (h *HTTPResponse) Gather(acc telegraf.Accumulator) error {
// Compile the body regex if it exist
if h.compiledStringMatch == nil {
var err error
h.compiledStringMatch, err = regexp.Compile(h.ResponseStringMatch)
if err != nil {
return fmt.Errorf("Failed to compile regular expression %s : %s", h.ResponseStringMatch, err)
}
}
// Set default values
if h.ResponseTimeout.Duration < time.Second {
h.ResponseTimeout.Duration = time.Second * 5
@ -237,9 +302,10 @@ func (h *HTTPResponse) Gather(acc telegraf.Accumulator) error {
if addr.Scheme != "http" && addr.Scheme != "https" {
return errors.New("Only http and https are supported")
}
// Prepare data
tags := map[string]string{"server": h.Address, "method": h.Method}
var fields map[string]interface{}
var tags map[string]string
if h.client == nil {
client, err := h.createHttpClient()
@ -250,10 +316,11 @@ func (h *HTTPResponse) Gather(acc telegraf.Accumulator) error {
}
// Gather data
fields, err = h.httpGather()
fields, tags, err = h.httpGather()
if err != nil {
return err
}
// Add metrics
acc.AddFields("http_response", fields, tags)
return nil

View File

@ -15,6 +15,68 @@ import (
"github.com/stretchr/testify/require"
)
// Receives a list with fields that are expected to be absent
func checkAbsentFields(t *testing.T, fields []string, acc testutil.Accumulator) {
for _, field := range fields {
ok := acc.HasField("http_response", field)
require.False(t, ok)
}
}
// Receives a list with tags that are expected to be absent
func checkAbsentTags(t *testing.T, tags []string, acc testutil.Accumulator) {
for _, tag := range tags {
ok := acc.HasTag("http_response", tag)
require.False(t, ok)
}
}
// Receives a dictionary and with expected fields and their values. If a value is nil, it will only check
// that the field exists, but not its contents
func checkFields(t *testing.T, fields map[string]interface{}, acc testutil.Accumulator) {
for key, field := range fields {
switch v := field.(type) {
case int:
value, ok := acc.IntField("http_response", key)
require.True(t, ok)
require.Equal(t, field, value)
case float64:
value, ok := acc.FloatField("http_response", key)
require.True(t, ok)
require.Equal(t, field, value)
case string:
value, ok := acc.StringField("http_response", key)
require.True(t, ok)
require.Equal(t, field, value)
case nil:
ok := acc.HasField("http_response", key)
require.True(t, ok)
default:
t.Log("Unsupported type for field: ", v)
t.Fail()
}
}
}
// Receives a dictionary and with expected tags and their values. If a value is nil, it will only check
// that the tag exists, but not its contents
func checkTags(t *testing.T, tags map[string]interface{}, acc testutil.Accumulator) {
for key, tag := range tags {
switch v := tag.(type) {
case string:
ok := acc.HasTag("http_response", key)
require.True(t, ok)
require.Equal(t, tag, acc.TagValue("http_response", key))
case nil:
ok := acc.HasTag("http_response", key)
require.True(t, ok)
default:
t.Log("Unsupported type for tag: ", v)
t.Fail()
}
}
}
func setUpTestMux() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/redirect", func(w http.ResponseWriter, req *http.Request) {
@ -56,6 +118,24 @@ func setUpTestMux() http.Handler {
return mux
}
func checkOutput(t *testing.T, acc testutil.Accumulator, presentFields map[string]interface{}, presentTags map[string]interface{}, absentFields []string, absentTags []string) {
if presentFields != nil {
checkFields(t, presentFields, acc)
}
if presentTags != nil {
checkTags(t, presentTags, acc)
}
if absentFields != nil {
checkAbsentFields(t, absentFields, acc)
}
if absentTags != nil {
checkAbsentTags(t, absentTags, acc)
}
}
func TestHeaders(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cHeader := r.Header.Get("Content-Type")
@ -78,9 +158,20 @@ func TestHeaders(t *testing.T) {
err := h.Gather(&acc)
require.NoError(t, err)
value, ok := acc.IntField("http_response", "http_response_code")
require.True(t, ok)
require.Equal(t, http.StatusOK, value)
expectedFields := map[string]interface{}{
"http_response_code": http.StatusOK,
"result_type": "success",
"result_code": 0,
"response_time": nil,
}
expectedTags := map[string]interface{}{
"server": nil,
"method": "GET",
"status_code": "200",
"result": "success",
}
absentFields := []string{"response_string_match"}
checkOutput(t, acc, expectedFields, expectedTags, absentFields, nil)
}
func TestFields(t *testing.T) {
@ -103,12 +194,20 @@ func TestFields(t *testing.T) {
err := h.Gather(&acc)
require.NoError(t, err)
value, ok := acc.IntField("http_response", "http_response_code")
require.True(t, ok)
require.Equal(t, http.StatusOK, value)
response_value, ok := acc.StringField("http_response", "result_type")
require.True(t, ok)
require.Equal(t, "success", response_value)
expectedFields := map[string]interface{}{
"http_response_code": http.StatusOK,
"result_type": "success",
"result_code": 0,
"response_time": nil,
}
expectedTags := map[string]interface{}{
"server": nil,
"method": "GET",
"status_code": "200",
"result": "success",
}
absentFields := []string{"response_string_match"}
checkOutput(t, acc, expectedFields, expectedTags, absentFields, nil)
}
func TestRedirects(t *testing.T) {
@ -130,9 +229,20 @@ func TestRedirects(t *testing.T) {
err := h.Gather(&acc)
require.NoError(t, err)
value, ok := acc.IntField("http_response", "http_response_code")
require.True(t, ok)
require.Equal(t, http.StatusOK, value)
expectedFields := map[string]interface{}{
"http_response_code": http.StatusOK,
"result_type": "success",
"result_code": 0,
"response_time": nil,
}
expectedTags := map[string]interface{}{
"server": nil,
"method": "GET",
"status_code": "200",
"result": "success",
}
absentFields := []string{"response_string_match"}
checkOutput(t, acc, expectedFields, expectedTags, absentFields, nil)
h = &HTTPResponse{
Address: ts.URL + "/badredirect",
@ -148,11 +258,21 @@ func TestRedirects(t *testing.T) {
err = h.Gather(&acc)
require.NoError(t, err)
value, ok = acc.IntField("http_response", "http_response_code")
require.False(t, ok)
response_value, ok := acc.StringField("http_response", "result_type")
require.True(t, ok)
require.Equal(t, "connection_failed", response_value)
expectedFields = map[string]interface{}{
"result_type": "connection_failed",
"result_code": 3,
}
expectedTags = map[string]interface{}{
"server": nil,
"method": "GET",
"result": "connection_failed",
}
absentFields = []string{"http_response_code", "response_time", "response_string_match"}
absentTags := []string{"status_code"}
checkOutput(t, acc, expectedFields, expectedTags, nil, nil)
expectedFields = map[string]interface{}{"result_type": "connection_failed"}
checkOutput(t, acc, expectedFields, expectedTags, absentFields, absentTags)
}
func TestMethod(t *testing.T) {
@ -174,9 +294,20 @@ func TestMethod(t *testing.T) {
err := h.Gather(&acc)
require.NoError(t, err)
value, ok := acc.IntField("http_response", "http_response_code")
require.True(t, ok)
require.Equal(t, http.StatusOK, value)
expectedFields := map[string]interface{}{
"http_response_code": http.StatusOK,
"result_type": "success",
"result_code": 0,
"response_time": nil,
}
expectedTags := map[string]interface{}{
"server": nil,
"method": "POST",
"status_code": "200",
"result": "success",
}
absentFields := []string{"response_string_match"}
checkOutput(t, acc, expectedFields, expectedTags, absentFields, nil)
h = &HTTPResponse{
Address: ts.URL + "/mustbepostmethod",
@ -192,9 +323,20 @@ func TestMethod(t *testing.T) {
err = h.Gather(&acc)
require.NoError(t, err)
value, ok = acc.IntField("http_response", "http_response_code")
require.True(t, ok)
require.Equal(t, http.StatusMethodNotAllowed, value)
expectedFields = map[string]interface{}{
"http_response_code": http.StatusMethodNotAllowed,
"result_type": "success",
"result_code": 0,
"response_time": nil,
}
expectedTags = map[string]interface{}{
"server": nil,
"method": "GET",
"status_code": "405",
"result": "success",
}
absentFields = []string{"response_string_match"}
checkOutput(t, acc, expectedFields, expectedTags, absentFields, nil)
//check that lowercase methods work correctly
h = &HTTPResponse{
@ -211,9 +353,20 @@ func TestMethod(t *testing.T) {
err = h.Gather(&acc)
require.NoError(t, err)
value, ok = acc.IntField("http_response", "http_response_code")
require.True(t, ok)
require.Equal(t, http.StatusMethodNotAllowed, value)
expectedFields = map[string]interface{}{
"http_response_code": http.StatusMethodNotAllowed,
"result_type": "success",
"result_code": 0,
"response_time": nil,
}
expectedTags = map[string]interface{}{
"server": nil,
"method": "head",
"status_code": "405",
"result": "success",
}
absentFields = []string{"response_string_match"}
checkOutput(t, acc, expectedFields, expectedTags, absentFields, nil)
}
func TestBody(t *testing.T) {
@ -235,9 +388,20 @@ func TestBody(t *testing.T) {
err := h.Gather(&acc)
require.NoError(t, err)
value, ok := acc.IntField("http_response", "http_response_code")
require.True(t, ok)
require.Equal(t, http.StatusOK, value)
expectedFields := map[string]interface{}{
"http_response_code": http.StatusOK,
"result_type": "success",
"result_code": 0,
"response_time": nil,
}
expectedTags := map[string]interface{}{
"server": nil,
"method": "GET",
"status_code": "200",
"result": "success",
}
absentFields := []string{"response_string_match"}
checkOutput(t, acc, expectedFields, expectedTags, absentFields, nil)
h = &HTTPResponse{
Address: ts.URL + "/musthaveabody",
@ -252,9 +416,19 @@ func TestBody(t *testing.T) {
err = h.Gather(&acc)
require.NoError(t, err)
value, ok = acc.IntField("http_response", "http_response_code")
require.True(t, ok)
require.Equal(t, http.StatusBadRequest, value)
expectedFields = map[string]interface{}{
"http_response_code": http.StatusBadRequest,
"result_type": "success",
"result_code": 0,
}
expectedTags = map[string]interface{}{
"server": nil,
"method": "GET",
"status_code": "400",
"result": "success",
}
absentFields = []string{"response_string_match"}
checkOutput(t, acc, expectedFields, expectedTags, absentFields, nil)
}
func TestStringMatch(t *testing.T) {
@ -277,17 +451,20 @@ func TestStringMatch(t *testing.T) {
err := h.Gather(&acc)
require.NoError(t, err)
value, ok := acc.IntField("http_response", "http_response_code")
require.True(t, ok)
require.Equal(t, http.StatusOK, value)
value, ok = acc.IntField("http_response", "response_string_match")
require.True(t, ok)
require.Equal(t, 1, value)
response_value, ok := acc.StringField("http_response", "result_type")
require.True(t, ok)
require.Equal(t, "success", response_value)
_, ok = acc.FloatField("http_response", "response_time")
require.True(t, ok)
expectedFields := map[string]interface{}{
"http_response_code": http.StatusOK,
"response_string_match": 1,
"result_type": "success",
"result_code": 0,
"response_time": nil,
}
expectedTags := map[string]interface{}{
"server": nil,
"method": "GET",
"status_code": "200",
"result": "success",
}
checkOutput(t, acc, expectedFields, expectedTags, nil, nil)
}
func TestStringMatchJson(t *testing.T) {
@ -310,17 +487,20 @@ func TestStringMatchJson(t *testing.T) {
err := h.Gather(&acc)
require.NoError(t, err)
value, ok := acc.IntField("http_response", "http_response_code")
require.True(t, ok)
require.Equal(t, http.StatusOK, value)
value, ok = acc.IntField("http_response", "response_string_match")
require.True(t, ok)
require.Equal(t, 1, value)
response_value, ok := acc.StringField("http_response", "result_type")
require.True(t, ok)
require.Equal(t, "success", response_value)
_, ok = acc.FloatField("http_response", "response_time")
require.True(t, ok)
expectedFields := map[string]interface{}{
"http_response_code": http.StatusOK,
"response_string_match": 1,
"result_type": "success",
"result_code": 0,
"response_time": nil,
}
expectedTags := map[string]interface{}{
"server": nil,
"method": "GET",
"status_code": "200",
"result": "success",
}
checkOutput(t, acc, expectedFields, expectedTags, nil, nil)
}
func TestStringMatchFail(t *testing.T) {
@ -344,17 +524,20 @@ func TestStringMatchFail(t *testing.T) {
err := h.Gather(&acc)
require.NoError(t, err)
value, ok := acc.IntField("http_response", "http_response_code")
require.True(t, ok)
require.Equal(t, http.StatusOK, value)
value, ok = acc.IntField("http_response", "response_string_match")
require.True(t, ok)
require.Equal(t, 0, value)
response_value, ok := acc.StringField("http_response", "result_type")
require.True(t, ok)
require.Equal(t, "response_string_mismatch", response_value)
_, ok = acc.FloatField("http_response", "response_time")
require.True(t, ok)
expectedFields := map[string]interface{}{
"http_response_code": http.StatusOK,
"response_string_match": 0,
"result_type": "response_string_mismatch",
"result_code": 1,
"response_time": nil,
}
expectedTags := map[string]interface{}{
"server": nil,
"method": "GET",
"status_code": "200",
"result": "response_string_mismatch",
}
checkOutput(t, acc, expectedFields, expectedTags, nil, nil)
}
func TestTimeout(t *testing.T) {
@ -380,11 +563,126 @@ func TestTimeout(t *testing.T) {
err := h.Gather(&acc)
require.NoError(t, err)
_, ok := acc.IntField("http_response", "http_response_code")
require.False(t, ok)
response_value, ok := acc.StringField("http_response", "result_type")
require.True(t, ok)
require.Equal(t, "timeout", response_value)
_, ok = acc.FloatField("http_response", "response_time")
require.False(t, ok)
expectedFields := map[string]interface{}{
"result_type": "timeout",
"result_code": 4,
}
expectedTags := map[string]interface{}{
"server": nil,
"method": "GET",
"result": "timeout",
}
absentFields := []string{"http_response_code", "response_time", "response_string_match"}
absentTags := []string{"status_code"}
checkOutput(t, acc, expectedFields, expectedTags, absentFields, absentTags)
}
func TestPluginErrors(t *testing.T) {
mux := setUpTestMux()
ts := httptest.NewServer(mux)
defer ts.Close()
// Bad regex test. Should return an error and return nothing
h := &HTTPResponse{
Address: ts.URL + "/good",
Body: "{ 'test': 'data'}",
Method: "GET",
ResponseStringMatch: "bad regex:[[",
ResponseTimeout: internal.Duration{Duration: time.Second * 20},
Headers: map[string]string{
"Content-Type": "application/json",
},
FollowRedirects: true,
}
var acc testutil.Accumulator
err := h.Gather(&acc)
require.Error(t, err)
absentFields := []string{"http_response_code", "response_time", "response_string_match", "result_type", "result_code"}
absentTags := []string{"status_code", "result", "server", "method"}
checkOutput(t, acc, nil, nil, absentFields, absentTags)
// Attempt to read empty body test
h = &HTTPResponse{
Address: ts.URL + "/redirect",
Body: "",
Method: "GET",
ResponseStringMatch: ".*",
ResponseTimeout: internal.Duration{Duration: time.Second * 20},
FollowRedirects: false,
}
acc = testutil.Accumulator{}
err = h.Gather(&acc)
require.NoError(t, err)
expectedFields := map[string]interface{}{
"http_response_code": http.StatusMovedPermanently,
"response_string_match": 0,
"result_type": "body_read_error",
"result_code": 2,
"response_time": nil,
}
expectedTags := map[string]interface{}{
"server": nil,
"method": "GET",
"status_code": "301",
"result": "body_read_error",
}
checkOutput(t, acc, expectedFields, expectedTags, nil, nil)
}
func TestNetworkErrors(t *testing.T) {
// DNS error
h := &HTTPResponse{
Address: "https://nonexistent.nonexistent", // Any non-resolvable URL works here
Body: "",
Method: "GET",
ResponseTimeout: internal.Duration{Duration: time.Second * 20},
FollowRedirects: false,
}
var acc testutil.Accumulator
err := h.Gather(&acc)
require.NoError(t, err)
expectedFields := map[string]interface{}{
"result_type": "dns_error",
"result_code": 5,
}
expectedTags := map[string]interface{}{
"server": nil,
"method": "GET",
"result": "dns_error",
}
absentFields := []string{"http_response_code", "response_time", "response_string_match"}
absentTags := []string{"status_code"}
checkOutput(t, acc, expectedFields, expectedTags, absentFields, absentTags)
// Connecton failed
h = &HTTPResponse{
Address: "https://127.127.127.127", // Any non-routable IP works here
Body: "",
Method: "GET",
ResponseTimeout: internal.Duration{Duration: time.Second * 20},
FollowRedirects: false,
}
acc = testutil.Accumulator{}
err = h.Gather(&acc)
require.NoError(t, err)
expectedFields = map[string]interface{}{
"result_type": "connection_failed",
"result_code": 3,
}
expectedTags = map[string]interface{}{
"server": nil,
"method": "GET",
"result": "connection_failed",
}
absentFields = []string{"http_response_code", "response_time", "response_string_match"}
absentTags = []string{"status_code"}
checkOutput(t, acc, expectedFields, expectedTags, absentFields, absentTags)
}