golang日志服务器_深扒GO日志 | (一)从Go语言的日志包说起
原标题:深扒GO日志 | (一)从Go语言的日志包说起
本文转自容器时代
在计算机世界里,记录了发生在操作系统或其他软件运行时的事件或状态。技术人员通过日志记录进而判断系统的运行状态,寻找导致系统出错、崩溃的成因等。日志在软件系统里是非常重要的一环。
本系列文章将按照分析Go语言的日志包、Docker的日志实现、Kubernetes的日志实现以及如何打造自己的日志框架进行学习和思考。本文是系列第一篇。
1.日志初探
在“开扒”Golang的日志包之前,先来看看日志是什么。下面是Ubuntu系统里/var/log路径下的文件清单,大部分软件的日志都在这里存放。
常见的Apache服务器日志如下:
日志可以简单地分为事件日志和消息日志两种。事件日志记录了发生在系统运行过程中的事件,可以用来审计操作、诊断问题等,对于理解复杂系统(例如很少人为操作的服务系统)的运行非常关键。消息日志则被应用到如即时通信等软件里,用来记录来往的消息。
常用的日志标准是定义在(IETF) 里的。syslog标准使用一个标准化的子系统来生成、过滤、记录和分析日志消息。
从上面的日志示例中可以了解到日志记录某个事件,需要记录它的时间、地点、参与者、起因、简要经过等信息。而在syslog相关标准中,还定义了严重性等级(Severity Level),用来标识该条日志记录的紧要程度:
那么综上,日志记录的一般格式可以概括为:
[时间] [日志级别] [地点] [参与者] [事件] [起因]
那么Golang打印的日志长什么样?
2.Golang的日志格式
利用Golang的log包提供的Println()方法可以很快地打印出来一条日志:
它的构成相当简单,前面是事件发生的时间,后面是事件记录。
它的构成相当简单,前面是事件发生的时间,后面是事件记录。
还可以继续测试:如果试图利用 > 将它重定向到文件aaa中是不行的,只能使用 2> 来完成重定向,这说明了它是输出到stderr的本质。
log包里提供了多种格式化日志输出方法:Printf()、Fatalf()、Panicf()。
Fatalf()的输出如下:
Panicf()的输出如下:
相比Printf(), Fatalf()会在输出日志消息之后退出程序,退出代码为1,而Panicf()还会打印相关的调用信息,再退出程序,代码为2。
最后log包还可以自定义日志前缀: 借助于SetPrefix()方法,
和借助于SetFlags()方法修改默认的前缀为打印文件及行号:
3.Golang的日志包
Golang源码的日志目录结构是这样的:
其中包含了两个包,一个是syslog系统日志包,另一个是log包。这里先看log包。
log包里主要定义了Logger类型及它的各种方法。同时还定义了全局变量std,利用它的辅助函数Print[f|ln],Fatal[f|ln]和Panic[f|ln]可以快速地打印日志,而无需手动创建新的Logger实例。
在log包里首先定义了一些常量,它们是日志输出前缀的标识:
这些值除了Ldate和LstdFlags显式赋值,其它的并没有赋值,那么它们的值如何呢?经过测试,发现它们的值分别为1、2、4、8、16、32和3。这样的常量定义方法是通过iota来实现的。
iota的定义在/src/builtin/builtin.go里,它跟true 和 false一样都是untyped int类型。 它是预定义的标识符,作用在const块里,从0开始索引。可以把iota看作是常量计数器,而iota也只能用于常量表达式,使用它可以简化递增数值的定义,
可以对iota做一点小实验: 若有下面的常量定义:
分别输出ID0、ID1、ID2可以得到结果为0、1、2。 或者定义常量如下:
分别输出ID0、ID1、ID2可以得到结果为1、2、4。 而在下面的情况里,输出会略有不同:
分别输出ID0、ID1、ID2、ID3、ID4、ID5可以得到结果为0、1、2、6、7、8。这说明当ID3赋值的时候,iota的值已经变成了3,而非开始时的0。iota值的改变由编译器完成。
接下来log包里定义了类型Logger如下:
它表示一个活动的日志对象,给io.Writer生成多行输出。每次记录都简单地调用io.Writer的write方法。一个Logger可以被多个goroutines同步执行。
在Logger的定义里,其中:
mu是sync包提供的Mutex锁,为来提供原子写,并保护下列域。
prefix是在每条记录的前缀。
flag是标识属性。
out是输出目标。
buf是为了用来输出的块缓冲。
log包里其它的内容是Logger的一些方法定义:
有构造函数:
通过它得到Logger的新实例。
有专门设置输出的方法:
它用来将Logger的out域赋值为w。io.Writer是个接口类型,任何实现了方法Write(p []byte)(n int, err error)的类型都可以在这里使用。
有辅助函数,将整型转换为定长十进制ASCII码。赋予负数宽度来防止左侧补0(zero-padding)
在这里它将输入的整型i转换为ASCII码,并逆序存储在buf里。
有格式化输出前缀方法:
函数里通过l.flag与前面定义的常量标识求与,以确定是否设定该标识。
有日志事件的输出方法,
输出字符串s包含了被flags指定的前缀。这里在获取函数调用信息的时候要先解锁,否则开销会比较大。调用深度被用来恢复PC(Program Counter程序计数器)。一般情况下,所有的默认深度都是2。如果字符串的最后一个字符不是换行符'\n',会自动追加一个。
有用来打印输出的各种方法:
相比Print,Fatal仅比它多了os.Exit(1)调用。
而panic则稍有不同:
Panic()里调用的panic()是内建函数,定义在/usr/local/go/src/builtin/builtin.go里。它会终止当前goroutine的正常执行,使其立即停止。
假如函数F调用了panic之后F会立马结束,返回到调用F的函数里。又假设调用F的函数为G,这时对F的调用就像是直接对panic的调用一样,又会将G停止。如此直到所有的goroutine停止,程序结束,错误被输出。终止时的操作可以由内建函数recover()来控制。
最后设置标识位的函数,以及设置前缀的函数:
上面就是Golang的log包的全貌,了解到了Golang里Logger的定义及实现,发现在Logger的方法里,凡是涉及到更改Logger对象成员的处理,均被加锁。另外Logger并没有提供对于消息的严重级别(日志级别)、消息的分类的相应操作。整体够用但是还不完善,这为后面自定义日志框架留下了空间。
4.问题
Logger定义里的Mutex锁如何保护后面的域?
在获取函数调用信息时,要先解锁以减少开销。这部分开销是怎么造成的?
5.脑图
6.参考资料
责任编辑:
您可能感兴趣的文章:
golang日志服务器_深扒GO日志 | (一)从Go语言的日志包说起
golang 日志分析_每日一库之 logrus 日志使用教程
从零入门 Serverless | 函数计算的可观测性
Go 开发关键技术指南 | 为什么你要选择 Go?(内含超全知识大图)
从零入门Serverless|函数计算的可观测性
GO语言-文件版日志系统
MYSQL启用日志,查看日志,利用Mysqlbinlog工具恢复MySQL数据库
mysql主从复制配置与原理分析
教你删除MYSQl的BIN-LOG日志
学习mysql binlog日志清理