后面两篇曾经为大家介绍了golang中的日志如何应用,并在诸多日志框架库中抉择了zap作为咱们的日志框架,本篇将会解说:
- 如何联合当下支流的Web框架gin进行申请日志的打印
- 对zap进行二次封装,注入trace信息,一遍咱们能够在业务中查问一次申请的所有残缺日志
1、gin 默认的中间件这里是前两篇的链接:
- https://www.yuque.com/jinsesi…
- https://www.yuque.com/jinsesi…
首先咱们来看一个最简略的 gin 我的项目:
func main() {
r := gin.Default()
r.GET("/hello", func(c *gin.Context) {
c.String("hello jianfan.com!")
})
r.Run(
}
gin.Default()
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
gin.Default()Logger()Recovery()
Logger()Recovery()
2、基于 zap 的中间件
Logger()Recovery()
// GinLogger 接管gin框架默认的日志
func GinLogger(logger *zap.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
query := c.Request.URL.RawQuery
c.Next()
cost := time.Since(start)
logger.Info(path,
zap.Int("status", c.Writer.Status()),
zap.String("method", c.Request.Method),
zap.String("path", path),
zap.String("query", query),
zap.String("ip", c.ClientIP()),
zap.String("user-agent", c.Request.UserAgent()),
zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
zap.Duration("cost", cost),
)
}
}
// GinRecovery recover掉我的项目可能呈现的panic
func GinRecovery(logger *zap.Logger, stack bool) gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
// Check for a broken connection, as it is not really a
// condition that warrants a panic stack trace.
var brokenPipe bool
if ne, ok := err.(*net.OpError); ok {
if se, ok := ne.Err.(*os.SyscallError); ok {
if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
brokenPipe = true
}
}
}
httpRequest, _ := httputil.DumpRequest(c.Request, false)
if brokenPipe {
logger.Error(c.Request.URL.Path,
zap.Any("error", err),
zap.String("request", string(httpRequest)),
)
// If the connection is dead, we can't write a status to it.
c.Error(err.(error)) // nolint: errcheck
c.Abort()
return
}
if stack {
logger.Error("[Recovery from panic]",
zap.Any("error", err),
zap.String("request", string(httpRequest)),
zap.String("stack", string(debug.Stack())),
)
} else {
logger.Error("[Recovery from panic]",
zap.Any("error", err),
zap.String("request", string(httpRequest)),
)
}
c.AbortWithStatus(http.StatusInternalServerError)
}
}()
c.Next()
}
}
如果不想本人实现,能够应用 github 上有他人封装好的 https://github.com/gin-contrib/zap。
Logger()Recovery()
r := gin.New()
r.Use(GinLogger(), GinRecovery())
3、减少Trace信息
3.1 定义trace信息
// 定义trace构造体
type Trace struct {
TraceId string `json:"trace_id"`
SpanId string `json:"span_id"`
Caller string `json:"caller"`
SrcMethod *string `json:"srcMethod,omitempty"`
UserId int `json:"user_id"`
}
// 依据gin的context获取context,使log trace更加通用
func GetTraceCtx(c *gin.Context) context.Context {
return c.MustGet(consts.TraceCtx).(context.Context)
}
3.2 包装基于zap的日志工具
package log
import (
"best-practics/common"
"best-practics/common/consts"
"context"
"go.uber.org/zap"
)
type LogWrapper struct {
logger *zap.Logger
}
var Log LogWrapper
func Debug(tag string, fields ...zap.Field) {
Log.logger.Debug(tag, fields...)
}
func DebugF(ctx context.Context, tag string, fields ...zap.Field) {
trace := ctx.Value(consts.TraceKey).(*common.Trace)
Log.logger.Debug(tag,
append(fields, zap.String("trace_id", trace.TraceId), zap.Int("user_id", trace.UserId))...,
)
}
func Info(tag string, fields ...zap.Field) {
Log.logger.Info(tag, fields...)
}
func InfoF(ctx context.Context, tag string, fields ...zap.Field) {
trace := ctx.Value(consts.TraceKey).(*common.Trace)
Log.logger.Info(tag,
append(fields, zap.String("trace_id", trace.TraceId), zap.Int("user_id", trace.UserId))...,
)
}
func Warn(tag string, fields ...zap.Field) {
Log.logger.Warn(tag, fields...)
}
func WarnF(ctx context.Context, tag string, fields ...zap.Field) {
trace := ctx.Value(consts.TraceKey).(*common.Trace)
Log.logger.Warn(tag,
append(fields, zap.String("trace_id", trace.TraceId), zap.Int("user_id", trace.UserId))...,
)
}
func Error(tag string, fields ...zap.Field) {
Log.logger.Error(tag, fields...)
}
func ErrorF(ctx context.Context, tag string, fields ...zap.Field) {
trace := ctx.Value(consts.TraceKey).(*common.Trace)
Log.logger.Error(tag,
append(fields, zap.String("trace_id", trace.TraceId), zap.Int("user_id", trace.UserId))...,
)
}
func Fatal(tag string, fields ...zap.Field) {
Log.logger.Fatal(tag, fields...)
}
func FatalF(ctx context.Context, tag string, fields ...zap.Field) {
trace := ctx.Value(consts.TraceKey).(*common.Trace)
Log.logger.Fatal(tag,
append(fields, zap.String("trace_id", trace.TraceId), zap.Int("user_id", trace.UserId))...,
)
}
logger = logger.WithOptions(zap.AddCaller(),zap.AddCallerSkip(1))
/*
* Copyright (C) 2021 Baidu, Inc. All Rights Reserved.
*/
package log
import (
"best-practics/common"
"best-practics/utils"
"fmt"
"os"
"time"
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func InitZap() {
if ok, _ := utils.PathExists(common.GlobalConfig.Zap.Director); !ok { // 判断是否有Director文件夹
fmt.Printf("create %v directory\n", common.GlobalConfig.Zap.Director)
_ = os.Mkdir(common.GlobalConfig.Zap.Director, os.ModePerm)
}
// 调试级别
debugPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool {
return lev == zap.DebugLevel
})
// 日志级别
infoPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool {
return lev == zap.InfoLevel
})
// 正告级别
warnPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool {
return lev == zap.WarnLevel
})
// 谬误级别
errorPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool {
return lev >= zap.ErrorLevel
})
cores := [...]zapcore.Core{
getEncoderCore(fmt.Sprintf("./%s/server_debug.log", common.GlobalConfig.Zap.Director), debugPriority),
getEncoderCore(fmt.Sprintf("./%s/server_info.log", common.GlobalConfig.Zap.Director), infoPriority),
getEncoderCore(fmt.Sprintf("./%s/server_warn.log", common.GlobalConfig.Zap.Director), warnPriority),
getEncoderCore(fmt.Sprintf("./%s/server_error.log", common.GlobalConfig.Zap.Director), errorPriority),
}
logger := zap.New(zapcore.NewTee(cores[:]...), zap.AddCaller(), zap.AddCallerSkip(1))
common.Logger = logger
}
// getEncoderConfig 获取zapcore.EncoderConfig
func getEncoderConfig() (config zapcore.EncoderConfig) {
config = zapcore.EncoderConfig{
MessageKey: "message",
LevelKey: "level",
TimeKey: "time",
NameKey: "logger",
CallerKey: "caller",
StacktraceKey: common.GlobalConfig.Zap.StacktraceKey,
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: CustomTimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.FullCallerEncoder,
}
switch {
case common.GlobalConfig.Zap.EncodeLevel == "LowercaseLevelEncoder": // 小写编码器(默认)
config.EncodeLevel = zapcore.LowercaseLevelEncoder
case common.GlobalConfig.Zap.EncodeLevel == "LowercaseColorLevelEncoder": // 小写编码器带色彩
config.EncodeLevel = zapcore.LowercaseColorLevelEncoder
case common.GlobalConfig.Zap.EncodeLevel == "CapitalLevelEncoder": // 大写编码器
config.EncodeLevel = zapcore.CapitalLevelEncoder
case common.GlobalConfig.Zap.EncodeLevel == "CapitalColorLevelEncoder": // 大写编码器带色彩
config.EncodeLevel = zapcore.CapitalColorLevelEncoder
default:
config.EncodeLevel = zapcore.LowercaseLevelEncoder
}
return config
}
// getEncoder 获取zapcore.Encoder
func getEncoder() zapcore.Encoder {
if common.GlobalConfig.Zap.Format == "json" {
return zapcore.NewJSONEncoder(getEncoderConfig())
}
return zapcore.NewConsoleEncoder(getEncoderConfig())
}
// getEncoderCore 获取Encoder的zapcore.Core
func getEncoderCore(fileName string, level zapcore.LevelEnabler) (core zapcore.Core) {
writer := GetWriteSyncer(fileName) // 应用file-rotatelogs进行日志宰割
return zapcore.NewCore(getEncoder(), writer, level)
}
// 自定义日志输入工夫格局
func CustomTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.Format("2006/01/02 15:04:05"))
}
//@function: GetWriteSyncer
//@description: zap logger中退出file-rotatelogs
//@return: zapcore.WriteSyncer, error
func GetWriteSyncer(file string) zapcore.WriteSyncer {
// 每小时一个文件
logf, _ := rotatelogs.New(file +".%Y%m%d%H",
rotatelogs.WithLinkName(file),
rotatelogs.WithMaxAge(7*24*time.Hour),
rotatelogs.WithRotationTime(time.Minute),
)
if common.GlobalConfig.Zap.LogInConsole {
return zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(logf))
}
return zapcore.AddSync(logf)
}
func SetLoggerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
uuidStr := strings.ReplaceAll(uuid.New().String(), "-", "")
path := c.Request.URL.Path
userId := c.GetInt("user_id")
ctx := context.WithValue(context.Background(), consts.TraceKey, &common.Trace{TraceId: uuidStr, Caller: path, UserId: userId})
c.Set(consts.TraceCtx,ctx)
c.Next()
cost := time.Since(start)
common.Logger.Info("_com_request_info",
zap.Int("Status", c.Writer.Status()),
zap.String("Method", c.Request.Method),
zap.String("IP",c.ClientIP()),
zap.String("Path",path),
zap.String("TraceId", uuidStr),
zap.Int("UserId", userId),
zap.String("query", c.Request.URL.RawQuery),
zap.String("UserAgent",c.Request.UserAgent()),
zap.Duration("Cost",cost),
)
}
}
func main() {
r := gin.Default()
r.GET("/hello", func(c *gin.Context) {
// 3.1中封装的转换方法
ctx := common.GetTraceCtx(c)
log.WarnF(ctx,"BookNotFoundError",zap.String("test","666"))
c.String("hello jianfan.com!")
})
r.Run(
}
至此咱们就实现了在一个gin我的项目中接入zap作为日志框架,并可能打印残缺的链路日志信息。联合前两节咱们曾经满足了对常见日志框架的需要:
- 良好日志写入性能
- 反对不同的日志级别。并且可拆散成多个日志文件
- 多输入 - 同时反对规范输入,文件等
- 可能打印根本信息,如调用文件 / 函数名和行号,日志工夫等
- 可读性与结构化,Json格局或有分隔符,不便后续的日志采集、监控等
- 文件切割,可按小时、天进行日志拆分,或者按文件大小
- 日志书写敌对,反对通过context主动log trace等
- 文件定时删除
- 开源性,与其余开源框架反对较好
参考资料:
- https://liwenzhou.com/posts/Go/use_zap_in_gin/
咱们下期见,Peace😘
我是简凡,一个励志用最简略的语言,形容最简单问题的新时代农民工。求点赞,求关注,如果你对此篇文章有什么纳闷,欢送在我的微信公众号中留言,我还能够为你提供以下帮忙:
- 帮忙建设本人的常识体系
- 互联网实在高并发场景实战解说
- 不定期分享Golang、Java相干业内的经典场景实际