序
本文次要钻研一下 dubbo-go-proxy 的 AccessLogFilter
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
buildAccessLogMsg
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
AccessLogWriter
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
Write
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
WriteToFile
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。
doc
- dubbo-go-proxy