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 简单示例:
slog.Info("finished", "key", "value")slog.Debug("finished", "key1", "value1")
以下是打印日志 默认slog 输出级别是info以上,所以debug是打印不出来.
2023/09/08 00:27:24 INFO finished key=value
json格式化,设置日志等级,并打印调用函数和文件
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"}
原有 logger zap实现
原有的项目已经实现了一套logger,使用zap log 实现接口
原有代码示例
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,因代码量太大,以下是伪代码,主要提供思路,为下文 slog 无侵入替换zap 预热
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 使用日志
logger.Debugw("msg1", "k1", "v1") // debuglogger.SetLevel(DebugLevel) //设置等级logger.Debugw("msg3", "k3", "v3") newLogger := logger.With("name", "song")logger.Infow("msg4", "k4", "v4") // print
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 之类
slog 实现 callerSkip 功能
slog 的 addsource 参数 会打印文件名和行号,但 并不能像 zap 那样支持 callerSkip,也就是说 如果将 slog 封装在 logger 目录的log.go 文件下,使用logger进行打印,展示的文件会一只是log.go
看了 slog 的源码,其实slog 使用了 runtime.Callers 在内部实现了 callerSkip 功能,但是没有对外暴露 callerSkip 参数
我就封装了 ApppendFileLine 方法,使用 runtime.Callers 获取到 文件名 和 行号,增加 file 和 line 的key value到日志
可能会有性能问题,希望slog能对外提供一个 callerSkip 参数
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)
说明
文章中贴的代码不多,主要提供思路,虽然省略了一些方法和 全局logger的实现方式,也写了两个多小时
如要查看logger实现细节,可查看 在文章开头 快速体验 引用的包 github.com/webws/go-moda/logger
也可以直接看下我这个 仓库 go-moda 里使用 slog 和 zap 的封装
相关文章:
go logger 不侵入业务代码 用slog 替换 zap 并实现 callerSkip
快速体验 以下是 项目中 已经用slog替换 zap 后的 logger 使用方法,无任何感知,与之前一模一样 package mainimport "github.com/webws/go-moda/logger"func main() {// 格式化打印 {"time":"2023-09-08T01:25:21.31346308:00","level&qu…...
vuez 与 Vue3 响应式比较
Vue2 的响应式 对象:通过 defineProperty 对对象的已有属性值的读取和修改进行劫持(监视/拦被)。 数组:通过重写数组、更新数组等一系列更新元素的方法来实现元素修改的劫持。 存在的问题如下: &#…...
【Apollo学习笔记】——规划模块TASK之PIECEWISE_JERK_SPEED_OPTIMIZER
文章目录 TASK系列解析文章前言PIECEWISE_JERK_SPEED_OPTIMIZER功能简介PIECEWISE_JERK_SPEED_OPTIMIZER相关配置PIECEWISE_JERK_SPEED_OPTIMIZER流程QP问题的标准类型定义:优化变量设计目标函数约束条件相关矩阵二次项系数矩阵 H H H一次项系数向量 q q q设定OSQP求…...
CNI、CSI 和 CRI在 Docker 中的角色和作用
摘要 CNI(Container Network Interface): CNI 是用于容器网络的接口标准,它定义了容器和网络插件之间的通信协议。CNI 的主要作用是为容器创建和管理网络接口。当创建一个容器时,CNI 插件会被调用来为容器创建一个网络…...
「Docker」M1 Pro 打包docker image问题合集
运行docker 遇到 The requested images platform (linux/arm64/v8) does not match the detected host platform (linux/amd64/v4) and no specific platform was requested 说明打包的镜像没有 linux/amd64 解决方案:重新打包镜像 docker buildx build --platfor…...
Android发布依赖到 Jitpack
前言 我们在日常开发中,经常会用到第三方开源的库文件,有的来自JCenter,Maven Central,google等。但是随着JCenter的弃用,现在用的最多的还是Maven Central,google。今天我们就自己亲自发布一个依赖。 现…...
【虚拟机开不了】linux、centOS虚拟机出现entering emergency mode解决方案
按他的操作输入journalctl之后输入shiftg到日志最后查看报错发现是xfs(dm-0有问题) xfs_repair -v -L /dev/dm-0 reboot解决问题...
嘉泰实业举行“互联网金融知识社区”“安全理财风险讲座”等活动
每一次暖心的沟通都是一次公益,真诚不会因为它的渺小而被忽略;每一声问候都是一次公益,善意不会因为它的普通而被埋没。熟悉嘉泰实业的人都知道,这家企业不但擅长在金融理财领域里面呼风唤雨,同时也非常擅长在公益事业当中践行,属于企业的责任心,为更多有困难的群体带来大爱的传…...
《C++设计模式》——结构型
前言 结构模式可以让我们把很多小的东西通过结构模式组合起来成为一个打的结构,但是又不影响各自的独立性,尽可能减少各组件之间的耦合。 Adapter Class/Object(适配器) Bridge(桥接) Composite(组合) Decorator(装饰) 动态…...
docker-compose安装redis
基于docker-compose快速安装redis 目录 一、目录结构 1、docker-compose.yml 2、redis.conf 二、连接使用 一、目录结构 1、docker-compose.yml version: 3 services:redis:image: registry.cn-hangzhou.aliyuncs.com/zhengqing/redis:6.0.8 # 镜像red…...
机器学习基础之《分类算法(6)—决策树》
一、决策树 1、认识决策树 决策树思想的来源非常朴素,程序设计中的条件分支结构就是if-else结构,最早的决策树就是利用这类结构分割数据的一种分类学习方法 2、一个对话的例子 想一想这个女生为什么把年龄放在最上面判断!!&…...
2023国赛数学建模C题思路模型 - 蔬菜类商品的自动定价与补货决策
# 1 赛题 在生鲜商超中,一般蔬菜类商品的保鲜期都比较短,且品相随销售时间的增加而变差, 大部分品种如当日未售出,隔日就无法再售。因此, 商超通常会根据各商品的历史销售和需 求情况每天进行补货。 由于商超销售的蔬菜…...
【Docker】Docker网络与存储(三)
前言: Docker网络与存储的作用是实现容器之间的通信和数据持久化,以便有效地部署、扩展和管理容器化应用程序。 文章目录 Docker网络桥接网络容器之间的通信 覆盖网络创建一个覆盖网络 Docker存储卷 总结 Docker网络 Docker网络是在容器之间提供通信的机…...
python面向对象的一个简单实例
#发文福利# #!/usr/bin/env python # -*- coding:utf-8 -*-students {id001: {name: serena, age: 18, address: beijing},id002: {name: fanbingbing, age: 42, address: anhui},id003: {name: kahn, age: 20, address: shanghai}}class Student:def __init__(self, xid, na…...
微信小程序通过npm引入tdesign包进行构建的时候报错
问题 在通过npm 引入 tdesign时:https://tdesign.tencent.com/miniprogram/getting-started 通过微信小程序IDE进行npm构建的时候出现:无法构建,应该怎么办? 解决方法: 1 输入: npm init -y命令 2 重新点…...
三次握手四次挥手
TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议。它通过三次握手来建立连接,通过四次挥手来断开连接。 三次握手 所谓三次握手,是指建立一个TCP连接时,需要客户端和服务器总共发送3个报文。三次握手的目的是连接服务器指定端…...
Redis持久化、主从与哨兵架构详解
Redis持久化 RDB快照(snapshot) 在默认情况下, Redis 将内存数据库快照保存在名字为 dump.rdb 的二进制文件中。 你可以对 Redis 进行设置, 让它在“ N 秒内数据集至少有 M 个改动”这一条件被满足时, 自动保存一次数…...
SQLITE_BUSY 是指 SQLite 数据库返回的错误码,表示数据库正在被其他进程或线程使用,因此当前操作无法完成。
SQLITE_BUSY 当多个进程或线程同时尝试对同一个 SQLite 数据库进行写操作时,就可能出现 SQLITE_BUSY 错误。这是为了确保数据库的数据完整性和一致性而设计的并发控制机制。 如果你在使用 SQLite 时遇到 SQLITE_BUSY 错误,可以考虑以下解决方法&#x…...
matlab求解方程组-求解过程中限制解的取值范围
文章目录 问题背景代码my_fun.mmain.m 结果展示:不加入F(4)加入F(4) 问题背景 求解方程组的时候,对某些未知数的求解结果的取值范围有要求。例如在某些物理问题求解中,要求待求解量大于0。 代码 一共两个文件: my_fun.m main.mmy_fun.m function Fm…...
【正则表达式】正则表达式常见匹配模式
目录 常见匹配模式re.match 从字符串的起始位置匹配一个模式泛匹配匹配目标贪婪匹配非贪婪匹配匹配模式转义 re.search 扫描整个字符串并返回第一个成功的匹配re.findall 以列表形式返回全部能匹配的子串re.sub 替换字符串中每一个匹配的子串后返回替换后的字符串 re.compile 将…...
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造,完美适配AGV和无人叉车。同时,集成以太网与语音合成技术,为各类高级系统(如MES、调度系统、库位管理、立库等)提供高效便捷的语音交互体验。 L…...
【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)
HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...
Java 8 Stream API 入门到实践详解
一、告别 for 循环! 传统痛点: Java 8 之前,集合操作离不开冗长的 for 循环和匿名类。例如,过滤列表中的偶数: List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...
论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...
scikit-learn机器学习
# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: # Also add the following code, # so that every time the environment (kernel) starts, # just run the following code: import sys sys.path.append(/home/aistudio/external-libraries)机…...
怎么让Comfyui导出的图像不包含工作流信息,
为了数据安全,让Comfyui导出的图像不包含工作流信息,导出的图像就不会拖到comfyui中加载出来工作流。 ComfyUI的目录下node.py 直接移除 pnginfo(推荐) 在 save_images 方法中,删除或注释掉所有与 metadata …...
