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 fe78df3ba0
commit e9da4e529e
3 changed files with 496 additions and 111 deletions

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