golang echo 代码详解之 log 篇

echo 自带的 log 库

log 结构

echo 框架的 log 结构体是 echo.Echo 结构体的一个属性

    type Echo struct {
        ...
        Logger Logger
    }

而 logger 是个这样的接口

type (
    // Logger defines the logging interface.
    Logger interface {
        Output() io.Writer
        SetOutput(w io.Writer)
        Prefix() string
        SetPrefix(p string)
        Level() log.Lvl
        SetLevel(v log.Lvl)
        Print(i ...interface{})
        Printf(format string, args ...interface{})
        Printj(j log.JSON)
        Debug(i ...interface{})
        Debugf(format string, args ...interface{})
        Debugj(j log.JSON)
        Info(i ...interface{})
        Infof(format string, args ...interface{})
        Infoj(j log.JSON)
        Warn(i ...interface{})
        Warnf(format string, args ...interface{})
        Warnj(j log.JSON)
        Error(i ...interface{})
        Errorf(format string, args ...interface{})
        Errorj(j log.JSON)
        Fatal(i ...interface{})
        Fatalj(j log.JSON)
        Fatalf(format string, args ...interface{})
        Panic(i ...interface{})
        Panicj(j log.JSON)
        Panicf(format string, args ...interface{})
    }
)

一般的 log 也都实现了这些方法,所以我们可以使用自己的 log 包替换这个。而作者是使用的 github.com/labstack/gommon/log 这个包。

到这里,自定义 log 级别,输出位置都一目了然了。

默认的 log

在生成 echo.Echo 实例的时候,会初始化一个默认的 log。

// 初始化一个 Echo 实例
func New() (e *Echo) {
    e = &Echo{
        ...
        Logger:   log.New("echo"),
    }
    ...
    e.Logger.SetLevel(log.ERROR) // 默认日志级别
    ...
    return
}

// log.New() 方法是这样的
func New(prefix string) (l *Logger) {
    l = &Logger{
        level:    INFO,
        prefix:   prefix,
        template: l.newTemplate(defaultHeader),
        color:    color.New(), // 这个是让不同级别的日志在控制台显示不用颜色的。
        bufferPool: sync.Pool{
            New: func() interface{} {
                return bytes.NewBuffer(make([]byte, 256))
            },
        },
    }
    l.initLevels() // 同样是处理颜色
    l.SetOutput(output()) // 默认是 os.Stdout
    return
}

这里的 template 是 github.com/valyala/fasttemplate 包的对象,是一个简单的模版引擎,用来控制 log 的输出样式。

默认的样式是这样的

    defaultHeader = `{"time":"${time_rfc3339_nano}","level":"${level}","prefix":"${prefix}",` +
        `"file":"${short_file}","line":"${line}"}`

这里的很多配置都和官方 log 的配置类似,也显示了文件名和行号。这里默认支持的时间格式只有两种

time_rfc3339 // "2006-01-02T15:04:05Z07:00"
time_rfc3339_nano // "2006-01-02T15:04:05.999999999Z07:00"

需要更深层次的定制的话就需要修改或者替换 log 包了。

log 中间件

通过这样的方法来注册 log 中间件,这个中间件主要用来针对 http 请求打印日志。

// 使用默认的配置
e.Use(middleware.Logger())

// 自定义配置
// 自定义配置只支持 Format 和 Output 两个属性
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
  Format: "method=${method}, uri=${uri}, status=${status}\n",
  Output os.Stdout,
}))

默认的配置是这样的

    DefaultLoggerConfig = LoggerConfig{
        ...
        Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}","host":"${host}",` +
            `"method":"${method}","uri":"${uri}","status":${status}, "latency":${latency},` +
            `"latency_human":"${latency_human}","bytes_in":${bytes_in},` +
            `"bytes_out":${bytes_out}}` + "\n",
        Output:  os.Stdout,
        colorer: color.New(),
    }

自定义配置支持下面这些字段

- time_unix
- time_unix_nano
- time_rfc3339
- time_rfc3339_nano
// 时间上多了两个 unix 时间戳类型

- id (Request ID)
- remote_ip
- uri
- host
- method
- path
- referer
- user_agent
- status
// 常规的 http 请求内容

- latency (In nanoseconds) 
- latency_human (Human readable)
// 这个可以算作处理日志花的时间

- bytes_in (Bytes received)
- bytes_out (Bytes sent)
// request 请求和 response 响应的大小

- header:<NAME>
- query:<NAME>
- form:<NAME>
- cookie:<NAME>
// 这几个可以拿到具体内容,分别用下面的方法取得
// tag 就是上面的字段
// c.Request().Header.Get(tag[7:])
// c.QueryParam(tag[6:])
// c.FormValue(tag[5:])
// c.Cookie(tag[7:]

整个日志中间件在这里 https://github.com/labstack/echo/blob/master/middleware/logger.go ,不符合也可以根据需要重新实现一个。

后记

之前翻译了 echo 的中文文档 http://go-echo.org,发现文档有很多地方没有说清楚,就萌生了边看代码边补充使用文档的想法。拖了很久终于开工了,拿最简单的 log 开篇,后面会陆续更新关于 echo 其他模块的介绍。