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