关于收集,标准化和集中化处理Golang日志的一些建议
依赖分布式系统的公司组织和团队经常使用Go
语言编写其应用程序,以利用Go
语言诸如通道和goroutine
之类的并发功能。如果你负责研发或运维Go
应用程序,则考虑周全的日志记录策略可以帮助你了解用户行为,定位错误并监控应用程序的性能。
这篇文章将展开聊一些用于管理Go
日志的工具和技术。我们将首先考虑要使用哪种日志记录包来满足各种记录要求。然后会介绍一些使日志更易于搜索和可靠,减少日志资源占用以及使日志消息标准化的技术。
日志包的选择
Go
标准库的日志库非常简单,仅仅提供了print
,panic
和fatal
三个函数对于更精细的日志级别、日志文件分割以及日志分发等方面并没有提供支持. 所以催生了很多第三方的日志库,流行的日志框架包括logrus
、zap
、glog
等。我们先来大致看下这些日志库的特点再来根据实际应用情况选择合适的日志库。
log标准库
Go
的内置日志记录库(log
)带有一个默认记录器(logger
),该记录器可写入标准错误并自动向记录中添加时间戳,而无需进行配置。你可以使用它日志用于本地开发,和试验性的代码段。这时从代码中获得快速反馈可能比生成丰富结构化的日志更为重要。
logrus
logrus
是一个为结构化日志记录而设计的日志记录包,非常适合以JSON
格式记录日志。 JSON
格式使机器可以轻松解析Go
日志。而且,由于JSON
是定义明确的标准,因此通过包含新字段可以轻松地添加上下文,解析器能够自动提取它们。
使用logrus
,可以使用功能WithFields
定义要添加到JSON日志中的标准字段,如下所示。然后,可以在不同日志级别调用记录器,例如Info()
,Warn()
和Error()
。 logrus
库将自动以JSON
格式写入日志,并插入标准字段以及您即时定义的所有字段。
package main
import (log "github.com/sirupsen/logrus"
)func main() {log.SetFormatter(&log.JSONFormatter{})standardFields := log.Fields{"hostname": "staging-1","appname": "foo-app","session": "1ce3f6v",}requestLogger := log.withFields(standardFields)requestLogger.WithFields(log.Fields{"string": "foo", "int": 1, "float": 1.1}).Info("My first ssl event from Golang")}
生成的日志将在JSON
对象中包括消息,日志级别,时间戳、标准字段以及调用记录器即时写入的字段:
{"appname":"foo-app","float":1.1,"hostname":"staging-1","int":1,"level":"info","msg":"My first ssl event from Golang","session":"1ce3f6v","string":"foo","time":"2019-03-06T13:37:12-05:00"}
glog
glog
允许启用或禁用特定级别的日志记录,这对于在开发和生产环境之间切换时保持检查日志量很有用。它使您可以在命令行中使用标志(例如,-v表示详细信息)来设置运行代码时的日志记录级别。然后,可以在if
语句中使用V()
函数仅在特定日志级别上写入Go
日志。功能Info()
,Warning()
,Error()
和Fatal()
分别指定日志级别0
到3
if err != nil && glog.V(2){glog.Error(err)}
日志库的选择
上面分析了,标准库的log
只适合非项目级别的代码片段的快速验证和调试。logrus
在结构化日志上做的最好,有利于日志分析。glog
可以减少日志占用的磁盘空间。不过相比产生的日志占用空间大的问题,利于分析的日志给应用产品带来的价值更大,所以logrus
使用的更多一些。很多开源项目,如Docker
,Prometheus
等都是用了logrus
来记录他们的日志。
logrus的使用介绍
logrus
是目前Github
上star数量最多的日志库,目前(2020.03)star
数量为14000+,fork
数为1600+。logrus
功能强大,性能高效,而且具有高度灵活性,提供了自定义插件的功能。很多开源项目,如Docker
,Prometheus
等都是用了logrus
来记录他们的日志。
logrus
完全兼容Go
标准库日志模块,拥有六种日志级别:debug
、info
、warn
、error
、fatal
和panic
,这是Go
标准库日志模块的API的超集.如果你的项目使用标准库日志模块,完全可以以最低的代价迁移到logrus
上.- 可扩展的
Hook
机制:允许使用者通过hook
的方式将日志分发到任意地方,如本地文件系统、标准输出、logstash
、elasticsearch
或者mq
等。 logrus
内置了两种日志格式,JSONFormatter
和TextFormatter
还可以自己动手实现接口Formatter,来定义自己的日志格式。Field
机制:logrus
鼓励通过Field
机制进行精细化的、结构化的日志记录,而不是通过冗长的消息来记录日志。Entry
:logrus.WithFields
会自动返回一个*Entry
,Entry
会自动向日志记录里添加记录创建的时间time
字段。
基本用法
logrus
与Go
标准库日志模块完全兼容, logrus
可以通过简单的配置,来定义输出、格式或者日志级别等。
package mainimport ("os"log "github.com/sirupsen/logrus"
)func init() {// 设置日志格式为json格式log.SetFormatter(&log.JSONFormatter{})// 设置将日志输出到指定文件(默认的输出为stderr,标准错误)// 日志消息输出可以是任意的io.writer类型logFile := ...file, _ := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)log.SetOutput(file)// 设置只记录日志级别为warn及其以上的日志log.SetLevel(log.WarnLevel)
}func main() {log.WithFields(log.Fields{"animal": "walrus","size": 10,}).Info("A group of walrus emerges from the ocean")log.WithFields(log.Fields{"omg": true,"number": 122,}).Warn("The group's number increased tremendously!")log.WithFields(log.Fields{"omg": true,"number": 100,}).Fatal("The ice breaks!")
}
自定义Logger
如果想在一个应用里面向多个地方写log
,可以创建多个记录器Logger
实例。
package mainimport ("github.com/sirupsen/logrus""os"
)// logrus提供了New()函数来创建一个logrus的实例.
// 项目中,可以创建任意数量的logrus实例.
var log = logrus.New()func main() {// 为当前logrus实例设置消息的输出,同样地,// 可以设置logrus实例的输出到任意io.writerlog.Out = os.Stdout// 为当前logrus实例设置消息输出格式为json格式.// 同样地,也可以单独为某个logrus实例设置日志级别和hook,这里不详细叙述.log.Formatter = &logrus.JSONFormatter{}log.WithFields(logrus.Fields{"animal": "walrus","size": 10,}).Info("A group of walrus emerges from the ocean")
}
Fields
logrus
不推荐使用冗长的消息来记录运行信息,它推荐使用Fields
来进行精细化的、结构化的信息记录. 例如下面的记录日志的方式:
log.Fatalf("Failed to send event %s to topic %s with key %d", event, topic, key)
在logrus
中不太提倡,logrus
鼓励使用以下方式替代之:
log.WithFields(log.Fields{"event": event,"topic": topic,"key": key,
}).Fatal("Failed to send event")
WithFields
可以规范使用者按照其提倡的方式记录日志。但是WithFields
依然是可选的,因为某些场景下,确实只需要记录一条简单的消息。
Default Fields
通常,在一个应用中、或者应用的一部分中,始终附带一些固定的记录字段会很有帮助。比如在处理用户HTTP
请求时,上下文中所有的日志都会有request_id
和user_ip。
为了避免每次记录日志都要使用:
log.WithFields(log.Fields{“request_id”: request_id, “user_ip”: user_ip})
我们可以创建一个logrus.Entry
实例,为这个实例设置默认Fields
,把logrus.Entry
实例设置到记录器Logger
,再记录日志时每次都会附带上这些默认的字段。
requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
requestLogger.Info("something happened on that request") # will log request_id and user_ip
requestLogger.Warn("something not great happened")
Hook接口
logrus
最令人心动的功能就是其可扩展的HOOK
机制。通过在初始化时为logrus
添加hook
,logrus
可以实现各种扩展功能.
logrus
的hook
接口定义如下,其原理是每次写入日志时拦截修改logrus.Entry
.
// logrus在记录Levels()返回的日志级别的消息时会触发HOOK,
// 按照Fire方法定义的内容修改logrus.Entry.
type Hook interface {Levels() []LevelFire(*Entry) error
}
一个简单自定义hook
如下,DefaultFieldHook
定义会在所有级别的日志消息中加入默认字段appName=”myAppName”
。
type DefaultFieldHook struct {
}func (hook *DefaultFieldHook) Fire(entry *log.Entry) error {entry.Data["appName"] = "MyAppName"return nil
}func (hook *DefaultFieldHook) Levels() []log.Level {return log.AllLevels
}
hook
的使用也很简单,在初始化前调用log.AddHook(hook)
添加相应的hook
即可。Hook
比较常见的用法是把指定错误级别的日志记录消息提醒发送到邮件组或者错误监控系统(比如sentry
),起到主动错误通知的作用。
logrus
官方仅仅内置了syslog
的hook
。但Github
有很多第三方的hook
可供使用。比方刚才说的sentry
相关的hook
。
sentry-hook
Sentry
是一个错误监控系统,可以使用厂商的服务也可以在自己的服务器上搭建Sentry
。模式跟GitLab
很像,也是提供一键安装包。为应用注册Sentry
后会分配一个DSN
用于连接Sentry
服务。
import ("github.com/sirupsen/logrus""github.com/evalphobia/logrus_sentry"
)func main() {log := logrus.New()hook, err := logrus_sentry.NewSentryHook(YOUR_DSN, []logrus.Level{logrus.PanicLevel,logrus.FatalLevel,logrus.ErrorLevel,})if err == nil {log.Hooks.Add(hook)}
}
logrus 是线程安全的
默认情况下,Logger
受mutex
保护,以进行并发写入。当钩子被调用并且日志被写入时,mutex
会被保持。如果确定不需要这种锁,则可以调用logger.SetNoLock()
禁用该锁。
不需要锁的情况包括:
- 没有注册
hook
,或者hook
调用是线程安全的。 - 写入
logger.Out
是线程安全的,比如logger.Out
已经被锁保护或者logger.Out
是一个以Append
模式打开的文件句柄。
日志写入和存储的一些建议
选择了项目使用的日志库后,您还需要计划在代码中调用记录器的位置,如何存储日志。在本部分中,将推荐一些整理Go
日志的最佳实践,他们包括:
- 从的主应用程序流程而不是
goroutine
中调用记录器。 - 将日志从应用程序写入本地文件,即使以后再将其发送到日志集中化处理平台也是如此。
- 定义日志的标准化默认字段
- 将日志发送到日志处理平台,以便进行分析和汇总。
- 使用
HTTP
标头携带分布式唯一ID记录微服务中的用户行为。
避免在goroutine中使用日志记录器
避免创建自己的goroutine
来处理写日志有两个原因。首先,它可能导致并发问题,因为记录器的副本将尝试访问相同的io.Writer
。其次,日志记录库通常会自己启动goroutine
,在内部管理所有并发问题,而启动自己的goroutine
只会造成干扰。
总是将日志写入文件
即使将日志发送到中央日志平台,我们也建议您先将日志写到本地计算机上的文件中。这确保您的日志始终在本地可用,并且不会在网络中丢失。此外,写入文件意味着您可以将写入日志的任务与将日志发送到中央日志平台的任务分开。您的应用程序本身无需建立连接或流式传输日志给日志平台,您可以将这些任务交给专业的软件处理,比如使用Elasticsearch
索引日志数据的话,那么就可以用Logstash
从日志文件里抽取日志数据。
使用日志处理平台集中处理日志
如果您的应用程序部署在多个主机群集中,应用的日志会分散到不同机器上。日志从本地文件传递到中央日志平台,以便进行日志数据的分析和汇总。关于日志处理服务的选择,开源的日志处理服务有ELK
,各个云服务厂商也有自己的日志处理服务,根据自身情况选择即可,尽量选和云服务器同一厂商的日志服务,这样不用消耗公网的流量。
使用唯一ID跨服务跟踪Go日志
对于构建在分布式系统之上的应用,一个请求可能会流经多个服务,每个服务都会自己记录日志。这种情况下为了查询请求对应的日志,通常的解决方案是在请求头中携带唯一ID,分布式系统中所有服务的日志记录器中增加唯一ID字段,这样每条写入的日志里都会有HTTP
请求的唯一ID。在统一日志平台中分析日志时,通过上游服务日志记录的请求唯一 ID 即可查询到该请求在下游所有服务中产生的日志。
参考链接:
https://www.datadoghq.com/blo...
https://github.com/sirupsen/l...
关于收集,标准化和集中化处理Golang日志的一些建议相关推荐
- 关于如何收集,标准化和集中化处理Golang日志的一些建议
依赖分布式系统的公司组织和团队经常使用Go语言编写其应用程序,以利用Go语言诸如通道和goroutine之类的并发功能.如果你负责研发或运维Go应用程序,则考虑周全的日志记录策略可以帮助你了解用户行为 ...
- 数据中心模块化、标准化、预制化、定制化、智能化……傻傻分不清楚?大咖来帮你!...
模块化.标准化.预制化.定制化.智能化--每当看到这些名词堆在一起,就像回到了上学时代,对着书本上一个个名词解释,睁大眼睛努力辨别它们到底有什么不同. 为此,我们特地向几位来自施耐德电气的资深数据大牛 ...
- 【阿里云课程】详解深度学习优化:参数初始化,激活函数,标准化,池化
大家好,继续更新有三AI与阿里天池联合推出的深度学习系列课程,本次更新内容为第6课中两节,介绍如下: 第1节:激活函数与参数初始化 第1节课内容为:卷积神经网络的上篇,简单介绍卷积神经网络的生物学机制 ...
- 四大科技支撑大健康生态 360保险输出标准化、定制化两大核心力
"新基建"加速布局下,人工智能.大数据等新兴技术也逐渐渗透至保险行业.数字化发展浪潮下,互联网保险平台360保险已经抢先入场.在近日举行的360保险新战略暨新产品发布会上,创始人唐 ...
- 运维标准化与流程化建设
运维标准化与流程化建设 当下企业很多都热衷于建设运维自动化.智能化,通过技术革新代替繁杂的手工运维,提高生产效率的同时最大程度的减少人为失误.但是如何建设自动化运维,在不同的企业有着不同的建设方法和技 ...
- 规范化、归一化、标准化、中心化、正则化
规范化.归一化.标准化.中心化 规范化指的是对数据进行规范处理,包含归一化.标准化和中心化.归一化包括最大最小归一化.均值归一化. 维基百科中对规范化的方法有定义,详细可见 https://en.wi ...
- 归一化、标准化、中心化
一.概念 归一化:把数据变成 [0,1] 或 [-1,1] 之间的小数.主要是为了数据处理方便提出来的,把数据映射到0-1范围之内处理,更加便捷快速.是一种简化计算的方式,将有量纲的表达式,经过变换, ...
- 第五章:量化研究专题(第五篇:数据处理专题:去极值、标准化、中性化 )
导语:一般的数据预处理中常提及到三类处理:去极值.标准化.中性化.我们将向大家讲述这常见的 三种数据处理操作. 一.去极值 在分析上市公司当季净利润同比增长率数据时,我们往往会被其中一些公司的数据干扰 ...
- golang 日志分析_容器日志采集利器:Filebeat深度剖析与实践
在云原生时代和容器化浪潮中,容器的日志采集是一个看起来不起眼却又无法忽视的重要议题.对于容器日志采集我们常用的工具有filebeat和fluentd,两者对比各有优劣,相比基于ruby的fluentd ...
最新文章
- 使用Optuna的XGBoost模型的高效超参数优化
- This is my first time to write blog
- 前端开发课件 202002
- 数据库封装 sql server mysql_mysql操作数据库进行封装实现增删改查功能
- 2.2 LayoutInflater 加载布局文件源码
- UVAlive3708 UVA1388 POJ3154 Graveyard【水题】
- linux下tomcat部署java web项目_在linux下用tomcat部署java web项目的过程与注意事项
- 跟随我在oracle学习php(42)
- mysql 判断指定条件数据存不存在,不存在则插入
- 兄弟连Linux学习笔记
- PHPExcel 插件使用详解
- 计算机word表格转换,怎么把Word表格转换成Excel表格
- 在阿里云服务器上安装常用应用
- 实现JSON在线美化(格式化)、JSON转CSV、CSV转XML工具-toolfk程序员工具网
- 20162316刘诚昊 第五周作业
- 计算几何(立体几何)基础篇
- [转载]烟台话--普通话对照表
- 数据预处理系列:(二)创建试验样本数据
- 抽奖小程序制作开发 助手力参与抽奖 小程序设计微信大屏幕公众号抽奖关注助手租用 助力 定时开奖小程序365抽奖助手小程序吸粉裂变引流神器商家拓客曝光小程序
- vulnhub View2akill