咱们好,我是煎鱼。
在日常工作中,打日志是很常见的动作。毕竟不打日志,从内部来讲,一旦出问题,定位、排查都会变的非常困难。谁也不想大深夜在那靠猜处理问题。
在其他方面,对日志的存储的内容、时长、安全均有不同程度的合规要求,应对客户诉求和提单上门的事件。
日志好不好用,就成了重要的诉求了。
规范库 log 很痛
考虑一个问题:平时你在写 Go 工程时,是否很少直接运用官方规范库 log?
在正式项目中,大多是优先运用几个爆款第三方库,例如:Logrus、Zap、zerolog。而规范库 log,在暂时调试,屏幕输出的场景居多,占比较少。
这问题出在了哪里?主要集中在以下方面:
- **没有日志分级。**不便于分类、定位、排查问题,例如:Error、Warn、Info、Debug 等。
- **没有结构化日志。**只供给格局化日志,不供给结构化,不便于程序读取、解析,例如:Json 格局。
- **没有扩展性,灵敏度差。**规范库 log 的日志输出都是固定格局,没有一个 Logger 接口规范,让咱们都遵守,以至于现在社区纯天然演进,难互相兼容。
除此之外,在用户场景上,有着不包含上下文(context)信息、功能不够微弱、无法引入自定义插件等扩展诉求。根本上第三方库均有完结的,根本都用户的痛点之一。
为什么不早点处理
你或许会想,规范库 log 作为 Go 生态里的核心库,为什么不早点处理?
实践上在 2017 年时,有在社区进行了大规模评论,惋惜放弃了。原因是:“咱们还没有找到足够多的导入和运用详细 Logger 的 Go 库,因此没有理由持续开展这项工作”。
如下图:
持续摆烂。
救星 slog 库诞生
评论和方针
在 2022 年 8 月,Go 团队的 @ Jonathan Amsterdam 发起了 discussion: structured, leveled logging 的评论,企图与这个乱象再度一决雌雄。
提案(含评论)的方针是:
- **运用便利。**对现有 Logger 库的查询阐明,开发人员更想要一个简洁且易懂的日志 API。
- **高功能。**新的 API 希望做到最大限度的减少内存分配和锁定。
- **与运行时盯梢集成。**Go 团队正在开发和改进运行时盯梢体系,基于新 Logger 库的日志将能够无缝衔接到这个盯梢体系中,开发人员能够完结程序操作与运行时的行为相相关。
方针涵盖了前文布景中说到的痛点。我关注到上述的第三点,来自 Go 团队自己的需求,果然最优先要做的需求都是自己想要 PUSH 的需求?雾了雾了。
毕竟现已 10 年了,本评论中得到了许多人的建议和推进,成功孵化。
快速 Demo
该库目前现已通过 “石锤” 阶段,进入了实验库,导入地址是:golang.org/x/exp/slog。
咱们先上手新日志库 slog 的快速 Demo,便于咱们快速了解和熟悉。
如下代码:
import "log/slog"
func main() {
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr)))
slog.Info("hello", "name", "Al")
slog.Error("oops", net.ErrClosed, "status", 500)
slog.LogAttrs(slog.ErrorLevel, "oops",
slog.Int("status", 500), slog.Any("err", net.ErrClosed))
}
假如不设置 slog.SetDefault
将会默许输出到规范输出。由于上述程序设置了 os.Stderr
,因此会在此输出。
程序结果如下:
time=2022-10-24T16:05:48.054-04:00 level=INFO msg=hello name=Al
time=2022-10-24T16:05:48.054-04:00 level=ERROR msg=oops status=500 err="use of closed network connection"
time=2022-10-24T16:05:48.054-04:00 level=ERROR msg=oops status=500 err="use of closed network connection"
咱们现已看到了日志分级(Level)、自定义字段追加、设置输出地等特性。在输出格局上,新的 slog 库,将会采纳与 logfmt 库类似的方式来完结,内置至少两种格局。
默许的 logfmt 消息格局:
foo=bar a=14 baz="hello kitty" cool%story=bro f %^asdf
假如想调整为 JSON 格局,可进行设置:
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stderr)))
会运用 JSON 格局输出:
{ "foo": "bar", "a": 14, "baz": "hello kitty", "cool%story": "bro", "f": true, "%^asdf": true }
规划思路
作者将 slog 库的规划分为:前端、后端。
前端,slog 以为你常用且能看得见的 API 都是前端,例如:Info、Debug 等日志分级的,设置上下文内容的 Context 和自定义字段注入等都包含在前端的领域内。
如下办法:
后端,slog 以为实践干详细事务逻辑的 Handler 是后端,并将其抽象成了 Handler 接口,只需要完结 Handler 接口,就能够注入自定义 Handler。
如下 Handler 接口:
type Handler interface {
// 启用记录的日志等级
Enabled(Level) bool
// 详细的处理办法,需要 Enabled 回来 true
Handle(r Record) error
WithAttrs(attrs []Attr) Handler
WithGroup(name string) Handler
}
其中你能够看到 Handle 函数有一个 Record 属性,它是一个核心的数据结构。
如下代码:
type Record struct {
Time time.Time
Message string
Level Level
Context context.Context
}
新的 slog 的内部流程如下:
- 前端办法(例如:Info)将所传属性封装为 Record 类型的变量。
- 将 Record 类型的变量传递给后端办法(例如:Handle)。
- 后端 Handle 办法依据所得 Record,进行对应的格局化、办法调用、日志输出。
与其他 Logger 交互
那回到最开始的问题?
假如咱们现在要写一个私有的 Logger,或是复用 Zap。要怎么做?
后端办法,有两条路(同一条路):
- 要不走 Record,调用 NewRecord 将其包装成 Record 类型的变量,再往下传。
- 要不走 Handle,将处理逻辑写到自定义 Handle 中去完结。
假如是想在前端办法来处理,很遗憾,Go 没有方案将 slog 前端敞开。确保了前端稳态,后端可变可扩展的灵敏性。
假如有兴趣了解如何完结自定义 Handle,能够查看 TextHandler 和 JSONHandler 即可,是官方最佳实践。
上下文注入
经典的 context 场景,slog 库直接内置了相关的函数进行支撑。
如下代码:
func FromContext(ctx context.Context) Logger
FromContext returns the Logger stored in ctx by NewContext, or the default
Logger if there is none.
func NewContext(ctx context.Context, l Logger) context.Context
NewContext returns a context that contains the given Logger. Use FromContext
to retrieve the Logger.
详细的 Demo:
func handle(w http.ResponseWriter, r *http.Request) {
rlogger := slog.FromContext(r.Context()).With(
"method", r.Method,
"url", r.URL,
"traceID", getTraceID(r),
)
ctx := slog.NewContext(r.Context(), rlogger)
// ... use slog.FromContext(ctx) ...
}
仍是比较便利的。
总结
在此刻,Go 社区中的 log 库们现已根本成熟,格局已定的 7788。此时 Go 官方的 slog 库推出,很明显吸取了前者的大量丰厚经验(提案有声明)。
我相信在未来 slog 库,会和更多的 Go 生态的工具链打通,供给更丰厚的相关场景。处理 Go 没有一个靠谱 log 库的痛点。
你觉得这个新库对你有帮助吗?
文章持续更新,能够微信搜【脑子进煎鱼了】阅览,本文 GitHub github.com/eddycjy/blo… 已录入,学习 Go 言语能够看 Go 学习地图和道路,欢迎 Star 催更。
推荐阅览
- Go 只会 if err != nil?这是不对的,分享这些优雅的处理姿态给你!
- Go 错误处理新思路?用左侧函数和表达式
- 先睹为快,Go2 Error 的挣扎之路
Go 图书系列
- Go 言语入门系列:初探 Go 项目实战
- Go 言语编程之旅:深入用 Go 做项目
- Go 言语规划哲学:了解 Go 的为什么和规划考虑
- Go 言语进阶之旅:进一步深入 Go 源码