golang日志库
golang标准库的日志框架非常简单,仅仅提供了print,panic和fatal三个函数对于更精细的日志级别、日志文件分割以及日志分发等方面并没有提供支持。所以催生了很多第三方的日志库,但是在golang的世界里,没有一个日志库像slf4j那样在Java中具有绝对统治地位。golang中,流行的日志框架包括logrus、zap、zerolog、seelog等。
logrus是目前Github上star数量最多的日志库,目前(2018.08,下同)star数量为8119,fork数为1031。logrus功能强大,性能高效,而且具有高度灵活性,提供了自定义插件的功能。很多开源项目,如docker,prometheus等,都是用了logrus来记录其日志。
zap是Uber推出的一个快速、结构化的分级日志库。具有强大的ad-hoc分析功能,并且具有灵活的仪表盘。zap目前在GitHub上的star数量约为4.3k。
seelog提供了灵活的异步调度、格式化和过滤功能。目前在GitHub上也有约1.1k。
logrus特性
logrus具有以下特性:
- 完全兼容golang标准库日志模块:logrus拥有六种日志级别:debug、info、warn、error、fatal和panic,这是golang标准库日志模块的API的超集。如果您的项目使用标准库日志模块,完全可以以最低的代价迁移到logrus上。
- 可扩展的Hook机制:允许使用者通过hook的方式将日志分发到任意地方,如本地文件系统、标准输出、logstash、elasticsearch或者mq等,或者通过hook定义日志内容和格式等。
- 可选的日志输出格式:logrus内置了两种日志格式,JSONFormatter和TextFormatter,如果这两个格式不满足需求,可以自己动手实现接口Formatter,来定义自己的日志格式。
- Field机制:logrus鼓励通过Field机制进行精细化的、结构化的日志记录,而不是通过冗长的消息来记录日志。
- logrus是一个可插拔的、结构化的日志框架。
logrus的使用
第一个示例
最简单的使用logrus的示例如下:
上面代码执行后,标准输出上输出如下:
log“github.com/sirupsen/logrus”
Logger
logger
Fields
Fields
WithFieldsWithFields
Fieldrequest_iduser_iplog.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})logrus.EntryFieldslogrus.Entry
Hook
logrus最令人心动的功能就是其可扩展的HOOK机制了,通过在初始化时为logrus添加hook,logrus可以实现各种扩展功能。
Hook接口
logrus的hook接口定义如下,其原理是每此写入日志时拦截,修改logrus.Entry。
DefaultFieldHookappName="myAppName"
log.AddHook(hook)hook
logrus官方仅仅内置了syslog的hook。
此外,但Github也有很多第三方的hook可供使用,文末将提供一些第三方HOOK的连接。
问题与解决方案
尽管logrus有诸多优点,但是为了灵活性和可扩展性,官方也削减了很多实用的功能,例如:
- 没有提供行号和文件名的支持
- 输出到本地文件系统没有提供日志分割功能
- 官方没有提供输出到ELK等日志处理中心的功能
但是这些功能都可以通过自定义hook来实现。
记录文件名和行号
logrus.Entryruntime
runtimeCaller(skip int)runtime.Callerruntime.Caller()
runtime.FuncForPC(pc uintptr) *Funcpc
runtime.Caller()sirupsensiupsenrpcruntime.FuncForPC()sourcelogrus.Entry
time="2018-08-11T19:10:15+08:00" level=warning msg="postgres_exporter is ready for scraping on 0.0.0.0:9295..." source="postgres_exporter/main.go:60:main()"
time="2018-08-11T19:10:17+08:00" level=error msg="!!!msb info not found" source="postgres/postgres_query.go:63:QueryPostgresInfo()"
time="2018-08-11T19:10:17+08:00" level=error msg="get postgres instances info failed, scrape metrics failed, error:msb env not found" source="collector/exporter.go:71:Scrape()"
日志本地文件分割
file-rotatelogsfile-rotatelogs
使用上述本地日志文件切割的效果如下:
将日志发送到elasticsearch
将日志发送到elasticsearch是很多日志监控系统的选择,将logrus日志发送到elasticsearch的原理是在hook的每次fire调用时,使用golang的es客户端将日志信息写到elasticsearch。elasticsearch官方没有提供golang客户端,但是有很多第三方的go语言客户端可供使用,我们选择elastic。elastic提供了丰富的文档,以及Java中的流式接口,使用起来非常方便。
考虑到logrus的Fields机制,可以实现如下数据格式:
Hostlogrus.Entry
从Elasticsearch查询得到日志存储,效果如下:
将日志发送到其他位置
将日志发送到日志中心也是logrus所提倡的,虽然没有提供官方支持,但是目前Github上有很多第三方hook可供使用:
logrus_amqp:Logrus hook for Activemq。
logrus-logstash-hook:Logstash hook for logrus。
mgorus:Mongodb Hooks for Logrus。
logrus_influxdb:InfluxDB Hook for Logrus。
logrus-redis-hook:Hook for Logrus which enables logging to RELK stack (Redis, Elasticsearch, Logstash and Kibana)。
等等,上述第三方hook我这里没有具体验证,大家可以根据需要自行尝试。
其他注意事项
Fatal处理
Fatalos.Exit(1)fatal handlerlogrus.RegisterExitHandler(handler func() {} )os.Exit(1)fatal handler
线程安全
logger.SetNoLock()
logger.Outlogger.OutO_APPEND