本文主要研究一下dubbo-go-proxy的AccessLogFilter
dubbo-go-proxy/pkg/filter/accesslog/access_log.go
var accessLogWriter = &model.AccessLogWriter{AccessLogDataChan: make(chan model.AccessLogData, constant.LogDataBuffer)} func init() { extension.SetFilterFunc(constant.AccessLogFilter, accessLog()) accessLogWriter.Write() } // access log filter func accessLog() context.FilterFunc { return func(c context.Context) { alc := config.GetBootstrap().StaticResources.AccessLogConfig if !alc.Enable { return } start := time.Now() c.Next() latency := time.Now().Sub(start) // build access_log message accessLogMsg := buildAccessLogMsg(c, latency) if len(accessLogMsg) > 0 { accessLogWriter.Writer(model.AccessLogData{AccessLogConfig: alc, AccessLogMsg: accessLogMsg}) } } }
init方法通过extension.SetFilterFunc注册了名为constant.AccessLogFilter的context.FilterFunc,然后执行accessLogWriter.Write();accessLog方法返回context.FilterFunc,该func通过buildAccessLogMsg构建accessLogMsg,然后执行accessLogWriter.Writer
dubbo-go-proxy/pkg/filter/accesslog/access_log.go
func buildAccessLogMsg(c context.Context, cost time.Duration) string { req := c.(*http.HttpContext).Request valueStr := req.URL.Query().Encode() if len(valueStr) != 0 { valueStr = strings.ReplaceAll(valueStr, "&", ",") } builder := strings.Builder{} builder.WriteString("[") builder.WriteString(time.Now().Format(constant.MessageDateLayout)) builder.WriteString("] ") builder.WriteString(req.RemoteAddr) builder.WriteString(" -> ") builder.WriteString(req.Host) builder.WriteString(" - ") if len(valueStr) > 0 { builder.WriteString("request params: [") builder.WriteString(valueStr) builder.WriteString("] ") } builder.WriteString("cost time [ ") builder.WriteString(strconv.Itoa(int(cost)) + " ]") err := c.(*http.HttpContext).Err if err != nil { builder.WriteString(fmt.Sprintf("invoke err [ %v", err)) builder.WriteString("] ") } resp := c.(*http.HttpContext).TargetResp.Data rbs, err := getBytes(resp) if err != nil { builder.WriteString(fmt.Sprintf(" response can not convert to string")) builder.WriteString("] ") } else { builder.WriteString(fmt.Sprintf(" response [ %+v", string(rbs))) builder.WriteString("] ") } //builder.WriteString("\n") return builder.String() }
buildAccessLogMsg方法使用strings.Builder构建accesslog,依次打印time、remoteAddr、host、request params、cost time、err、resp
dubbo-go-proxy/pkg/model/log.go
// access log chan type AccessLogWriter struct { AccessLogDataChan chan AccessLogData } // access log data type AccessLogData struct { AccessLogMsg string AccessLogConfig AccessLogConfig } // writer msg into chan func (alw *AccessLogWriter) Writer(accessLogData AccessLogData) { select { case alw.AccessLogDataChan <- accessLogData: return default: logger.Warn("the channel is full and the access logIntoChannel data will be dropped") return } }
AccessLogWriter定义了AccessLogDataChan属性,它是一个AccessLogData类型的channel;Writer方法往AccessLogDataChan写入accessLogData
dubbo-go-proxy/pkg/model/log.go
// write log into out put path func (alw *AccessLogWriter) Write() { go func() { for accessLogData := range alw.AccessLogDataChan { alw.writeLogToFile(accessLogData) } }() } // write log to file or console func (alw *AccessLogWriter) writeLogToFile(ald AccessLogData) { alc := ald.AccessLogConfig alm := ald.AccessLogMsg if len(alc.OutPutPath) == 0 || alc.OutPutPath == constant.Console { logger.Info(alm) return } _ = WriteToFile(alm, alc.OutPutPath) }
Write启动异步线程遍历AccessLogDataChan,执行writeLogToFile
dubbo-go-proxy/pkg/model/log.go
// write message to access log file func WriteToFile(accessLogMsg string, filePath string) error { pd := filepath.Dir(filePath) if _, err := os.Stat(pd); err != nil { if os.IsExist(err) { logger.Warnf("can not open log dir: %s, %v", filePath, err) } err = os.MkdirAll(pd, os.ModePerm) if err != nil { logger.Warnf("can not create log dir: %s, %v", filePath, err) return err } } logFile, err := os.OpenFile(filePath, os.O_CREATE|os.O_APPEND|os.O_RDWR, constant.LogFileMode) if err != nil { logger.Warnf("can not open the access log file: %s, %v", filePath, err) return err } now := time.Now().Format(constant.FileDateFormat) fileInfo, err := logFile.Stat() if err != nil { logger.Warnf("can not get the info of access log file: %s, %v", filePath, err) return err } last := fileInfo.ModTime().Format(constant.FileDateFormat) // this is confused. // for example, if the last = '2020-03-04' // and today is '2020-03-05' // we will create one new file to log access data // By this way, we can split the access log based on days. if now != last { err = os.Rename(fileInfo.Name(), fileInfo.Name()+"."+now) if err != nil { logger.Warnf("can not rename access log file: %s, %v", fileInfo.Name(), err) return err } logFile, err = os.OpenFile(fileInfo.Name(), os.O_CREATE|os.O_APPEND|os.O_RDWR, constant.LogFileMode) if err != nil { logger.Warnf("can not open access log file: %s, %v", fileInfo.Name(), err) return err } } _, err = logFile.WriteString(accessLogMsg + "\n") if err != nil { logger.Warnf("can not write to access log file: %s, v%", fileInfo.Name(), err) return err } return nil }
WriteToFile方法会按日期对log进行分割,最后通过logFile.WriteString写入日志
dubbo-go-proxy的AccessLogFilter通过extension.SetFilterFunc注册了名为constant.AccessLogFilter的context.FilterFunc,该func通过buildAccessLogMsg构建accessLogMsg,然后往AccessLogDataChan写入accessLogData;然后执行accessLogWriter.Write()启动协程遍历AccessLogDataChan,执行writeLogToFile。