本文参与思维科技论文征集,欢迎正在阅读的你加入。大家好,我是炸鱼。在日常工作中,打日志是一个很常见的动作。毕竟没有日志记录,从内部来看,一旦出现问题,定位和排查就会变得非常困难。没有人愿意在半夜靠猜测来解决问题。其他方面,对日志存储的内容、期限、安全性等方面有不同程度的合规要求,应对客户诉求和提单事件。日志有没有用就成了一个重要的诉求。标准库日志想一个问题很痛苦:你在写Go项目的时候,是不是很少直接使用官方的标准库日志?在官方项目中,大多优先使用几个流行的第三方库,如Logrus、Zap、zerolog。对于标准库日志,在临时调试的时候,屏幕输出的大部分场景占比很小。问题是什么?主要集中在以下几个方面:没有日志分类。Error、Warn、Info、Debug等问题难以归类、定位、排查,没有结构化的日志。只提供格式化的日志,没有结构化,不易被程序读取和解析,例如:Json格式。没有可扩展性,灵活性差。标准库日志的日志输出是固定格式的,没有一个大家共同遵守的Logger接口规范,这样社区自然进化很难相互兼容。此外,在用户场景方面,存在不包含上下文信息、性能不够强、无法引入自定义插件等扩展需求。基本上都是第三方库实现,这基本是用户的痛点之一。为什么不早点解决你可能会想,标准库日志是Go生态中的核心库,为什么不早点解决呢?其实在2017年的时候,社区就有过大规模的讨论,可惜后来放弃了。原因是:“我们还没有找到足够的Go库来导入和使用具体的Loggers,所以没有理由继续研究这个”。如下图所示:继续显示bad。救世主slog库诞生的讨论和目标2022年8月,Go团队的@JonathanAmsterdam发起了一场讨论:结构化、分级的日志记录,试图再次对抗这种混乱。该提案(包括讨论)的目标是:易于使用。对现有Logger库的调查表明,开发人员更喜欢简洁易懂的日志记录API。高性能。新的API旨在最小化内存分配和锁定。与运行时跟踪集成。Go团队正在开发和改进运行时跟踪系统。基于新的Logger库的日志将无缝集成到跟踪系统中,开发人员可以将程序操作与运行时行为相关联。目标涵盖了前面背景中提到的痛点。上面的第三点我已经注意到了,它来自于Go团队自身的需求。果然,当务之急是自己对PUSH的需求?云雾缭绕。毕竟10年过去了,这个讨论得到了很多人的建议和进步,才成功孵化。QuickDemo图书馆已经过了“石锤”阶段,进入了实验图书馆。导入地址为:golang.org/x/exp/slog。下面先来快速演示一下新的日志库slog,让大家快速了解和熟悉。以下代码:import"log/slog"funcmain(){slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr)))slog.Info("hello","name","Al").错误(“糟糕”,net.ErrClosed,“状态”,500)slog.LogAttrs(slog.ErrorLevel,“糟糕”,slog.Int(“状态”,500),slog.Any(“错误”,net.ErrClosed))}如果不设置slog.SetDefault,默认输出到标准输出。由于上面的程序设置了os.Stderr,所以会在那里输出。程序运行结果如下:time=2022-10-24T16:05:48.054-04:00level=INFOmsg=helloname=Altime=2022-10-24T16:05:48.054-04:00level=ERRORmsg=oopsstatus=500err="useofclosednetworkconnection"time=2022-10-24T16:05:48.054-04:00level=ERRORmsg=oopsstatus=500err="useofclosednetworkconnection"我们看到了日志调平(Level)、添加自定义字段、设置输出位置等功能。在输出格式方面,新的slog库将以与logfmt库类似的方式实现,至少内置两种格式。默认的logfmt消息格式:foo=bara=14baz="hellokitty"cool%story=brof%^asdf如果要调整为JSON格式,可以设置:slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stderr)))将以JSON格式输出:{"foo":"bar","a":14,"baz":"hellokitty","cool%story":"bro","f":true,"%^asdf":true}设计思路作者将slog库的设计分为:前端和后端。前端,slog认为你常用的、可见的API都是前端,比如:Info、Debug等日志分类,Context和设置上下文内容的自定义字段注入,都属于前端范畴。如下方法:Backend,slog认为真正做具体业务逻辑的Handler是backend,抽象成一个Handler接口。你只需要实现Handler接口来注入一个自定义的Handler。下面的Handler接口:typeHandlerinterface{//启用记录的日志级别Enabled(Level)bool//具体处理方法需要Enabled返回trueHandle(rRecord)errorWithAttrs(attrs[]Attr)HandlerWithGroup(namestring)Handler}其中,可以看到Handle函数有一个Record属性,这是一个核心数据结构。如下代码:typeRecordstruct{Timetime.TimeMessagestringLevelLevelContextcontext.Context}新建slog的内部流程如下:前端方法(例如:Info)将传递的属性封装成变量记录类型。将Record类型的变量传递给后端方法(例如:Handle)。后端Handle方法根据获取到的Record进行相应的格式化、方法调用、日志输出。与其他Loggers交互回到最初的问题?如果我们现在想写一个私有的Logger,或者复用Zap。怎么做?后端方法,有两种方式(方式相同):或者使用Record,调用NewRecord将其包装成Record类型的变量,然后传递下去。否则,将处理逻辑写入自定义Handle中完成。如果想在前端方法中处理,很遗憾,Go并没有开放slog前端的打算。它保证了前端的稳定性和后端可变和可扩展的灵活性。如果你对如何实现自定义Handle感兴趣,可以查看官方的最佳实践TextHandler和JSONHandler。上下文注入是经典的上下文场景,slog库直接内置相关函数支持。如下代码:funcFromContext(ctxcontext.Context)LoggerFromContext返回NewContext存储在ctx中的Logger,没有则返回默认的Logger。funcNewContext(ctxcontext.Context,lLogger)context.ContextNewContext返回一个context包含给定的记录器。使用FromContext检索记录器。具体Demo:funchandle(whttp.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)//...使用slog.FromContext(ctx)...}更方便。综上所述,此时Go社区的日志库已经基本成熟,模式已经定为7788。此时Go官方的slog库上线,显然吸收了前者的很多丰富经验(该提案有一个声明)。相信未来slog库会与更多的Go生态工具链对接,提供更丰富的相关场景。解决Go没有可靠日志库的痛点。您觉得这个新图书馆对您有帮助吗?欢迎一起交流。
