gin框架是款高性能的GoWeb框架,可以快速开发部署api服务。在使用过程中我们需要记录各种各样的日志,下面介绍下我们怎么自定义日志记录格式或扩展日志。
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080
}
以上是github中官方介绍的一个简单的demo,三步创建了一个api服务
gin.Default 获取到一个Engine实例
engine.GET() 添加一个Get请求的路由逻辑
engine.Run() 启动服务
func New() *Engine {
debugPrintWARNINGNew()
engine := &Engine{
RouterGroup: RouterGroup{
Handlers: nil,
basePath: "/",
root: true,
},
FuncMap: template.FuncMap{},
RedirectTrailingSlash: true,
RedirectFixedPath: false,
HandleMethodNotAllowed: false,
ForwardedByClientIP: true,
AppEngine: defaultAppEngine,
UseRawPath: false,
UnescapePathValues: true,
trees: make(methodTrees, 0, 9),
delims: render.Delims{"{{", "}}"},
}
engine.RouterGroup.engine = engine
engine.pool.New = func() interface{} {
return engine.allocateContext()
}
return engine
}
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
gin.Default 通过New创建了Engine实例, 并Use了 Logger Recovery两个HandlerFunc中间件。注释也介绍了
默认返回一个引擎实例,其中包含日志记录器和崩溃恢复中间件。
那么我们是不是可以通过自己的Logger中间件来记录日志? 写到这里发现官方文档其实是有介绍的,少走弯路还是先看文档额~~不过直接看源码也没坏处,哈哈
func (c *Context) Next() {
c.index++
s := int8(len(c.handlers))
for ; c.index < s; c.index++ {
c.handlers[c.index](c)
}
}
gin通过循环当前的中间件处理链Handler,逐个调用中间件。
日志记录(logrus)
logrus是第三方包,github上比较活跃,已经实现了很多常用功能,日常开发中用到的较多。
日志分割(rotatelogs)
logrus没有提供日志切分,go-file-rotatelogs可以实现linux logratate的大多数功能。
使用logrus的hook来加载 github.com/lestrrat/go-file-rotatelogs 模块.每次当我们写入日志的时候,logrus都会调用go-file-rotatelogs 来判断日志是否要进行切分…
package api
import (
"github.com/gin-gonic/gin"
"os"
"fmt"
"github.com/sirupsen/logrus"
"github.com/lestrrat/go-file-rotatelogs"
"time"
"github.com/rifflock/lfshook"
)
func Logger() gin.HandlerFunc {
logClient := logrus.New()
//禁止logrus的输出
src, err := os.OpenFile(os.DevNull, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
if err!= nil{
fmt.Println("err", err)
}
logClient.Out = src
logClient.SetLevel(logrus.DebugLevel)
apiLogPath := "api.log"
logWriter, err := rotatelogs.New(
apiLogPath+".%Y-%m-%d-%H-%M.log",
rotatelogs.WithLinkName(apiLogPath), // 生成软链,指向最新日志文件
rotatelogs.WithMaxAge(7*24*time.Hour), // 文件最大保存时间
rotatelogs.WithRotationTime(24*time.Hour), // 日志切割时间间隔
)
writeMap := lfshook.WriterMap{
logrus.InfoLevel: logWriter,
logrus.FatalLevel: logWriter,
}
lfHook := lfshook.NewHook(writeMap, &logrus.JSONFormatter{})
logClient.AddHook(lfHook)
return func (c *gin.Context) {
// 开始时间
start := time.Now()
// 处理请求
c.Next()
// 结束时间
end := time.Now()
//执行时间
latency := end.Sub(start)
path := c.Request.URL.Path
clientIP := c.ClientIP()
method := c.Request.Method
statusCode := c.Writer.Status()
logClient.Infof("| %3d | %13v | %15s | %s %s |",
statusCode,
latency,
clientIP,
method, path,
)
}
}
注:
// Next should be used only inside middleware.
// It executes the pending handlers in the chain inside the calling handler.
// See example in GitHub.
func (c *Context) Next() {
c.index++
s := int8(len(c.handlers))
for ; c.index < s; c.index++ {
c.handlers[c.index](c)
}
}
这里面为了计算程序的执行时长, 我们又调用了一次c.Next(),让后面的中间件执行,这样日志中间件拿到了程序其它所有中间件执行的总时长,认为是程序的处理时间。
c.Next循环的时候都是同一个Context指针上下文,index下标为共享的数据,我们可以通过调用一次c.Next让log中间件插一脚。