master
go 563 lines 11 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package weblog
4
5 import (
6 "fmt"
7 "io"
8 "net/http"
9 "runtime"
10 "strconv"
11 "strings"
12
13 "github.com/netdata/netdata/go/plugins/pkg/stm"
14 "github.com/netdata/netdata/go/plugins/plugin/framework/collectorapi"
15 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/logs"
16 )
17
18 func (c *Collector) logPanicStackIfAny() {
19 err := recover()
20 if err == nil {
21 return
22 }
23 c.Errorf("[ERROR] %s\n", err)
24 for depth := 0; ; depth++ {
25 _, file, line, ok := runtime.Caller(depth)
26 if !ok {
27 break
28 }
29 c.Errorf("======> %d: %v:%d", depth, file, line)
30 }
31 panic(err)
32 }
33
34 func (c *Collector) collect() (map[string]int64, error) {
35 defer c.logPanicStackIfAny()
36 c.mx.reset()
37
38 var mx map[string]int64
39
40 n, err := c.collectLogLines()
41
42 if n > 0 || err == nil {
43 mx = stm.ToMap(c.mx)
44 }
45 return mx, err
46 }
47
48 func (c *Collector) collectLogLines() (int, error) {
49 logOnce := true
50 var n int
51 for {
52 c.line.reset()
53 err := c.parser.ReadLine(c.line)
54 if err != nil {
55 if err == io.EOF {
56 return n, nil
57 }
58 if !logs.IsParseError(err) {
59 return n, err
60 }
61 n++
62 if logOnce {
63 c.Infof("unmatched line: %v (parser: %s)", err, c.parser.Info())
64 logOnce = false
65 }
66 c.collectUnmatched()
67 continue
68 }
69 n++
70 if c.line.empty() {
71 c.collectUnmatched()
72 } else {
73 c.collectLogLine()
74 }
75 }
76 }
77
78 func (c *Collector) collectLogLine() {
79 // https://github.com/netdata/netdata/issues/17716
80 if c.line.hasReqProcTime() && c.line.respCode == http.StatusSwitchingProtocols {
81 c.line.reqProcTime = emptyNumber
82 }
83 c.mx.Requests.Inc()
84 c.collectVhost()
85 c.collectPort()
86 c.collectReqScheme()
87 c.collectReqClient()
88 c.collectReqMethod()
89 c.collectReqURL()
90 c.collectReqProto()
91 c.collectRespCode()
92 c.collectReqSize()
93 c.collectRespSize()
94 c.collectReqProcTime()
95 c.collectUpsRespTime()
96 c.collectSSLProto()
97 c.collectSSLCipherSuite()
98 c.collectCustomFields()
99 }
100
101 func (c *Collector) collectUnmatched() {
102 c.mx.Requests.Inc()
103 c.mx.ReqUnmatched.Inc()
104 }
105
106 func (c *Collector) collectVhost() {
107 if !c.line.hasVhost() {
108 return
109 }
110 cntr, ok := c.mx.ReqVhost.GetP(c.line.vhost)
111 if !ok {
112 c.addDimToVhostChart(c.line.vhost)
113 }
114 cntr.Inc()
115 }
116
117 func (c *Collector) collectPort() {
118 if !c.line.hasPort() {
119 return
120 }
121 cntr, ok := c.mx.ReqPort.GetP(c.line.port)
122 if !ok {
123 c.addDimToPortChart(c.line.port)
124 }
125 cntr.Inc()
126 }
127
128 func (c *Collector) collectReqClient() {
129 if !c.line.hasReqClient() {
130 return
131 }
132 if strings.ContainsRune(c.line.reqClient, ':') {
133 c.mx.ReqIPv6.Inc()
134 c.mx.UniqueIPv6.Insert(c.line.reqClient)
135 return
136 }
137 // NOTE: count hostname as IPv4 address
138 c.mx.ReqIPv4.Inc()
139 c.mx.UniqueIPv4.Insert(c.line.reqClient)
140 }
141
142 func (c *Collector) collectReqScheme() {
143 if !c.line.hasReqScheme() {
144 return
145 }
146 if c.line.reqScheme == "https" {
147 c.mx.ReqHTTPSScheme.Inc()
148 } else {
149 c.mx.ReqHTTPScheme.Inc()
150 }
151 }
152
153 func (c *Collector) collectReqMethod() {
154 if !c.line.hasReqMethod() {
155 return
156 }
157 cntr, ok := c.mx.ReqMethod.GetP(c.line.reqMethod)
158 if !ok {
159 c.addDimToReqMethodChart(c.line.reqMethod)
160 }
161 cntr.Inc()
162 }
163
164 func (c *Collector) collectReqURL() {
165 if !c.line.hasReqURL() {
166 return
167 }
168 for _, p := range c.urlPatterns {
169 if !p.MatchString(c.line.reqURL) {
170 continue
171 }
172 cntr, _ := c.mx.ReqURLPattern.GetP(p.name)
173 cntr.Inc()
174
175 c.collectURLPatternStats(p.name)
176 return
177 }
178 }
179
180 func (c *Collector) collectReqProto() {
181 if !c.line.hasReqProto() {
182 return
183 }
184 cntr, ok := c.mx.ReqVersion.GetP(c.line.reqProto)
185 if !ok {
186 c.addDimToReqVersionChart(c.line.reqProto)
187 }
188 cntr.Inc()
189 }
190
191 func (c *Collector) collectRespCode() {
192 if !c.line.hasRespCode() {
193 return
194 }
195
196 code := c.line.respCode
197 switch {
198 case code >= 100 && code < 300, code == 304, code == 401, code == 429:
199 c.mx.ReqSuccess.Inc()
200 case code >= 300 && code < 400:
201 c.mx.ReqRedirect.Inc()
202 case code >= 400 && code < 500:
203 c.mx.ReqBad.Inc()
204 case code >= 500 && code < 600:
205 c.mx.ReqError.Inc()
206 }
207
208 switch code / 100 {
209 case 1:
210 c.mx.Resp1xx.Inc()
211 case 2:
212 c.mx.Resp2xx.Inc()
213 case 3:
214 c.mx.Resp3xx.Inc()
215 case 4:
216 c.mx.Resp4xx.Inc()
217 case 5:
218 c.mx.Resp5xx.Inc()
219 }
220
221 codeStr := strconv.Itoa(code)
222 cntr, ok := c.mx.RespCode.GetP(codeStr)
223 if !ok {
224 c.addDimToRespCodesChart(codeStr)
225 }
226 cntr.Inc()
227 }
228
229 func (c *Collector) collectReqSize() {
230 if !c.line.hasReqSize() {
231 return
232 }
233 c.mx.BytesReceived.Add(float64(c.line.reqSize))
234 }
235
236 func (c *Collector) collectRespSize() {
237 if !c.line.hasRespSize() {
238 return
239 }
240 c.mx.BytesSent.Add(float64(c.line.respSize))
241 }
242
243 func (c *Collector) collectReqProcTime() {
244 if !c.line.hasReqProcTime() {
245 return
246 }
247 c.mx.ReqProcTime.Observe(c.line.reqProcTime)
248 if c.mx.ReqProcTimeHist == nil {
249 return
250 }
251 c.mx.ReqProcTimeHist.Observe(c.line.reqProcTime)
252 }
253
254 func (c *Collector) collectUpsRespTime() {
255 if !c.line.hasUpsRespTime() {
256 return
257 }
258 c.mx.UpsRespTime.Observe(c.line.upsRespTime)
259 if c.mx.UpsRespTimeHist == nil {
260 return
261 }
262 c.mx.UpsRespTimeHist.Observe(c.line.upsRespTime)
263 }
264
265 func (c *Collector) collectSSLProto() {
266 if !c.line.hasSSLProto() {
267 return
268 }
269 cntr, ok := c.mx.ReqSSLProto.GetP(c.line.sslProto)
270 if !ok {
271 c.addDimToSSLProtoChart(c.line.sslProto)
272 }
273 cntr.Inc()
274 }
275
276 func (c *Collector) collectSSLCipherSuite() {
277 if !c.line.hasSSLCipherSuite() {
278 return
279 }
280 cntr, ok := c.mx.ReqSSLCipherSuite.GetP(c.line.sslCipherSuite)
281 if !ok {
282 c.addDimToSSLCipherSuiteChart(c.line.sslCipherSuite)
283 }
284 cntr.Inc()
285 }
286
287 func (c *Collector) collectURLPatternStats(name string) {
288 v, ok := c.mx.URLPatternStats[name]
289 if !ok {
290 return
291 }
292 if c.line.hasRespCode() {
293 status := strconv.Itoa(c.line.respCode)
294 cntr, ok := v.RespCode.GetP(status)
295 if !ok {
296 c.addDimToURLPatternRespCodesChart(name, status)
297 }
298 cntr.Inc()
299 }
300
301 if c.line.hasReqMethod() {
302 cntr, ok := v.ReqMethod.GetP(c.line.reqMethod)
303 if !ok {
304 c.addDimToURLPatternReqMethodsChart(name, c.line.reqMethod)
305 }
306 cntr.Inc()
307 }
308
309 if c.line.hasReqSize() {
310 v.BytesReceived.Add(float64(c.line.reqSize))
311 }
312
313 if c.line.hasRespSize() {
314 v.BytesSent.Add(float64(c.line.respSize))
315 }
316 if c.line.hasReqProcTime() {
317 v.ReqProcTime.Observe(c.line.reqProcTime)
318 }
319 }
320
321 func (c *Collector) collectCustomFields() {
322 if !c.line.hasCustomFields() {
323 return
324 }
325
326 for _, cv := range c.line.custom.values {
327 _, _ = cv.name, cv.value
328
329 if patterns, ok := c.customFields[cv.name]; ok {
330 for _, pattern := range patterns {
331 if !pattern.MatchString(cv.value) {
332 continue
333 }
334 v, ok := c.mx.ReqCustomField[cv.name]
335 if !ok {
336 break
337 }
338 c, _ := v.GetP(pattern.name)
339 c.Inc()
340 break
341 }
342 } else if histogram, ok := c.customTimeFields[cv.name]; ok {
343 v, ok := c.mx.ReqCustomTimeField[cv.name]
344 if !ok {
345 continue
346 }
347 ctf, err := strconv.ParseFloat(cv.value, 64)
348 if err != nil || !isTimeValid(ctf) {
349 continue
350 }
351 v.Time.Observe(ctf)
352 if histogram != nil {
353 v.TimeHist.Observe(ctf * timeMultiplier(cv.value))
354 }
355 } else if c.customNumericFields[cv.name] {
356 m, ok := c.mx.ReqCustomNumericField[cv.name]
357 if !ok {
358 continue
359 }
360 v, err := strconv.ParseFloat(cv.value, 64)
361 if err != nil {
362 continue
363 }
364 v *= float64(m.multiplier)
365 m.Summary.Observe(v)
366 }
367 }
368 }
369
370 func (c *Collector) addDimToVhostChart(vhost string) {
371 chart := c.Charts().Get(reqByVhost.ID)
372 if chart == nil {
373 c.Warningf("add dimension: no '%s' chart", reqByVhost.ID)
374 return
375 }
376 dim := &Dim{
377 ID: "req_vhost_" + vhost,
378 Name: vhost,
379 Algo: collectorapi.Incremental,
380 }
381 if err := chart.AddDim(dim); err != nil {
382 c.Warning(err)
383 return
384 }
385 chart.MarkNotCreated()
386 }
387
388 func (c *Collector) addDimToPortChart(port string) {
389 chart := c.Charts().Get(reqByPort.ID)
390 if chart == nil {
391 c.Warningf("add dimension: no '%s' chart", reqByPort.ID)
392 return
393 }
394 dim := &Dim{
395 ID: "req_port_" + port,
396 Name: port,
397 Algo: collectorapi.Incremental,
398 }
399 if err := chart.AddDim(dim); err != nil {
400 c.Warning(err)
401 return
402 }
403 chart.MarkNotCreated()
404 }
405
406 func (c *Collector) addDimToReqMethodChart(method string) {
407 chart := c.Charts().Get(reqByMethod.ID)
408 if chart == nil {
409 c.Warningf("add dimension: no '%s' chart", reqByMethod.ID)
410 return
411 }
412 dim := &Dim{
413 ID: "req_method_" + method,
414 Name: method,
415 Algo: collectorapi.Incremental,
416 }
417 if err := chart.AddDim(dim); err != nil {
418 c.Warning(err)
419 return
420 }
421 chart.MarkNotCreated()
422 }
423
424 func (c *Collector) addDimToReqVersionChart(version string) {
425 chart := c.Charts().Get(reqByVersion.ID)
426 if chart == nil {
427 c.Warningf("add dimension: no '%s' chart", reqByVersion.ID)
428 return
429 }
430 dim := &Dim{
431 ID: "req_version_" + version,
432 Name: version,
433 Algo: collectorapi.Incremental,
434 }
435 if err := chart.AddDim(dim); err != nil {
436 c.Warning(err)
437 return
438 }
439 chart.MarkNotCreated()
440 }
441
442 func (c *Collector) addDimToSSLProtoChart(proto string) {
443 chart := c.Charts().Get(reqBySSLProto.ID)
444 if chart == nil {
445 chart = reqBySSLProto.Copy()
446 if err := c.Charts().Add(chart); err != nil {
447 c.Warning(err)
448 return
449 }
450 }
451 dim := &Dim{
452 ID: "req_ssl_proto_" + proto,
453 Name: proto,
454 Algo: collectorapi.Incremental,
455 }
456 if err := chart.AddDim(dim); err != nil {
457 c.Warning(err)
458 return
459 }
460 chart.MarkNotCreated()
461 }
462
463 func (c *Collector) addDimToSSLCipherSuiteChart(cipher string) {
464 chart := c.Charts().Get(reqBySSLCipherSuite.ID)
465 if chart == nil {
466 chart = reqBySSLCipherSuite.Copy()
467 if err := c.Charts().Add(chart); err != nil {
468 c.Warning(err)
469 return
470 }
471 }
472 dim := &Dim{
473 ID: "req_ssl_cipher_suite_" + cipher,
474 Name: cipher,
475 Algo: collectorapi.Incremental,
476 }
477 if err := chart.AddDim(dim); err != nil {
478 c.Warning(err)
479 return
480 }
481 chart.MarkNotCreated()
482 }
483
484 func (c *Collector) addDimToRespCodesChart(code string) {
485 chart := c.findRespCodesChart(code)
486 if chart == nil {
487 c.Warning("add dimension: cant find resp codes chart")
488 return
489 }
490 dim := &Dim{
491 ID: "resp_code_" + code,
492 Name: code,
493 Algo: collectorapi.Incremental,
494 }
495 if err := chart.AddDim(dim); err != nil {
496 c.Warning(err)
497 return
498 }
499 chart.MarkNotCreated()
500 }
501
502 func (c *Collector) addDimToURLPatternRespCodesChart(name, code string) {
503 id := fmt.Sprintf(urlPatternRespCodes.ID, name)
504 chart := c.Charts().Get(id)
505 if chart == nil {
506 c.Warningf("add dimension: no '%s' chart", id)
507 return
508 }
509 dim := &Dim{
510 ID: fmt.Sprintf("url_ptn_%s_resp_code_%s", name, code),
511 Name: code,
512 Algo: collectorapi.Incremental,
513 }
514
515 if err := chart.AddDim(dim); err != nil {
516 c.Warning(err)
517 return
518 }
519 chart.MarkNotCreated()
520 }
521
522 func (c *Collector) addDimToURLPatternReqMethodsChart(name, method string) {
523 id := fmt.Sprintf(urlPatternReqMethods.ID, name)
524 chart := c.Charts().Get(id)
525 if chart == nil {
526 c.Warningf("add dimension: no '%s' chart", id)
527 return
528 }
529 dim := &Dim{
530 ID: fmt.Sprintf("url_ptn_%s_req_method_%s", name, method),
531 Name: method,
532 Algo: collectorapi.Incremental,
533 }
534
535 if err := chart.AddDim(dim); err != nil {
536 c.Warning(err)
537 return
538 }
539 chart.MarkNotCreated()
540 }
541
542 func (c *Collector) findRespCodesChart(code string) *Chart {
543 if !c.GroupRespCodes {
544 return c.Charts().Get(respCodes.ID)
545 }
546
547 var id string
548 switch class := code[:1]; class {
549 case "1":
550 id = respCodes1xx.ID
551 case "2":
552 id = respCodes2xx.ID
553 case "3":
554 id = respCodes3xx.ID
555 case "4":
556 id = respCodes4xx.ID
557 case "5":
558 id = respCodes5xx.ID
559 default:
560 return nil
561 }
562 return c.Charts().Get(id)
563 }