-
Notifications
You must be signed in to change notification settings - Fork 3
/
url_monitor.go
369 lines (332 loc) · 8.83 KB
/
url_monitor.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
package url_monitor
import (
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/plugins/inputs"
)
// HTTPResponse struct
type HTTPResponse struct {
Cmdbid string
App string
Address string
Params string
Body string
Method string
ResponseTimeout internal.Duration
Headers map[string]string
FollowRedirects bool
RequireStr string
RequireCode string
FailedCount int
FailedTimeout float64
// Path to CA file
SSLCA string `toml:"ssl_ca"`
// Path to host cert file
SSLCert string `toml:"ssl_cert"`
// Path to cert key file
SSLKey string `toml:"ssl_key"`
// Use SSL but skip chain & host verification
InsecureSkipVerify bool
}
// Description returns the plugin Description
func (h *HTTPResponse) Description() string {
return "HTTP/HTTPS request given an address a method and a timeout"
}
var sampleConfig = `
## App Name
app = "monitor"
## CMDB ID
cmdbid = "1701"
## Server address (default http://localhost)
address = "http://www.baidu.com"
## Set response_timeout (default 5 seconds)
response_timeout = "5s"
## HTTP Request Method
method = "GET"
## Require String 正则表达式用单引号避免转义,如果需要包含单引号,请使用'''。如果字符串末尾有单引号',需要换行
require_str = '''
baidu.com
'''
require_code = '20\d'
failed_count = 3
failed_timeout = 0.5
## Whether to follow redirects from the server (defaults to false)
follow_redirects = true
## GET params
params = '''
tk=xxx&sign=xxx
'''
## Optional HTTP Request Body
# body = '''
# {'fake':'data'}
# '''
## Optional SSL Config
# ssl_ca = "/etc/telegraf/ca.pem"
# ssl_cert = "/etc/telegraf/cert.pem"
# ssl_key = "/etc/telegraf/key.pem"
## Use SSL but skip chain & host verification
# insecure_skip_verify = false
## HTTP Request Headers (all values must be strings)
## 表格名下,直到下一个表格名或文件尾,均为当前表格的内容 所以Headers应该放在最后
# [inputs.url_monitor.headers]
# Host = "github.com"
`
// SampleConfig returns the plugin SampleConfig
func (h *HTTPResponse) SampleConfig() string {
return sampleConfig
}
// ErrRedirectAttempted indicates that a redirect occurred
var ErrRedirectAttempted = errors.New("redirect")
// CreateHttpClient creates an http client which will timeout at the specified
// timeout period and can follow redirects if specified
func (h *HTTPResponse) createHttpClient() (*http.Client, error) {
tlsCfg, err := internal.GetTLSConfig(
h.SSLCert, h.SSLKey, h.SSLCA, h.InsecureSkipVerify)
if err != nil {
return nil, err
}
tr := &http.Transport{
ResponseHeaderTimeout: h.ResponseTimeout.Duration,
TLSClientConfig: tlsCfg,
}
client := &http.Client{
Transport: tr,
Timeout: h.ResponseTimeout.Duration,
}
if h.FollowRedirects == false {
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return ErrRedirectAttempted
}
}
return client, nil
}
// HTTPGather gathers all fields and returns any errors it encounters
func (h *HTTPResponse) HTTPGather() (map[string]interface{}, error) {
// Prepare fields
fields := make(map[string]interface{})
// 为了require_str,require_code能够包含任意字符,使用多行'''包裹,因此需要去除换行符
rStr := trimStr(h.RequireStr)
rCode := trimStr(h.RequireCode)
rParms := trimStr(h.Params)
rBody := trimStr(h.Body)
// 经常被用户更改配置的变量不在作为tag,作为fields
fields["require_code"] = rCode
fields["require_str"] = rStr
fields["require_time"] = strconv.FormatFloat(h.FailedTimeout, 'g', 1, 64)
fields["failed_threshold"] = strconv.Itoa(h.FailedCount)
client, err := h.createHttpClient()
if err != nil {
return nil, err
}
var body io.Reader
address := h.Address + "?" + rParms
if rBody != "" {
body = strings.NewReader(rBody)
}
request, err := http.NewRequest(h.Method, address, body)
if err != nil {
fields["msg"] = err
//return fields,nil
//return nil, err
}
content_type := 0
for key, val := range h.Headers {
request.Header.Add(key, val)
if key == "Host" {
request.Host = val
}
//如果指定了content-type, content_type设置为1
if strings.ToLower(key) == "content-type" {
content_type = 1
}
}
//如果没有指定content-type,则设置默认值为application/x-www-form-urlencoded
if content_type == 0 {
request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
}
// Start Timer
start := time.Now()
resp, err := client.Do(request)
// 保证关闭连接. 不关闭连接将导致close-wait累积,最终占满端口。监控将报错:cannot assign requested address
// 当请求失败,resp为nil时,直接defer会导致panic,因此需要先判断
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
if h.FollowRedirects {
fields["msg"] = err
//return fields,nil
//return nil, err
}
if urlError, ok := err.(*url.Error); ok &&
urlError.Err == ErrRedirectAttempted {
err = nil
} else {
fields["msg"] = err
//err = nil
//return fields,nil
}
}
_, ok := fields["msg"]
if ok {
fields["data_match"] = 0
fields["code_match"] = 0
fields["time_match"] = 0
fields["response_time"] = time.Since(start).Seconds()
fields["http_code"] = 000
return fields, nil
}
// require string
if rStr == "" {
fields["data_match"] = 1
} else {
body, _ := ioutil.ReadAll(resp.Body)
bodystr := string(body)
r, err := regexp.Compile(rStr)
//r,_ := regexp.CompilePOSIX(rStr)
if err != nil {
if strings.Contains(bodystr, rStr) {
fields["data_match"] = 1
} else {
fields["data_match"] = 0
// fields['msg']中文unicode转字符串,并截取超长的内容
fields["msg"] = suberrmsg(bodystr)
}
} else {
if r.FindString(bodystr) != "" {
fields["data_match"] = 1
} else {
fields["data_match"] = 0
fields["msg"] = suberrmsg(bodystr)
}
}
}
// require http code
if rCode == "" {
fields["code_match"] = 1
} else {
status_code := strconv.Itoa(resp.StatusCode)
r, err := regexp.Compile(rCode)
//r,_ := regexp.CompilePOSIX(rCode)
if err != nil {
if strings.Contains(status_code, rCode) {
fields["code_match"] = 1
} else {
fields["code_match"] = 0
}
} else {
if r.FindString(status_code) != "" {
fields["code_match"] = 1
} else {
fields["code_match"] = 0
}
}
}
fields["response_time"] = time.Since(start).Seconds()
fields["http_code"] = resp.StatusCode
// require response time
fields["time_match"] = 1
rt := fields["response_time"].(float64)
if rt > h.FailedTimeout {
fields["time_match"] = 0
}
if fields["time_match"] == 0 && rt < h.FailedTimeout*0.7 {
fields["time_match"] = 1
}
return fields, nil
}
// Gather gets all metric fields and tags and returns any errors it encounters
func (h *HTTPResponse) Gather(acc telegraf.Accumulator) error {
// Set default values
if h.ResponseTimeout.Duration < time.Second {
h.ResponseTimeout.Duration = time.Second * 5
}
// Check send and expected string
if h.Method == "" {
h.Method = "GET"
}
if h.Address == "" {
h.Address = "http://localhost"
}
addr, err := url.Parse(h.Address)
if err != nil {
return err
}
if addr.Scheme != "http" && addr.Scheme != "https" {
return errors.New("Only http and https are supported")
}
// Prepare data
tags := map[string]string{"cmdbid": h.Cmdbid, "app": h.App, "url": h.Address, "method": h.Method}
var fields map[string]interface{}
// Gather data
fields, err = h.HTTPGather()
if err != nil {
return err
}
// Add metrics
acc.AddFields("url_monitor", fields, tags)
return nil
}
func init() {
inputs.Add("url_monitor", func() telegraf.Input {
return &HTTPResponse{}
})
}
// delete \n \r from string
func trimStr(str string) (newstr string) {
newstr = strings.Replace(str, "\r", "", -1)
newstr = strings.Replace(newstr, "\n", "", -1)
return
}
// fields['msg']最长不超过1kb
func suberrmsg(errmsg string) (submsg string) {
limit := 1250
u2s, _ := unicode2str(errmsg)
if len(u2s) > limit {
submsg = u2s[0:limit]
} else {
submsg = u2s
}
r := regexp.MustCompile(`(.*?)?\\\\*$`)
rep := "${1}."
submsg = r.ReplaceAllString(submsg, rep)
return
}
// convert unicode chinese char to string
func unicode2str(u string) (context string, err error) {
sUnicodev := strings.Split(u, "\\u")
for _, v := range sUnicodev {
if len(v) < 1 {
continue
}
if len(v) > 4 {
v1 := v[0:4]
v2 := v[4:len(v)]
temp, err := strconv.ParseInt(v1, 16, 32)
if err != nil {
context += v
} else {
context += fmt.Sprintf("%c", temp)
context += v2
}
} else {
temp, err := strconv.ParseInt(v, 16, 32)
if err != nil {
context += v
} else {
context += fmt.Sprintf("%c", temp)
}
}
}
return
}