zap是Uber开源的一款高性能的日志库,和seelog,logrus相比,高性能是它的突出优势, 已经在业界广泛使用,下面拿官方的性能测试对比原图来说明
10个字段的log输出性能测试对比:
Package | Time | Time % to zap | Objects Allocated |
---|---|---|---|
zap | 862 ns/op | +0% | 5 allocs/op |
zap (sugared) | 1250 ns/op | +45% | 11 allocs/op |
zerolog | 4021 ns/op | +366% | 76 allocs/op |
go-kit | 4542 ns/op | +427% | 105 allocs/op |
apex/log | 26785 ns/op | +3007% | 115 allocs/op |
logrus | 29501 ns/op | +3322% | 125 allocs/op |
log15 | 29906 ns/op | +3369% | 122 allocs/op |
可以看出,相比其他日志库,zap日志库的性能更为突出,几乎比logrus快了3倍,比go-kit原生的log库快了差不多要6倍了。
这次先写一篇使用篇,后期会再出一篇高性能的原理分析。
上一节我们使用go-kit工具包构建了简单的一个整数乘法计算服务。如果不记得了可以回顾一下上一节内容: go-kit 构建微服务(1)
之前所有的输出都是用的控制台输出, 实际生产环境基本都是会将log写日志文件的。日志在大部分场景是以一个公共的组件提供出来的,所以我这边将zap 日志库和lumberjack(日志切割组件)整合到了一个 pkg/log目录下, 所有的logger初始化以及获取都是由这个包提供。 当然如果有定制话需求的话也仅需要调用包里面封装好的NewLogger方法实例化便可。包下面只有两个两个文件log_def.go和log_instance.log
package log
import (
"github.com/natefinch/lumberjack"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
"path/filepath"
"sync"
"time"
)
type Options struct {
LogFileDir string //文件保存地方
AppName string //日志文件前缀
ErrorFileName string
WarnFileName string
InfoFileName string
DebugFileName string
Level zapcore.Level //日志等级
MaxSize int //日志文件小大(M)
MaxBackups int // 最多存在多少个切片文件
MaxAge int //保存的最大天数
Development bool //是否是开发模式
zap.Config
}
type ModOptions func(options *Options)
var (
l *Logger
sp = string(filepath.Separator)
errWS, warnWS, infoWS, debugWS zapcore.WriteSyncer // IO输出
debugConsoleWS = zapcore.Lock(os.Stdout) // 控制台标准输出
errorConsoleWS = zapcore.Lock(os.Stderr)
)
type Logger struct {
*zap.Logger
sync.RWMutex
Opts *Options `json:"opts"`
zapConfig zap.Config
initialized bool
}
func NewLogger(mod ...ModOptions) *zap.Logger {
l = &Logger{}
l.Lock()
defer l.Unlock()
if l.initialized {
l.Info("[NewLogger] logger initEd")
return nil
}
l.Opts = &Options{
LogFileDir: "",
AppName: "app",
ErrorFileName: "error.log",
WarnFileName: "warn.log",
InfoFileName: "info.log",
DebugFileName: "debug.log",
Level: zapcore.DebugLevel,
MaxSize: 100,
MaxBackups: 60,
MaxAge: 30,
}
if l.Opts.LogFileDir == "" {
l.Opts.LogFileDir, _ = filepath.Abs(filepath.Dir(filepath.Join(".")))
l.Opts.LogFileDir += sp + "logs" + sp
}
if l.Opts.Development {
l.zapConfig = zap.NewDevelopmentConfig()
l.zapConfig.EncoderConfig.EncodeTime = timeEncoder
} else {
l.zapConfig = zap.NewProductionConfig()
l.zapConfig.EncoderConfig.EncodeTime = timeUnixNano
}
if l.Opts.OutputPaths == nil || len(l.Opts.OutputPaths) == 0 {
l.zapConfig.OutputPaths = []string{"stdout"}
}
if l.Opts.ErrorOutputPaths == nil || len(l.Opts.ErrorOutputPaths) == 0 {
l.zapConfig.OutputPaths = []string{"stderr"}
}
for _, fn := range mod {
fn(l.Opts)
}
l.zapConfig.Level.SetLevel(l.Opts.Level)
l.init()
l.initialized = true
return l.Logger
}
func (l *Logger) init() {
l.setSyncs()
var err error
l.Logger, err = l.zapConfig.Build(l.cores())
if err != nil {
panic(err)
}
defer l.Logger.Sync()
}
func (l *Logger) setSyncs() {
f := func(fN string) zapcore.WriteSyncer {
return zapcore.AddSync(&lumberjack.Logger{
Filename: l.Opts.LogFileDir + sp + l.Opts.AppName + "-" + fN,
MaxSize: l.Opts.MaxSize,
MaxBackups: l.Opts.MaxBackups,
MaxAge: l.Opts.MaxAge,
Compress: true,
LocalTime: true,
})
}
errWS = f(l.Opts.ErrorFileName)
warnWS = f(l.Opts.WarnFileName)
infoWS = f(l.Opts.InfoFileName)
debugWS = f(l.Opts.DebugFileName)
return
}
func SetMaxSize(MaxSize int) ModOptions {
return func(option *Options) {
option.MaxSize = MaxSize
}
}
func SetMaxBackups(MaxBackups int) ModOptions {
return func(option *Options) {
option.MaxBackups = MaxBackups
}
}
func SetMaxAge(MaxAge int) ModOptions {
return func(option *Options) {
option.MaxAge = MaxAge
}
}
func SetLogFileDir(LogFileDir string) ModOptions {
return func(option *Options) {
option.LogFileDir = LogFileDir
}
}
func SetAppName(AppName string) ModOptions {
return func(option *Options) {
option.AppName = AppName
}
}
func SetLevel(Level zapcore.Level) ModOptions {
return func(option *Options) {
option.Level = Level
}
}
func SetErrorFileName(ErrorFileName string) ModOptions {
return func(option *Options) {
option.ErrorFileName = ErrorFileName
}
}
func SetWarnFileName(WarnFileName string) ModOptions {
return func(option *Options) {
option.WarnFileName = WarnFileName
}
}
func SetInfoFileName(InfoFileName string) ModOptions {
return func(option *Options) {
option.InfoFileName = InfoFileName
}
}
func SetDebugFileName(DebugFileName string) ModOptions {
return func(option *Options) {
option.DebugFileName = DebugFileName
}
}
func SetDevelopment(Development bool) ModOptions {
return func(option *Options) {
option.Development = Development
}
}
func (l *Logger) cores() zap.Option {
fileEncoder := zapcore.NewJSONEncoder(l.zapConfig.EncoderConfig)
encoderConfig := zap.NewDevelopmentEncoderConfig()
encoderConfig.EncodeTime = timeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
consoleEncoder := zapcore.NewConsoleEncoder(encoderConfig)
errPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl == zapcore.ErrorLevel && zapcore.ErrorLevel-l.zapConfig.Level.Level() > -1
})
warnPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl == zapcore.WarnLevel && zapcore.WarnLevel-l.zapConfig.Level.Level() > -1
})
infoPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl == zapcore.InfoLevel && zapcore.InfoLevel-l.zapConfig.Level.Level() > -1
})
debugPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl == zapcore.DebugLevel && zapcore.DebugLevel-l.zapConfig.Level.Level() > -1
})
cores := []zapcore.Core{
zapcore.NewCore(fileEncoder, errWS, errPriority),
zapcore.NewCore(fileEncoder, warnWS, warnPriority),
zapcore.NewCore(fileEncoder, infoWS, infoPriority),
zapcore.NewCore(fileEncoder, debugWS, debugPriority),
}
if l.Opts.Development {
cores = append(cores, []zapcore.Core{
zapcore.NewCore(consoleEncoder, errorConsoleWS, errPriority),
zapcore.NewCore(consoleEncoder, debugConsoleWS, warnPriority),
zapcore.NewCore(consoleEncoder, debugConsoleWS, infoPriority),
zapcore.NewCore(consoleEncoder, debugConsoleWS, debugPriority),
}...)
}
return zap.WrapCore(func(c zapcore.Core) zapcore.Core {
return zapcore.NewTee(cores...)
})
}
func timeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.Format("2006-01-02 15:04:05"))
}
func timeUnixNano(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendInt64(t.UnixNano() / 1e6)
}
type Options struct {
LogFileDir string //文件保存地方
AppName string //日志文件前缀
ErrorFileName string //error日志文件后缀
WarnFileName string //warn日志文件后缀
InfoFileName string //info日志文件后缀
DebugFileName string //debug日志文件后缀
Level zapcore.Level //日志等级
MaxSize int //日志文件小大(M)
MaxBackups int // 最多存在多少个切片文件
MaxAge int //保存的最大天数
Development bool //是否是开发模式
zap.Config
}
其中如果Development开发模式,则会将log也打印到控制台,生产环境建议设置为false
package log
import (
"fmt"
"github.com/spf13/viper"
"go.uber.org/zap"
)
var logger *zap.Logger
// log instance init
func InitLog() {
level := viper.GetString("log.level")
logLevel := zap.DebugLevel
if "debug" == level {
logLevel = zap.DebugLevel
}
if "info" == level {
logLevel = zap.InfoLevel
}
if "error" == level {
logLevel = zap.ErrorLevel
}
if "warn" == level {
logLevel = zap.WarnLevel
}
fmt.Println(logLevel)
logger = NewLogger(
SetAppName(viper.GetString("log.appName")),
SetDevelopment(viper.GetBool("log.development")),
SetDebugFileName(viper.GetString("log.debugFileName")),
SetErrorFileName(viper.GetString("log.errorFileName")),
SetInfoFileName(viper.GetString("log.infoFileName")),
SetMaxAge(viper.GetInt("log.maxAge")),
SetMaxBackups(viper.GetInt("log.maxBackups")),
SetMaxSize(viper.GetInt("log.maxSize")),
SetLevel(zap.DebugLevel),
)
}
func GetLogger() *zap.Logger {
return logger
}
package main
import (
"fmt"
"github.com/oklog/oklog/pkg/group"
"github.com/spf13/viper"
"go-kit-microservice/internal/endpoint"
"go-kit-microservice/internal/pkg/cfg"
"go-kit-microservice/internal/pkg/log"
"go-kit-microservice/internal/service"
"go-kit-microservice/internal/transport"
"net"
"net/http"
)
func main() {
// init yaml config
cfg.InitYmlConfig()
// init logger
log.InitLog()
svc := service.NewService()
endpoints := endpoint.NewEndpoints(svc)
httpHandler := transport.MewHttpHandler(endpoints)
var g group.Group
{
httpListener, err := net.Listen("tcp", ":"+viper.GetString("server.port"))
if err != nil {
fmt.Println("http server start failed:" + err.Error())
}
g.Add(func() error {
return http.Serve(httpListener, httpHandler)
}, func(e error) {
err = httpListener.Close()
if err != nil {
fmt.Println("http server close failed", err.Error())
}
})
}
log.GetLogger().Info("http start success, port " + viper.GetString("server.port"))
_ = g.Run()
}
下面开始在 transport里面使用log, 使用的时候只需使用log.GetLogger()就可以获得默认的一个logger对象进行log输出
reqId := ctx.Value(utils.BaseRequestId).(string)
msg := fmt.Sprintf("%s\t%s\t%s\treqId=%s\t%s\t%s\t",
utils.RemoteIp(r),
r.Method,
r.RequestURI,
reqId,
r.Proto,
r.Header.Get("User-Agent"))
log.GetLogger().Info("decode request:" + msg)
{"level":"info","ts":1614919778628,"caller":"cmd/main.go:46","msg":"http start success, port 10086"}
{"level":"info","ts":1614919782856,"caller":"transport/transport.go:55","msg":"decode request:127.0.0.1\tGET\t/multiply?a=6&b=2\treqId=31f082f3-ccb3-5aab-8aed-6897d2b56dcd\tHTTP/1.1\tPostmanRuntime/7.26.8\t"}
{"level":"info","ts":1614919782856,"caller":"endpoint/endpoint.go:26","msg":"endpoint layer: reqId=31f082f3-ccb3-5aab-8aed-6897d2b56dcd"}
{"level":"info","ts":1614919782856,"caller":"service/service.go:35","msg":"service layer: do Multiply handler, reqId=31f082f3-ccb3-5aab-8aed-6897d2b56dcd"}
可以看出log正常打印,且是以一个结构体方式打印的,这也是zap高性能的原因,用结构体代替反射,节约了对象分配和回收的时间。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。