go logger 不侵入业务代码 用slog 替换 zap 并实现 callerSkip

快速体验

以下是 项目中 已经用slog替换 zap 后的 logger 使用方法,与替换前使用方式相同,无任何感知

package mainimport "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")   // 打印jsonlogger.Debugw("debug hello slog", "key", "value") // 不展示logger.SetLevel(logger.DebugLevel)                // 设置等级logger.Debugw("debug hello slog", "key", "value") // 设置了等级之后展示 debug// withnewLog := logger.With("newkey", "newValue")newLog.Debugw("new hello slog") // 会打印 newkey:newValuelogger.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.Loggerlevel  *slog.LevelVar
}

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

原有 logger zap实现

原有项目已经实现了一套logger,使用zap log 以下代码都是在 logger 包下 github.com/webws/go-moda/logger

原zap代码

logger interface LoggerInterface

package loggertype 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.SugaredLoggerzapConfig *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 loggerimport ("log/slog""os""runtime"
)var _ LoggerInterface = (*SlogLogger)(nil)type SlogLogger struct {logger *slog.Loggerlevel  *slog.LevelVar// true 代表使用slog打印文件路径,false 会使用自定的方法给日志 增加字段 file lineaddSource 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 = falseif !l.addSource {var pc uintptrvar 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

也可以直接看下我这个 仓库 go-moda 里使用 slog 和 zap 的封装

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/72002.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

滴滴笔试——算式转移

题目:给出一个仅包含加减乘除四种运算符的算式(不含括号),如12*3/4,在保持运算符顺序不变的情况下,现在你可以进行若干次如下操作:如果交换相邻的两个数,表达式值不变,那么你就可以交换这两个数…

Ceph入门到精通-生产日志级别设置

Ceph 子系统及其日志记录级别的信息。 了解 Ceph 子系统及其日志记录级别 Ceph 由多个子系统组成: 每个子系统都有其日志记录级别: 默认情况下存储在 /var/log/ceph/ 目录中的输出日志(日志级别)存储在内存缓存中的日志&#…

无涯教程-JavaScript - DEC2HEX函数

描述 DEC2HEX函数将十进制数转换为十六进制。 语法 DEC2HEX (number, [places])争论 Argument描述Required/Optionalnumber 要转换的十进制整数。 如果number为负数,则将忽略位数,并且DEC2HEX返回10个字符(40位)的十六进制数字,其中最高有效位是符号位。其余的39位是幅度位…

Laravel 模型的关联写入多对多的关联写入 ⑩③

作者 : SYFStrive 博客首页 : HomePage 📜: THINK PHP 📌:个人社区(欢迎大佬们加入) 👉:社区链接🔗 📌:觉得文章不错可以点点关注 &#x1f44…

读高性能MySQL(第4版)笔记04_操作系统和硬件优化

1. 从软件本身和它运行的典型工作负载来看,MySQL通常也更适合运行在廉价硬件上 2. 基本资源 2.1. CPU 2.2. 内存 2.3. 磁盘 2.4. 瓶颈 2.5. 网络资源 3. CPU 3.1. 最常见的瓶颈是CPU耗尽 3.2. 检查CPU使用率来确定工作负载是否受CPU限制 3.3. 低延迟&…

Python基础篇(17):模块与包

一、as 关键字的使用 1、as 关键字的作用:给导入的模块取别名 import 测试1 as Test_1 import 测试2 as Test_2Test_1.say_hello() Test_2.say_hello() 二、if __name__ __main__ 1、作用 测试当前模块所编写的代码块,根据业务自主选择需要运行的代…

TcpServerChannel 类服务

服务端: var provider new BinaryServerFormatterSinkProvider(); provider.TypeFilterLevel System.Runtime.Serialization.Formatters.TypeFilterLevel.Full; IDictionary props new Hashtable(); props["name"] "Ser…

2023 年高教社杯全国大学生数学建模竞赛题目 A 题 定日镜场的优化设计

A 题 定日镜场的优化设计 构建以新能源为主体的新型电力系统,是我国实现“碳达峰”“碳中和”目标的一项重要措施。塔式太阳能光热发电是一种低碳环保的新型清洁能源技术[1]。 定日镜是塔式太阳能光热发电站(以下简称塔式电站)收集太阳能的基…

网络编程套接字 | UDP套接字

前面的文章中我们叙述了网络编程套接字的一些预备知识点,从本文开始我们就将开始UDP套接字的编写。本文中的服务端与客户端都是在阿里云的云服务器进行编写与测试的。 udp_v1 在v1的版本中我们先来使用一下前面讲过得一些接口,简单的构建一个udp服务器…

OceanBase 4.1解读:读写兼备的DBLink让数据共享“零距离”

梁长青,OceanBase 高级研发工程师,从事 SQL 执行引擎相关工作,目前主要负责 DBLink、单机引擎优化等方面工作。 沈大川,OceanBase 高级研发工程师,从事 SQL 执行引擎相关工作,曾参与 TPC-H 项目攻坚&#x…

c共享内存

共享内存 共享内存实现使用共享内存步骤&#xff1a;示例&#xff1a; 共享内存实现 共享内存实质是将内核中的一块内存映射到进程中的内存&#xff0c;操作本地内存就相当于操作共享内存。 使用共享内存步骤&#xff1a; 创建共享内存 #include <sys/ipc.h> #includ…

用百度云怎么重装电脑系统

用百度云怎么重装电脑系统 随着云计算技术的飞速发展&#xff0c;百度云成为了人们日常生活中不可或缺的一部分。百度云不仅提供了强大的文件存储和传输功能&#xff0c;还可以帮助人们轻松地重装电脑系统。下面就让我们来介绍一下如何用百度云重装电脑系统。 步骤一&#xf…

mysql配置项整理

二、&#xff1a;mysql服务器参数 general 基础配置 datadir/var/lib/mysql #数据文件存放的目录 socket/var/lib/mysql/mysql.sock #mysql.socket表示server和client在同一台服务器&#xff0c;并且使用localhost进行连接&#xff0c;就会使用socket进行连接 pid_file/v…

NUC980webServer开发

目录 1.RTL8189FTV驱动移植 2.wifi配置工具hostapd移植 1.openssl-1.0.2r交叉编译 2.libnl-3.2.25.tar.gz交叉编译 3.hostapd-2.9.tar.gz交叉编译 4.移植相关工具到开发板 1.RTL8189FTV驱动移植 1. 把驱动文件源码放在linux源码的drivers/net/wireless/realtek/rtlwifi/目录…

大语言模型推理与部署工具介绍

推理与部署 本项目中的相关模型主要支持以下量化、推理和部署方式&#xff0c;具体内容请参考对应教程。 工具特点CPUGPU量化GUIAPIvLLM16K‡教程llama.cpp丰富的量化选项和高效本地推理✅✅✅❌✅❌✅link&#x1f917;Transformers原生transformers推理接口✅✅✅✅❌✅✅l…

一生一芯13——linux设置环境变量

参考自https://baijiahao.baidu.com/s?id1753516015142083750&wfrspider&forpc 本机使用ubuntu22.04 目录 1. 读取环境变量1. 读取特定环境变量2. 读取所有环境变量 2. 设置环境变量1. 对当前用户有效2. root设置 1. 读取环境变量 1. 读取特定环境变量 在命令行中输…

【内存管理】C与C++的内存管理异同点

C/C程序内存区域划分 栈又称堆栈&#xff1a;存放非静态局部变量/函数参数/返回值等等&#xff0c;栈是向下增长的。内存映射段&#xff1a;高效的I/O映射方式&#xff0c;用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存&#xff0c;做进程间通信。堆&…

LeetCode 1113.报告的记录

数据准备 Create table If Not Exists Actions (user_id int, post_id int, action_date date, action ENUM(view, like, reaction, comment, report, share), extra varchar(10)); Truncate table Actions; insert into Actions (user_id, post_id, action_date, action, ext…

Mybatis传递实体对象只能直接获取,不能使用对象.属性方式获取

mybatis的自动识别参数功能很强大&#xff0c;pojo实体类可以直接写进mapper接口里面&#xff0c;不需要在mapper.xml文件中添加paramType,但是加了可以提高mybatis的效率 不加Param注解&#xff0c;取值的时候直接写属性 //这里是单参数&#xff0c;可以不加param&#xff01…

软件测试/测试开发丨Web自动化 测试用例流程设计

点此获取更多相关资料 本文为霍格沃兹测试开发学社学员学习笔记分享 原文链接&#xff1a;https://ceshiren.com/t/topic/27173 一、测试用例通用结构回顾 1.1、现有测试用例存在的问题 可维护性差可读性差稳定性差 1.2、用例结构设计 测试用例的编排测试用例的项目结构 1…