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  }