侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

在go语言中logger用slog替换zap并实现callerSkip

Go  /  管理员 发布于 1年前   523

在go项目中已经用slog替换zap后的logger使用方法,与实现无感知替换方案

代码示例:

package main
import "github.com/webws/go-moda/logger"
func main() {
    // 格式化打印 {"time":"2023-09-08T01:25:21.313463+08:00","level":"INFO","msg":"info hello slog","key":"value","file":"/Users/xxx/w/pro/go-moda/example/logger/main.go","line":6}
    logger.Infow("info hello slog", "key", "value")   // 打印json
    logger.Debugw("debug hello slog", "key", "value") // 不展示
    logger.SetLevel(logger.DebugLevel)                // 设置等级
    logger.Debugw("debug hello slog", "key", "value") // 设置了等级之后展示 debug
    // with
    newLog := logger.With("newkey", "newValue")
    newLog.Debugw("new hello slog") // 会打印 newkey:newValue
    logger.Debugw("old hello slog") // 不会打印 newkey:newValue
}

slog 基础使用

Go 1.21 版本中 将 golang.org/x/exp/slog 引入了 go 标准库 路径为 log/slog。

新项目的 如果不使用第三方包,可以直接用 slog 当你的 logger


slog 简单示例:

默认 输出级别是 info 以上,所以 debug 是打印不出来.

import "log/slog"
func main() {
    slog.Info("finished", "key", "value")
    slog.Debug("finished", "key", "value")
}

输出

2023/09/08 00:27:24 INFO finished key=value

slog 格式化

HandlerOptions Level: 设置日志等级 AddSource: 打印文件相关信息

func main() {
    opts := &slog.HandlerOptions{AddSource: true, Level: slog.LevelInfo}
    logger := slog.New(slog.NewJSONHandler(os.Stdout, opts))
    logger.Info("finished", "key", "value")
}

输出

{"time":"2023-09-08T00:34:22.035962+08:00","level":"INFO","source":{"function":"callvis/slog.TestLogJsonHandler","file":"/Users/websong/w/pro/go-note/slog/main_test.go","line":39},"msg":"finished","key":"value"}

slog 切换日志等级

看 slog 源码 HandlerOptions 的 Level 是一个 interface,slog 自带的 slog.LevelVar 实现了这个 interface, 也可以自己定义实现 下面是部分源码

type Leveler interface {
    Level() Level
}
type LevelVar struct {
    val atomic.Int64
}
// Level returns v's level.
func (v *LevelVar) Level() Level {
    return Level(int(v.val.Load()))
}
// Set sets v's level to l.
func (v *LevelVar) Set(l Level) {
    v.val.Store(int64(l))
}

通过 slog.LevelVar 设置 debug 等级后,第二次的 debug 日志是可以打印出来

func main() {
    levelVar := &slog.LevelVar{}
    levelVar.Set(slog.LevelInfo)
    opts := &slog.HandlerOptions{AddSource: true, Level: levelVar}
    logger := slog.New(slog.NewJSONHandler(os.Stdout, opts))
    logger.Info("finished", "key", "value")
    levelVar.Set(slog.LevelDebug)
    logger.Debug("finished", "key", "value")
}

想要实现 文章开头 通过 logger.SetLevel (logger.DebugLevel) 快速切换等级,可以选择将 slog.Logger 与 slog.LevelVar 封装到同一结构,比如

type SlogLogger struct {
    logger *slog.Logger
    level  *slog.LevelVar
}

下文 slog 替换 zap 有详细代码体现


原有 logger zap 实现

原有项目已经实现了一套 logger, 使用 zap log 以下代码都是在 logger 包下 

github.com/webws/go-moda/logger


原 zap 代码

logger interface LoggerInterface

package logger
type LoggerInterface interface {
    Debugw(msg string, keysAndValues ...interface{})
    Infow(msg string, keysAndValues ...interface{})
    Errorw(msg string, keysAndValues ...interface{})
    Fatalw(msg string, keysAndValues ...interface{})
    SetLevel(level Level)
    With(keyValues ...interface{}) LoggerInterface
}

zap log 实现 LoggerInterface

type ZapSugaredLogger struct {
    logger    *zap.SugaredLogger
    zapConfig *zap.Config
}
func buildZapLog(level Level) LoggerInterface {
    encoderConfig := zapcore.EncoderConfig{
        TimeKey:        "ts",
        LevelKey:       "level",
        NameKey:        "logger",
        CallerKey:      "caller",
        MessageKey:     "msg",
        StacktraceKey:  "stacktrace",
        LineEnding:     zapcore.DefaultLineEnding,
        EncodeDuration: zapcore.SecondsDurationEncoder,
        EncodeTime:     zapcore.ISO8601TimeEncoder,
        EncodeLevel:    zapcore.LowercaseLevelEncoder,
        EncodeCaller:   zapcore.ShortCallerEncoder,
    }
    zapConfig := &zap.Config{
        Level:             zap.NewAtomicLevelAt(zapcore.Level(level)),
        Development:       true,
        DisableCaller:     false,
        DisableStacktrace: true,
        Sampling:          &zap.SamplingConfig{Initial: 100, Thereafter: 100},
        Encoding:          "json",
        EncoderConfig:     encoderConfig,
        OutputPaths:       []string{"stderr"},
        ErrorOutputPaths:  []string{"stderr"},
    }
    l, err := zapConfig.Build(zap.AddCallerSkip(2))
    if err != nil {
        fmt.Printf("zap build logger fail err=%v", err)
        return nil
    }
    return &ZapSugaredLogger{
        logger:    l.Sugar(),
        zapConfig: zapConfig,
    }
    func (l *ZapSugaredLogger) Debugw(msg string, keysAndValues ...interface{}) {
    l.logger.Debugw(msg, keysAndValues...)
    }
    func (l *ZapSugaredLogger) Errorw(msg string, keysAndValues ...interface{}) {
        l.logger.Errorw(msg, keysAndValues...)
    }
    // ...省略info 之类其他实现接口的方法 
}

全局初始化 logger, 因代码量太大,以下是伪代码,主要提供思路

package logger
// 全局 log,也可以单独 NewLogger 获取新的实例
var globalog = newlogger(DebugLevel)
func newlogger(level Level) *Logger {
    l := &Logger{logger: buildZapLog(level)}
    return l
}
func Infow(msg string, keysAndValues ...interface{}) {
    globalog.logger.Infow(msg, keysAndValues...)
}
// ...省略其他全局方法,比如DebugW 之类

在项目中通过 如下使用 logger

import "github.com/webws/go-moda/logger"
func main() {
    logger.Infow("hello", "key", "value")   // 打印json
}

slog 不侵入业务 替换 zap

logger interface 接口保持不变


slog 实现 代码

package logger
import (
    "log/slog"
    "os"
    "runtime"
)
var _ LoggerInterface = (*SlogLogger)(nil)
type SlogLogger struct {
    logger *slog.Logger
    level  *slog.LevelVar
    // true 代表使用slog打印文件路径,false 会使用自定的方法给日志 增加字段 file line
    addSource bool
}
// newSlog
func newSlog(level Level, addSource bool) LoggerInterface {
    levelVar := &slog.LevelVar{}
    levelVar.Set(slog.LevelInfo)
    opts := &slog.HandlerOptions{AddSource: addSource, Level: levelVar}
    logger := slog.New(slog.NewJSONHandler(os.Stdout, opts))
    return &SlogLogger{
        logger: logger,
        level:  levelVar,
    }
}
func (l *SlogLogger) Fatalw(msg string, keysAndValues ...interface{}) {
    keysAndValues = l.ApppendFileLine(keysAndValues...)
    l.logger.Error(msg, keysAndValues...)
    os.Exit(1)
}
func (l *SlogLogger) Infow(msg string, keysAndValues ...interface{}) {
    keysAndValues = l.ApppendFileLine(keysAndValues...)
    l.logger.Info(msg, keysAndValues...)
}
// 省略继承接口的其他方法 DebugW 之类的
func (l *SlogLogger) SetLevel(level Level) {
    zapLevelToSlogLevel(level)
    l.level.Set(slog.Level(zapLevelToSlogLevel(level)))
}
// 
func (l *SlogLogger) With(keyValues ...interface{}) LoggerInterface {
    newLog := l.logger.With(keyValues...)
    return &SlogLogger{
        logger: newLog,
        level:  l.level,
    }
}
// ApppendFileLine 获取调用方的文件和文件号
// slog 原生 暂不支持 callerSkip,使用此函数啃根会有性能问题,最好等slog提供 CallerSkip 的参数
func (l *SlogLogger) ApppendFileLine(keyValues ...interface{}) []interface{} {
    l.addSource = false
    if !l.addSource {
        var pc uintptr
        var pcs [1]uintptr
        // skip [runtime.Callers, this function, this function's caller]
        runtime.Callers(4, pcs[:])
        pc = pcs[0]
        fs := runtime.CallersFrames([]uintptr{pc})
        f, _ := fs.Next()
        keyValues = append(keyValues, "file", f.File, "line", f.Line)
        return keyValues
    }
    return keyValues
}

全局初始化 logger, 以下伪代码

package logger
// 全局 log,也可以单独 NewLogger 获取新的实例
var globalog = newlogger(DebugLevel)
func newlogger(level Level) *Logger {
    l := &Logger{logger: newSlog(level, false)}
    return l
}
func Infow(msg string, keysAndValues ...interface{}) {
    globalog.logger.Infow(msg, keysAndValues...)
}
// ...省略其他全局方法,比如DebugW 之类

一样可以 通过 如下使用 logger, 与使用 zap 时一样

import "github.com/webws/go-moda/logger"
func main() {
    logger.Infow("hello", "key", "value")   // 打印json
}

slog 实现 callerSkip 功能

slog 的 addsource 参数 会打印文件名和行号,但 并不能像 zap 那样支持 callerSkip, 也就是说 如果将 slog 封装在 logger 目录的 log.go 文件下,使用 logger 进行打印,展示的文件会一只是 log.go

看了 slog 的源码,使用了 runtime.Callers 在内部实现了 callerSkip 功能,但是没有对外暴露 callerSkip 参数

可以看我上面代码 自己封装了一个方法: 

ApppendFileLine, 使用 runtime.Callers 获取到 文件名 和 行号,增加 file 和 line 的 key value 到日志

可能会有性能问题,希望 slog 能对外提供一个 callerSkip 参数


说明

文章中贴的代码不多,主要提供思路,虽然省略了一些方法和 全局 logger 的实现方式

如要查看logger实现细节,可查看在文章开头快速体验引用的包 

github.com/webws/go-moda/logger



  • 上一条:
    微软Azure澳洲数据中心宕机超24小时!因值班工程师不足,无奈关服务器
    下一条:
    在laravel框架中8.x版本升级至10.x版本的两种解决方案分享
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • 近期文章
    • 智能合约Solidity学习CryptoZombie二课:让你的僵尸猎食(0个评论)
    • 智能合约Solidity学习CryptoZombie第一课:生成一只你的僵尸(0个评论)
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2017-09
    • 2020-03
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-12
    • 2021-01
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-10
    • 2021-11
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-03
    • 2022-04
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-02
    • 2024-03
    • 2024-04
    • 2024-05
    • 2024-06
    • 2024-07
    • 2024-08
    • 2024-11
    • 2025-02
    • 2025-04
    • 2025-05
    • 2025-06
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客