Go日志库log竟然这么小巧!

简介: Go原生的log库本身比较简单,这篇文章先分析一下它的实现,为后续阅读Kitex的日志库klog做一下铺垫。

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情

前言

最近在尝试阅读字节开源RPC框架Kitex的源码,看到日志库klog部分,果不其然在Go原生的log库的基础上增加了自己的设计,大体包括增加了一些格式化的输出增加一些常用的日志级别等。

一番了解后,发现有不少开源的日志库也做了类似的事情,以补充原生log库的不足。因为Go原生的log库本身也比较简单,这篇文章先分析一下它的实现,为后续阅读Kitex的日志库klog做一下铺垫。

本次分析基于:GO SDK 1.18.1 /src/log/log.go的源码。

log库的使用

image-20220530210132795

结果如下:

image-20220530210344181

第三个日志因为第二个日志打印之后,调用panic()函数,且没有调用recover(),导致程序终止。如果注释掉第二行日志即可打印出第三个日志的结果如下:

image-20220530210752882

log.xxx能直接打印日志的原因

carbon

通过观察源码,log包log.go文件中,提供了9个函数可以直接使用,3个一套,分别针对print型日志输出、panic型日志输出(可以recover)、fatal型日志输出(直接终止程序)。

并且这9个函数中频繁使用到了一个std实例,只要我们引入了log包std就会完成初始化,并且作为默认使用的log实例。

image-20220530211928675

Logger结构

既然std是默认的Logger实例,这里先看一下Logger的结构:

image-20220531123015509

  • mu:互斥锁,用于原子写入操作。
  • prefix:日志前缀/后缀。
  • flag:控制需要展示的日志内容。
  • out:描述输出。
  • buf:缓冲区。

关于flag的使用,Go定义了如下的常量:

image-20220531124125730

iota是常量计数器,从0开始自增,可以配合表达式使用,且在一系列常量声明时,可以只指定第一个位置,后续会默认初始化,这里依次初始化为1、2、4...

  • Ldata:输出当地日期,如2009/01/23。
  • Ltime:输出当地时间,如01:23:23。
  • Lmicroseconds:时间精确到微妙,如01:23:23.123123,兼并Ltime。
  • Llongfile:输出文件名全路径 + 调用行号,如/a/b/c/d.go:23。
  • Lshortfile:输出最终文件的名称 + 调用行号,如d.go:23,覆盖Llongfile
  • LUTC:如果设置了LdataLtime,则将输出UTC时间,而不是本地时区。
  • Lmsgprefix:将prefix信息从当前日志行首部移动到message之前。
  • LstdFlagsstd实例的默认值,表示Ldata | Ltime = 3

官方的注释中给出了一些介绍flag用法的例子,这里介绍一个:

如果:std.flag == Ldate | Ltime | Lmicroseconds | Llongfile == 15

则日志行输出结果为:2009/01/23 01:23:23.123123 /a/b/c/d.go:23: messagemessage为具体的日志内容。

std.Output()

回到上面9个函数打印日志,都通过调用std.Output()实现日志的输出,是log库的核心函数,看一下代码:

image-20220531122130791

  • 通过l.mu.Lock(),确保日志内容的写入是原子的。
  • 检查l.flag是否包括Lshortfile或者Llongfile标志位,如果有则需要获取文件名行数,且这一步先释放了锁,因为Caller方法的调用比较耗时(expensive),确保锁住的临界区尽可能小。
  • calldepth:0表示获取调用runtime.Caller(calldepth)的文件名和行数,1表示调用std.Output()的文件名和函数,2表示调用log.Println()的文件名和行数,3则已经用不到了,Go原生log库获取行信息用的都是2。

image-20220531141453630

  • 清空缓冲区l.buf,并格式化日志头部信息(日期、文件名、行数),将其append入`buf。
  • 最后将具体的日志信息s添加入buf,会补全末尾换行符,并调用l.out.Write(),将日志写入事先注册的输出文件。

定制自己的Logger

image-20220531135448565

log库默认使用的std实例是事先初始化好的,那么借助New方法,我们也可以定制自己的logger:

image-20220531142257578

这里指定了日志输出到文件log.txt中,并且定义了一些flag,结果如下:

image-20220531142835128

小结

通过分析,我们发现log是一个很简洁的日志库,它有三种日志输出方式printpanicfatal,且可以自己定制日志的输出格式。但是熟悉其他语言开发的同学可能会对日志级别有更多的需求,且log的格式化用起来比较复杂。

因此会衍生出很多基于log的二次封装的日志库,下一篇文章将讲解字节跳动RPC框架Kitex的日志库klog的实现。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
24天前
|
Java
使用Java代码打印log日志
使用Java代码打印log日志
77 1
|
4天前
|
数据采集 存储 Go
使用Go语言和chromedp库下载Instagram图片:简易指南
Go语言爬虫示例使用chromedp库下载Instagram图片,关键步骤包括设置代理IP、创建带代理的浏览器上下文及执行任务,如导航至用户页面、截图并存储图片。代码中新增`analyzeAndStoreImage`函数对图片进行分析和分类后存储。注意Instagram的反爬策略可能需要代码适时调整。
使用Go语言和chromedp库下载Instagram图片:简易指南
|
4天前
|
Java
log4j异常日志过滤规则配置
log4j异常日志过滤规则配置
15 0
|
16天前
|
C++
glog --- C++日志库
glog --- C++日志库
|
16天前
|
运维 安全 Ubuntu
`/var/log/syslog` 和 `/var/log/messages` 日志详解
`/var/log/syslog` 和 `/var/log/messages` 是Linux系统的日志文件,分别在Debian和Red Hat系发行版中记录系统事件和错误。它们包含时间戳、日志级别、PID及消息内容,由`rsyslog`等守护进程管理。常用命令如`tail`和`grep`用于查看和搜索日志。日志级别从低到高包括`debug`到`emerg`,表示不同严重程度的信息。注意保护日志文件的安全,防止未授权访问,并定期使用`logrotate`进行文件轮转以管理磁盘空间。
24 1
|
17天前
|
网络协议 应用服务中间件 Linux
centos7 Nginx Log日志统计分析 常用命令
centos7 Nginx Log日志统计分析 常用命令
33 2
|
17天前
|
Ubuntu Linux 网络安全
/var/log/auth.log日志详解
`/var/log/auth.log`是Linux(尤其是Debian系如Ubuntu)记录身份验证和授权事件的日志文件,包括登录尝试(成功或失败)、SSH活动、sudo使用和PAM模块的操作。登录失败、SSH连接、sudo命令及其它认证活动都会在此记录。查看此日志通常需root权限,可使用`tail`、`less`或`grep`命令。文件内容可能因发行版和配置而异。例如,`sudo tail /var/log/auth.log`显示最后几行,`sudo grep "failed password" /var/log/auth.log`搜索失败密码尝试。
73 8
|
25天前
|
API 开发工具 C语言
【嵌入式开源库】EasyLogger的使用, 一款轻量级且高性能的日志库
【嵌入式开源库】EasyLogger的使用, 一款轻量级且高性能的日志库
|
1天前
|
安全 Go 开发者
Golang深入浅出之-Go语言并发编程面试:Goroutine简介与创建
【4月更文挑战第22天】Go语言的Goroutine是其并发模型的核心,是一种轻量级线程,能低成本创建和销毁,支持并发和并行执行。创建Goroutine使用`go`关键字,如`go sayHello("Alice")`。常见问题包括忘记使用`go`关键字、不正确处理通道同步和关闭、以及Goroutine泄漏。解决方法包括确保使用`go`启动函数、在发送完数据后关闭通道、设置Goroutine退出条件。理解并掌握这些能帮助开发者编写高效、安全的并发程序。
10 1