前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >go-kit 微服务 整合zap日志库

go-kit 微服务 整合zap日志库

原创
作者头像
Johns
修改2022-06-30 10:32:13
2.1K0
修改2022-06-30 10:32:13
举报
文章被收录于专栏:代码工具代码工具

简介

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倍了。

这次先写一篇使用篇,后期会再出一篇高性能的原理分析。

zap日志库整合

上一节我们使用go-kit工具包构建了简单的一个整数乘法计算服务。如果不记得了可以回顾一下上一节内容: go-kit 构建微服务(1)

之前所有的输出都是用的控制台输出, 实际生产环境基本都是会将log写日志文件的。日志在大部分场景是以一个公共的组件提供出来的,所以我这边将zap 日志库和lumberjack(日志切割组件)整合到了一个 pkg/log目录下, 所有的logger初始化以及获取都是由这个包提供。 当然如果有定制话需求的话也仅需要调用包里面封装好的NewLogger方法实例化便可。包下面只有两个两个文件log_def.go和log_instance.log

  • log_def.go : 封装zap和lumberjack,定义对外的配置项
代码语言:txt
复制
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)
}
  • log_instance.go : 实例化logger对象, 并提供外部访问入口, 实际上就是配置如下选项
代码语言:txt
复制
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

代码语言:txt
复制
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
}

项目使用

  • log初始化
代码语言:txt
复制
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()
}
  • logger使用

下面开始在 transport里面使用log, 使用的时候只需使用log.GetLogger()就可以获得默认的一个logger对象进行log输出

代码语言:txt
复制
	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)
  • 结果
代码语言:txt
复制
{"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 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
  • zap日志库整合
  • 项目使用
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档