golang iris 学习五: zap日志

之前试用了一下logrus发现并不是很适合,今天来试用一下排行榜第2位的zap:

这波我们用Zap实现以下功能

  1. 将Error级别以下的信息写入Info文件
  2. 将Error级别以上的信息写入Error文件
  3. 日志按天分割
  4. 屏幕输出日志

安装

1
go get -u go.uber.org/zap

简单配置

Zap提供了两种类型的日志记录器—Sugared LoggerLogger

在性能很好但不是很关键的上下文中,使用SugaredLogger。它比其他结构化日志记录包快4-10倍,并且支持结构化和printf风格的日志记录。

在每一微秒和每一次内存分配都很重要的上下文中,使用Logger。它甚至比SugaredLogger更快,内存分配次数也更少,但它只支持强类型的结构化日志记录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var zaplog *zap.SugaredLogger

func main() {
InitLogger()
defer zaplog.Sync()
zaplog.Infof("这是一条Info日志!")
zaplog.Errorf("这是一条Error日志!")
}

func InitLogger() {
logger, _ := zap.NewProduction()
zaplog = logger.Sugar()

}
  • 调用zap.NewProduction()/zap.NewDevelopment()或者zap.Example()创建一个Logger。
  • 调用主logger的. Sugar()方法来获取一个SugaredLogger

开始定制

写入文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var zaplog *zap.SugaredLogger

func main() {
InitLogger()
defer zaplog.Sync()
zaplog.Infof("这是一条Info日志!")
zaplog.Errorf("这是一条Error日志!")
}

func InitLogger() {
fileWriter := getLogWriter()
encoder := getEncoder()
core := zapcore.NewCore(encoder, zapcore.AddSync(fileWriter), zapcore.DebugLevel)

logger := zap.New(core)
zaplog = logger.Sugar()

}

func getEncoder() zapcore.Encoder {
return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
}

func getLogWriter() io.Writer {
file, _ := os.Create("./test.log")
return file
}
  • Encoder:编码器(如何写入日志)。我们将使用开箱即用的NewJSONEncoder(),并使用预先设置的ProductionEncoderConfig()
  • WriterSyncer :指定日志将写到哪里去。我们使用zapcore.AddSync()函数并且将打开的文件句柄传进去。
  • Log Level:哪种级别的日志将被写入。

将Json格式改为普通文本

1
2
3
func getEncoder() zapcore.Encoder {
return zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
}

修改时间格式并把级别字段添加颜色

1
2
3
4
5
6
func getEncoder() zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
return zapcore.NewConsoleEncoder(encoderConfig)
}

自定义时间格式

使用zapcore.ISO8601TimeEncoder格式,还是不是很友好,我想自定义一下,先看看zapcore.ISO8601TimeEncoder这个函数定义

1
2
3
func ISO8601TimeEncoder(t time.Time, enc PrimitiveArrayEncoder) {
enc.AppendString(t.Format("2006-01-02T15:04:05.000Z0700"))
}

噢,很简单,我来抄一个

1
2
3
4
5
6
7
8
9
10
11
func customTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.Format("[2006-01-02 15:04:05]"))
}


func getEncoder() zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = customTimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
return zapcore.NewConsoleEncoder(encoderConfig)
}

添加调用者信息

添加打印位置,方便调试

1
2
3
4
5
6
7
8
9
func InitLogger() {
fileWriter := getLogWriter()
encoder := getEncoder()
core := zapcore.NewCore(encoder, zapcore.AddSync(fileWriter), zapcore.DebugLevel)

logger := zap.New(core, zap.AddCaller())
zaplog = logger.Sugar()

}

日志切割

zap本身不带这个功能,我们需要另外的库:

我们使用file-rotatelogs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func InitLogger() {
fileWriter := getLogWriter("./", "test", 7)
encoder := getEncoder()
core := zapcore.NewCore(encoder, zapcore.AddSync(fileWriter), zapcore.DebugLevel)

logger := zap.New(core, zap.AddCaller())
zaplog = logger.Sugar()

}

func getLogWriter(logPath, level string, save uint) io.Writer {
logFullPath := path.Join(logPath, level)
hook, err := rotatelogs.New(
logFullPath+".%Y%m%d%H", // 没有使用go风格反人类的format格式
rotatelogs.WithLinkName(logFullPath), // 生成软链,指向最新日志文件
rotatelogs.WithRotationCount(save), // 文件最大保存份数
rotatelogs.WithRotationTime(24*time.Hour), // 日志切割时间间隔
)
if err != nil {
panic(err)
}
return hook
}

按级别分文件打印

  • 使用zapcore.NewTee初始化多个zapcore.NewCore
  • 使用zap.LevelEnablerFunc过滤写入条件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func InitLogger() {
encoder := getEncoder()

infoWrite := getLogWriter("./", "info", 7)
errorWrite := getLogWriter("./", "error", 7)

infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl < zapcore.ErrorLevel
})
warnLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= zapcore.ErrorLevel
})

core := zapcore.NewTee(
zapcore.NewCore(encoder, zapcore.AddSync(infoWrite), infoLevel),
zapcore.NewCore(encoder, zapcore.AddSync(errorWrite), warnLevel),
)

logger := zap.New(core, zap.AddCaller())
zaplog = logger.Sugar()

}

增加屏幕输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func InitLogger() {
encoder := getEncoder()

infoWrite := getLogWriter("./", "info", 7)
errorWrite := getLogWriter("./", "error", 7)

infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl < zapcore.ErrorLevel
})
warnLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= zapcore.ErrorLevel
})

core := zapcore.NewTee(
zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), zapcore.DebugLevel),
zapcore.NewCore(encoder, zapcore.AddSync(infoWrite), infoLevel),
zapcore.NewCore(encoder, zapcore.AddSync(errorWrite), warnLevel),
)

logger := zap.New(core, zap.AddCaller())
zaplog = logger.Sugar()

}

完整测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
var zaplog *zap.SugaredLogger

func main() {
InitLogger()
defer zaplog.Sync()
zaplog.Infof("这是一条Info日志!")
zaplog.Errorf("这是一条Error日志!")
}

func InitLogger() {
encoder := getEncoder()

infoWrite := getLogWriter("./", "info", 7)
errorWrite := getLogWriter("./", "error", 7)

infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl < zapcore.ErrorLevel
})
warnLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= zapcore.ErrorLevel
})

core := zapcore.NewTee(
zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), zapcore.DebugLevel),
zapcore.NewCore(encoder, zapcore.AddSync(infoWrite), infoLevel),
zapcore.NewCore(encoder, zapcore.AddSync(errorWrite), warnLevel),
)

logger := zap.New(core, zap.AddCaller())
zaplog = logger.Sugar()

}

func customTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.Format("[2006-01-02 15:04:05]"))
}

func getEncoder() zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = customTimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
return zapcore.NewConsoleEncoder(encoderConfig)
}

func getLogWriter(logPath, level string, save uint) io.Writer {
logFullPath := path.Join(logPath, level)
hook, err := rotatelogs.New(
logFullPath+".%Y%m%d%H", // 没有使用go风格反人类的format格式
rotatelogs.WithLinkName(logFullPath), // 生成软链,指向最新日志文件
rotatelogs.WithRotationCount(save), // 文件最大保存份数
rotatelogs.WithRotationTime(24*time.Hour), // 日志切割时间间隔
)
if err != nil {
panic(err)
}
return hook
}

结合Iris

先初始化一个Logger出来

config/config.go 定义一个log配置的结构体

1
2
3
4
5
type LogConfig struct {
Level string `yaml:"level"`
Path string `yaml:"path"`
Save uint `yaml:"save"`
}

config/logger.go` 根据配置的日志级别,日志路径,日志保留天数初始化一个Logger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package config

import (
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"io"
"os"
"path"
"time"
)

var Log *zap.SugaredLogger

func InitLogger(logConfig LogConfig) {
encoder := getEncoder()

infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl < zapcore.ErrorLevel
})
infoWriter := getLogWriter(logConfig.Path, "Info", logConfig.Save)

errorLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= zapcore.ErrorLevel
})
errorWriter := getLogWriter(logConfig.Path, "Error", logConfig.Save)

core := zapcore.NewTee(
zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), zap.DebugLevel),
zapcore.NewCore(encoder, zapcore.AddSync(infoWriter), infoLevel),
zapcore.NewCore(encoder, zapcore.AddSync(errorWriter), errorLevel),
)

logger := zap.New(core, zap.AddCaller())
Log = logger.Sugar()
}

func customTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.Format("[2006-01-02 15:04:05]"))
}

func getEncoder() zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = customTimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
return zapcore.NewConsoleEncoder(encoderConfig)
}

func getLogWriter(logPath, level string, save uint) io.Writer {
logFullPath := path.Join(logPath, level)
hook, err := rotatelogs.New(
logFullPath+".%Y%m%d%H", // 没有使用go风格反人类的format格式
rotatelogs.WithLinkName(logFullPath), // 生成软链,指向最新日志文件
rotatelogs.WithRotationCount(save), // 文件最大保存份数
rotatelogs.WithRotationTime(24*time.Hour), // 日志切割时间间隔
)
if err != nil {
panic(err)
}
return hook
}

再撸一个中间件打印请求记录middleware/logger_middleware.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package middleware

import (
"bytes"
"fmt"
"github.com/dgrijalva/jwt-go"
"github.com/kataras/iris/v12"
"goms/config"
"io/ioutil"
"net/http"
"path"
"time"
)

func LoggerHandler(ctx iris.Context) {
p := ctx.Request().URL.Path
method := ctx.Request().Method
start := time.Now()
fields := make(map[string]interface{})
fields["title"] = "访问日志"
fields["fun_name"] = path.Join(method, p)
fields["ip"] = ctx.Request().RemoteAddr
fields["method"] = method
fields["url"] = ctx.Request().URL.String()
fields["proto"] = ctx.Request().Proto
//fields["header"] = ctx.Request().Header
fields["user_agent"] = ctx.Request().UserAgent()
fields["x_request_id"] = ctx.GetHeader("X-Request-Id")

// 如果是POST/PUT请求,并且内容类型为JSON,则读取内容体
if method == http.MethodPost || method == http.MethodPut || method == http.MethodPatch {
body, err := ioutil.ReadAll(ctx.Request().Body)
if err == nil {
defer ctx.Request().Body.Close()
buf := bytes.NewBuffer(body)
ctx.Request().Body = ioutil.NopCloser(buf)
fields["content_length"] = ctx.GetContentLength()
fields["body"] = string(body)
}
}
ctx.Next()

//下面是返回日志
fields["res_status"] = ctx.ResponseWriter().StatusCode()
if ctx.Values().GetString("out_err") != "" {
fields["out_err"] = ctx.Values().GetString("out_err")
}
fields["res_length"] = ctx.ResponseWriter().Header().Get("size")
if v := ctx.Values().Get("res_body"); v != nil {
if b, ok := v.([]byte); ok {
fields["res_body"] = string(b)
}
}
token := ctx.Values().Get("jwt")
if token != nil {
fields["uid"] = token.(*jwt.Token).Claims
}
timeConsuming := time.Since(start).Nanoseconds() / 1e6
msg := fmt.Sprintf("[http] %s-%s-%s-%d(%dms)",
p, ctx.Request().Method, ctx.Request().RemoteAddr, ctx.ResponseWriter().StatusCode(), timeConsuming)
config.Log.Debug(fields)
config.Log.Infof(msg)
}

加进路由route/route.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package route

import (
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
"goms/controllers"
"goms/middleware"
)

func InitRoute(app *iris.Application) {
app.Use(middleware.LoggerHandler)

mvc.Configure(app.Party("/account"), func(m *mvc.Application) {
m.Handle(controllers.NewLoginController())
})

......
}

好了,就用这个Zap吧