热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Go十年了,终于想起要统一log库了!

大家好,我是煎鱼。在日常工作中,打日志是很常见的动作。毕竟不打日志,从内部来讲,一旦出问题,定位、排查都会变的

大家好,我是煎鱼。

在日常工作中,打日志是很常见的动作。毕竟不打日志,从内部来讲,一旦出问题,定位、排查都会变的非常困难。谁也不想大半夜在那靠猜解决问题。

在其他方面,对日志的存储的内容、时长、安全均有不同程度的合规要求,应对客户诉求和提单上门的事件。

日志好不好用,就成了重要的诉求了。

标准库 log 很痛

思考一个问题:平时你在写 Go 工程时,是否很少直接使用官方标准库 log?

在正式项目中,大多是优先使用几个爆款第三方库,例如:Logrus、Zap、zerolog,毕竟又快又猛。而标准库 log,在临时调试,屏幕输出的场景居多,占比较少。

这问题出在了哪里?主要集中在以下方面:

  • 没有日志分级。不便于分类、定位、排查问题,例如:Error、Warn、Info、Debug 等。

  • 没有结构化日志。只提供格式化日志,不提供结构化,不便于程序读取、解析,例如:Json 格式。

  • 没有扩展性,灵活度差。标准库 log 的日志输出都是固定格式,没有一个 Logger 接口规范,让大家都遵守,以至于现在社区纯自然演进,难互相兼容。

除此之外,在用户场景上,有着不包含上下文(context)信息、性能不够强劲、无法引入自定义插件等扩展诉求。基本上第三方库均有实现的,基本都用户的痛点之一。

为什么不早点解决

你可能会想,标准库 log 作为 Go 生态里的核心库,为什么不早点解决?

实际上在 2017 年时,有在社区进行了大规模讨论,可惜放弃了。原因是:“我们还没有找到足够多的导入和使用具体 Logger 的 Go 库,因此没有理由继续开展这项工作”。

如下图:

20134424cbe8270a40972b79d43e910a.png
g/golang-dev/c/F3l9Iz1JX4g/m/t0J0loRaDQAJ

继续摆烂,再鸽几年。

救星 slog 库诞生

讨论和目标

在 2022 年 8 月,Go 团队的 @ Jonathan Amsterdam 发起了 discussion: structured, leveled logging[1] 的讨论,试图与这个乱象再度一决雌雄。

如下图:

83fe2103569d70adcbeafa8e761280bd.png
discussions/54763

提案(含讨论)的目标是:

  • 使用方便。对现有 Logger 库的调查说明,开发人员更想要一个简洁且易懂的日志 API。

  • 高性能。新的 API 希望做到最大限度的减少内存分配和锁定。

  • 与运行时跟踪集成。Go 团队正在开发和改进运行时跟踪系统,基于新 Logger 库的日志将可以无缝衔接到这个跟踪系统中,开发人员能够实现程序操作与运行时的行为相关联。

目标涵盖了前文背景中提到的痛点。我关注到上述的第三点,来自 Go 团队自己的需求,果然最优先要做的需求都是自己想要 PUSH 的需求?雾了雾了。

毕竟已经 10 年了,本讨论中得到了许多人的建议和推进,成功孵化。

快速 Demo

该库目前已经经过 “石锤” 阶段,非常快速地进入了实验库,导入地址是:golang.org/x/exp/slog,推荐大家试用。

4c4615fbfbbdcd647e8acd3b36e18f3d.png

我们先上手新日志库 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[2] 库类似的方式来实现,内置至少两种格式,也可以自定义实现。

默认的 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 和自定义字段注入等都包含在前端的范畴内。

如下方法:

e1de44607a5bf7d2164e38db0a641762.png

后端,slog 认为实际干具体业务逻辑的 Handler 是后端,并将其抽象成了 Handler 接口,只需要实现 Handler 接口,就可以注入自定义 Handler。

如下 Handler 接口:

type Handler interface {// 启用记录的日志级别Enabled(Level) bool// 具体的处理方法,需要 Enabled 返回 trueHandle(r Record) errorWithAttrs(attrs []Attr) HandlerWithGroup(name string) Handler
}

其中你可以看到 Handle 函数有一个 Record 属性,它是一个核心的数据结构。

如下代码:

type Record struct {Time time.TimeMessage stringLevel LevelContext context.Context
}

新的 slog 的内部流程如下:

  1. 前端方法(例如:Info)将所传属性封装为 Record 类型的变量。

  2. 将 Record 类型的变量传递给后端方法(例如:Handle)。

  3. 后端 Handle 方法根据所得 Record,进行对应的格式化、方法调用、日志输出等具体日志动作。

与其他 Logger 交互

那回到最开始的问题?

如果我们现在要写一个私有的 Logger,或是复用 Zap。要怎么做?

后端方法,有两条路:

  1. 要不走 Record,调用 NewRecord 将其包装成 Record 类型的变量,再往下传。

  2. 要不走 Handle,将处理逻辑写到自定义 Handle 中去完成。

其实两者是同一条路。

如果是想在前端方法来处理,很遗憾,Go 没有计划将 slog 前端开放。确保了前端稳态,后端可变可扩展的灵活性。我个人觉得有利有弊了。

如果有兴趣了解如何实现自定义 Handle,可以查看 TextHandler[3] 和 JSONHandler[4] 即可,是官方最佳实践。

上下文注入

经典的 context 场景,slog 库直接内置了相关的函数进行支持。

如下代码:

func FromContext(ctx context.Context) LoggerFromContext returns the Logger stored in ctx by NewContext, or the defaultLogger if there is none.func NewContext(ctx context.Context, l Logger) context.ContextNewContext returns a context that contains the given Logger. Use FromContextto 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 库的痛点。

你觉得这个新库能在未来统一乱象吗?

参考资料

[1]

discussion: structured, leveled logging: https://github.com/golang/go/discussions/54763

[2]

logfmt: https://pkg.go.dev/github.com/kr/logfmt

[3]

TextHandler: https://cs.opensource.google/go/x/exp/+/c99f073a:slog/text_handler.go

[4]

JSONHandler: https://cs.opensource.google/go/x/exp/+/c99f073a:slog/json_handler.go

关注和加煎鱼微信,

获取一手业内消息和知识,拉你进交流群👇

f3f0b72d0a110b92a8cd46bb573f0c49.jpeg

你好,我是煎鱼,出版过 Go 畅销书《Go 语言编程之旅》,再到获得 GOP(Go 领域最有观点专家)荣誉,点击蓝字查看我的出书之路

日常分享高质量文章,输出 Go 面试、工作经验、架构设计,加微信拉读者交流群,和大家交流!


推荐阅读
  • 基于Net Core 3.0与Web API的前后端分离开发:Vue.js在前端的应用
    本文介绍了如何使用Net Core 3.0和Web API进行前后端分离开发,并重点探讨了Vue.js在前端的应用。后端采用MySQL数据库和EF Core框架进行数据操作,开发环境为Windows 10和Visual Studio 2019,MySQL服务器版本为8.0.16。文章详细描述了API项目的创建过程、启动步骤以及必要的插件安装,为开发者提供了一套完整的开发指南。 ... [详细]
  • 本文介绍了 Go 语言中的高性能、可扩展、轻量级 Web 框架 Echo。Echo 框架简单易用,仅需几行代码即可启动一个高性能 HTTP 服务。 ... [详细]
  • java解析json转Map前段时间在做json报文处理的时候,写了一个针对不同格式json转map的处理工具方法,总结记录如下:1、单节点单层级、单节点多层级json转mapim ... [详细]
  • 使用Tkinter构建51Ape无损音乐爬虫UI
    本文介绍了如何使用Python的内置模块Tkinter来构建一个简单的用户界面,用于爬取51Ape网站上的无损音乐百度云链接。虽然Tkinter入门相对简单,但在实际开发过程中由于文档不足可能会带来一些不便。 ... [详细]
  • 本文总结了一些开发中常见的问题及其解决方案,包括特性过滤器的使用、NuGet程序集版本冲突、线程存储、溢出检查、ThreadPool的最大线程数设置、Redis使用中的问题以及Task.Result和Task.GetAwaiter().GetResult()的区别。 ... [详细]
  • 在JavaWeb开发中,文件上传是一个常见的需求。无论是通过表单还是其他方式上传文件,都必须使用POST请求。前端部分通常采用HTML表单来实现文件选择和提交功能。后端则利用Apache Commons FileUpload库来处理上传的文件,该库提供了强大的文件解析和存储能力,能够高效地处理各种文件类型。此外,为了提高系统的安全性和稳定性,还需要对上传文件的大小、格式等进行严格的校验和限制。 ... [详细]
  • 在《Cocos2d-x学习笔记:基础概念解析与内存管理机制深入探讨》中,详细介绍了Cocos2d-x的基础概念,并深入分析了其内存管理机制。特别是针对Boost库引入的智能指针管理方法进行了详细的讲解,例如在处理鱼的运动过程中,可以通过编写自定义函数来动态计算角度变化,利用CallFunc回调机制实现高效的游戏逻辑控制。此外,文章还探讨了如何通过智能指针优化资源管理和避免内存泄漏,为开发者提供了实用的编程技巧和最佳实践。 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • 在Java Web服务开发中,Apache CXF 和 Axis2 是两个广泛使用的框架。CXF 由于其与 Spring 框架的无缝集成能力,以及更简便的部署方式,成为了许多开发者的首选。本文将详细介绍如何使用 CXF 框架进行 Web 服务的开发,包括环境搭建、服务发布和客户端调用等关键步骤,为开发者提供一个全面的实践指南。 ... [详细]
  • 本文介绍了如何在 Spring Boot 项目中使用 spring-boot-starter-quartz 组件实现定时任务,并将 cron 表达式存储在数据库中,以便动态调整任务执行频率。 ... [详细]
  • Cookie学习小结
    Cookie学习小结 ... [详细]
  • 本文详细介绍了 Spark 中的弹性分布式数据集(RDD)及其常见的操作方法,包括 union、intersection、cartesian、subtract、join、cogroup 等转换操作,以及 count、collect、reduce、take、foreach、first、saveAsTextFile 等行动操作。 ... [详细]
  • 本文详细介绍了 com.apollographql.apollo.api.internal.Optional 类中的 orNull() 方法,并提供了多个实际代码示例,帮助开发者更好地理解和使用该方法。 ... [详细]
  • MySQL Decimal 类型的最大值解析及其在数据处理中的应用艺术
    在关系型数据库中,表的设计与SQL语句的编写对性能的影响至关重要,甚至可占到90%以上。本文将重点探讨MySQL中Decimal类型的最大值及其在数据处理中的应用技巧,通过实例分析和优化建议,帮助读者深入理解并掌握这一重要知识点。 ... [详细]
  • 如何在方法上应用@ConfigurationProperties注解进行属性绑定 ... [详细]
author-avatar
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有