http_response : Add in support for looking for substring in response (#2204)
* Add in support for looking for substring in response * Add note to CHANGELOG.md * Switch from substring match to regex match * Requested code changes * Make requested changes and refactor to avoid nested if-else. * Convert tabs to space and compile regex once
This commit is contained in:
parent
07f2e6dc94
commit
c2d86e6649
|
@ -54,6 +54,7 @@ plugins, not just statsd.
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
|
- [#2204](https://github.com/influxdata/telegraf/pull/2204): Extend http_response to support searching for a substring in response. Return 1 if found, else 0.
|
||||||
- [#2123](https://github.com/influxdata/telegraf/pull/2123): Fix improper calculation of CPU percentages
|
- [#2123](https://github.com/influxdata/telegraf/pull/2123): Fix improper calculation of CPU percentages
|
||||||
- [#1564](https://github.com/influxdata/telegraf/issues/1564): Use RFC3339 timestamps in log output.
|
- [#1564](https://github.com/influxdata/telegraf/issues/1564): Use RFC3339 timestamps in log output.
|
||||||
- [#1997](https://github.com/influxdata/telegraf/issues/1997): Non-default HTTP timeouts for RabbitMQ plugin.
|
- [#1997](https://github.com/influxdata/telegraf/issues/1997): Non-default HTTP timeouts for RabbitMQ plugin.
|
||||||
|
|
|
@ -23,6 +23,11 @@ This input plugin will test HTTP/HTTPS connections.
|
||||||
# {'fake':'data'}
|
# {'fake':'data'}
|
||||||
# '''
|
# '''
|
||||||
|
|
||||||
|
## Optional substring or regex match in body of the response
|
||||||
|
## response_string_match = "\"service_status\": \"up\""
|
||||||
|
## response_string_match = "ok"
|
||||||
|
## response_string_match = "\".*_status\".?:.?\"up\""
|
||||||
|
|
||||||
## Optional SSL Config
|
## Optional SSL Config
|
||||||
# ssl_ca = "/etc/telegraf/ca.pem"
|
# ssl_ca = "/etc/telegraf/ca.pem"
|
||||||
# ssl_cert = "/etc/telegraf/cert.pem"
|
# ssl_cert = "/etc/telegraf/cert.pem"
|
||||||
|
|
|
@ -3,8 +3,11 @@ package http_response
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -15,12 +18,14 @@ import (
|
||||||
|
|
||||||
// HTTPResponse struct
|
// HTTPResponse struct
|
||||||
type HTTPResponse struct {
|
type HTTPResponse struct {
|
||||||
Address string
|
Address string
|
||||||
Body string
|
Body string
|
||||||
Method string
|
Method string
|
||||||
ResponseTimeout internal.Duration
|
ResponseTimeout internal.Duration
|
||||||
Headers map[string]string
|
Headers map[string]string
|
||||||
FollowRedirects bool
|
FollowRedirects bool
|
||||||
|
ResponseStringMatch string
|
||||||
|
compiledStringMatch *regexp.Regexp
|
||||||
|
|
||||||
// Path to CA file
|
// Path to CA file
|
||||||
SSLCA string `toml:"ssl_ca"`
|
SSLCA string `toml:"ssl_ca"`
|
||||||
|
@ -54,6 +59,11 @@ var sampleConfig = `
|
||||||
# {'fake':'data'}
|
# {'fake':'data'}
|
||||||
# '''
|
# '''
|
||||||
|
|
||||||
|
## Optional substring or regex match in body of the response
|
||||||
|
## response_string_match = "\"service_status\": \"up\""
|
||||||
|
## response_string_match = "ok"
|
||||||
|
## response_string_match = "\".*_status\".?:.?\"up\""
|
||||||
|
|
||||||
## Optional SSL Config
|
## Optional SSL Config
|
||||||
# ssl_ca = "/etc/telegraf/ca.pem"
|
# ssl_ca = "/etc/telegraf/ca.pem"
|
||||||
# ssl_cert = "/etc/telegraf/cert.pem"
|
# ssl_cert = "/etc/telegraf/cert.pem"
|
||||||
|
@ -137,6 +147,35 @@ func (h *HTTPResponse) HTTPGather() (map[string]interface{}, error) {
|
||||||
}
|
}
|
||||||
fields["response_time"] = time.Since(start).Seconds()
|
fields["response_time"] = time.Since(start).Seconds()
|
||||||
fields["http_response_code"] = 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["response_string_match"] = 0
|
||||||
|
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["response_string_match"] = 0
|
||||||
|
return fields, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.compiledStringMatch.Match(bodyBytes) {
|
||||||
|
fields["response_string_match"] = 1
|
||||||
|
} else {
|
||||||
|
fields["response_string_match"] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return fields, nil
|
return fields, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,9 @@ func setUpTestMux() http.Handler {
|
||||||
mux.HandleFunc("/good", func(w http.ResponseWriter, req *http.Request) {
|
mux.HandleFunc("/good", func(w http.ResponseWriter, req *http.Request) {
|
||||||
fmt.Fprintf(w, "hit the good page!")
|
fmt.Fprintf(w, "hit the good page!")
|
||||||
})
|
})
|
||||||
|
mux.HandleFunc("/jsonresponse", func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
fmt.Fprintf(w, "\"service_status\": \"up\", \"healthy\" : \"true\"")
|
||||||
|
})
|
||||||
mux.HandleFunc("/badredirect", func(w http.ResponseWriter, req *http.Request) {
|
mux.HandleFunc("/badredirect", func(w http.ResponseWriter, req *http.Request) {
|
||||||
http.Redirect(w, req, "/badredirect", http.StatusMovedPermanently)
|
http.Redirect(w, req, "/badredirect", http.StatusMovedPermanently)
|
||||||
})
|
})
|
||||||
|
@ -236,6 +239,87 @@ func TestBody(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStringMatch(t *testing.T) {
|
||||||
|
mux := setUpTestMux()
|
||||||
|
ts := httptest.NewServer(mux)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
h := &HTTPResponse{
|
||||||
|
Address: ts.URL + "/good",
|
||||||
|
Body: "{ 'test': 'data'}",
|
||||||
|
Method: "GET",
|
||||||
|
ResponseStringMatch: "hit the good page",
|
||||||
|
ResponseTimeout: internal.Duration{Duration: time.Second * 20},
|
||||||
|
Headers: map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
FollowRedirects: true,
|
||||||
|
}
|
||||||
|
fields, err := h.HTTPGather()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, fields)
|
||||||
|
if assert.NotNil(t, fields["http_response_code"]) {
|
||||||
|
assert.Equal(t, http.StatusOK, fields["http_response_code"])
|
||||||
|
}
|
||||||
|
assert.Equal(t, 1, fields["response_string_match"])
|
||||||
|
assert.NotNil(t, fields["response_time"])
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringMatchJson(t *testing.T) {
|
||||||
|
mux := setUpTestMux()
|
||||||
|
ts := httptest.NewServer(mux)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
h := &HTTPResponse{
|
||||||
|
Address: ts.URL + "/jsonresponse",
|
||||||
|
Body: "{ 'test': 'data'}",
|
||||||
|
Method: "GET",
|
||||||
|
ResponseStringMatch: "\"service_status\": \"up\"",
|
||||||
|
ResponseTimeout: internal.Duration{Duration: time.Second * 20},
|
||||||
|
Headers: map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
FollowRedirects: true,
|
||||||
|
}
|
||||||
|
fields, err := h.HTTPGather()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, fields)
|
||||||
|
if assert.NotNil(t, fields["http_response_code"]) {
|
||||||
|
assert.Equal(t, http.StatusOK, fields["http_response_code"])
|
||||||
|
}
|
||||||
|
assert.Equal(t, 1, fields["response_string_match"])
|
||||||
|
assert.NotNil(t, fields["response_time"])
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringMatchFail(t *testing.T) {
|
||||||
|
mux := setUpTestMux()
|
||||||
|
ts := httptest.NewServer(mux)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
h := &HTTPResponse{
|
||||||
|
Address: ts.URL + "/good",
|
||||||
|
Body: "{ 'test': 'data'}",
|
||||||
|
Method: "GET",
|
||||||
|
ResponseStringMatch: "hit the bad page",
|
||||||
|
ResponseTimeout: internal.Duration{Duration: time.Second * 20},
|
||||||
|
Headers: map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
FollowRedirects: true,
|
||||||
|
}
|
||||||
|
fields, err := h.HTTPGather()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, fields)
|
||||||
|
if assert.NotNil(t, fields["http_response_code"]) {
|
||||||
|
assert.Equal(t, http.StatusOK, fields["http_response_code"])
|
||||||
|
}
|
||||||
|
assert.Equal(t, 0, fields["response_string_match"])
|
||||||
|
assert.NotNil(t, fields["response_time"])
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestTimeout(t *testing.T) {
|
func TestTimeout(t *testing.T) {
|
||||||
mux := setUpTestMux()
|
mux := setUpTestMux()
|
||||||
ts := httptest.NewServer(mux)
|
ts := httptest.NewServer(mux)
|
||||||
|
|
Loading…
Reference in New Issue