第十部分: Go微服务 - 集中化日志
本文介绍我们的Go微服务基于Logrus、Docker Gelf日志驱动以及Loggly服务(Logging as a Service)的日志策略。
- Logrus: Go语言中的结构化、可插拔日志功能。
- Docker Gelf日志驱动器: 是一种方便的格式,可以被很多工具理解,例如Graylog,Logstash,Fluentd等等。
- Loggly: 这是一个日志数据管理的SaaS解决方案。使用它可以将日志从整个基础设施的深处带到一个可以跟踪活动和分析趋势的地方。最重要的是,Loggly是一种托管服务,你不需要任何额外的硬件或软件就可以使用Loggly,并且可以动态根据操作进行扩展。
简介
日志。你根本不知道你会失去多少,直到你这样做。为你的团队制定关于记录什么,什么时候记录以及如何记录,可能是产生可维护应用程序的关键因素之一。然后,微服务就发生了。
虽然对于单体应用来说处理一些日志文件通常都是可管理的(虽然存在例外...),但考虑到对于基于微服务的应用程序来说,同样可能使用数百个甚至数千个服务容器来产生日志。如果没有一个搜集和汇总日志的解决方案,基本上考虑不了变得更大的时候的问题了。
谢天谢地,很多聪明人已经想到这一点 - 叫做ELK的著名栈可能就是开源社区中最著名的一个。它是ElasticSearch,LogStash和Kibana构成的Elastic Stack(ELK),推荐可以在驻机和云主机上使用。然而ELK的文章遍地都是,所以本文我们基于四个部分来探索集中日志记录解决方案LaaS:
- Logrus: Go语言的日志框架。
- Docker Gelf驱动器: Greylog Extended Log格式的日志驱动器。
- Gelftail: 本文中将要构建的轻量级日志聚合器。
- Loggly: 一个LaaS提供商。 提供类似的管理和作用日志数据作为类似服务的能力。
解决方案概览
源代码
https://github.com/walkerqiao...
通常我们的Go微服务直到现在都是使用的fmt或log包打的日志,一般都输出到stdout或stderr。我们希望能更好的控制日志级别和格式。在Java世界,我们很多(大部分)都使用log4j、logback、slf4j之类的框架来处理日志。本文我们选择使用Logrus作为日志API,它大体上提供了我刚提到的关于日志级别、格式化还是钩子API同样类型的功能。
使用logrus
使用logrus很好的特性就是它实现了目前我们用于日志的fmt,log相同的接口。这就意味着我们或多或少的可以使用logrus作为无需太多改变的替换。首先确保你的GOPATH设置正确,然后使用下面的命令获取logrus:
go get github.com/sirupsen/logrus
更新代码
我们使用老派方式来操作。对于common,accountservice,vipservice分别使用IDE或文本编辑器做全局搜索替换。 fmt.和log.替换为logrus.。那么现在就会出现很多logrus.Println和logrus.Pringf调用。 即便通过这样的方式不错,但是我还是建议使用logrus更一般化的严格支持,例如INFO,WARN,DEBUG之类的。例如:
fmt log logrus Println Println Infoln printf Printf Infof Error Errorln
有一个例外,fmt.Error用于产生错误实例。不要替换fmt.Error。
使用goimports更新imports
鉴于我们已经使用logrus替换了大量的log.Println和fmt.Println(和其他日志函数),我们就有大量无用的import,这样会产生编译错误。与其一个文件一个文件的修改,不如我们使用一个小工具来帮我们做到这些。 这个工具就是goimports,可以通过下面的方式安装:
go get golang.org/x/tools/cmd/goimports
安装完后,这个命令工具在$GOPATH/bin目录。 接下来可以进入accountservice,vipservice,执行下面的命令:
cd $GOPATH/src/github.com/callistaenterprise/goblog/accountservice $GOPATH/bin/goimports -w **/*.go
执行goimports会自动为所有的文件添加未import的语句,同时会去掉无用的import语句。
然后可以对我们所有的微服务代码进行这样的操作,包括common目录。
然后运行go build确保每个服务都能正常编译。
配置logrus
如果我们不配置logrus,它将直接以纯文本的形式输出日志内容。例如:
logrus.Infof("Starting our service...") // 输出内容 INFO[0000] Starting our service...
这里0000是服务启动的时间。不是我们所想要的,我想要一个datetime类型的。 因此我们需要提供一个格式。
func init() { logrus.SetFormatter(&logrus.TextFormatter{ TimestampFormat: "2006-01-02T15:04:05.000",FullTimestamp: true,}) }
init函数最适合干这种事情了。设置之后,我们的日志输出就如下所示:
INFO[2017-07-17T13:22:49.164] Starting our service...
要比刚才好些。 然而,在我们微服务用例中,我们希望日志日志语句更容易解析,这样我们可以将它们发送到我们的选择的LaaS上,让日志索引、排序、聚合等等。因此当我们不在单例模式(-profile=dev)下运行微服务时,我们将希望使用JSON格式。
我们再次修改init函数, 这样它将使用json格式替代除非有-profile=dev标志传入。
func init() { profile := flag.String("profile","test","Environment profile") if *profile == "dev" { logrus.SetFormatter(&logrus.TextFormatter{ TimestampFormat: "2006-01-02T15:04:05.000",}) } else { logrus.SetFormatter(&logrus.JSONFormatter{}) } }
{"level":"info","msg":"Starting our service...","time":"2017-07-17T16:03:35+02:00"}
就是这样,你可以阅读logrus的文档获得更全面的例子。
应该清楚的是,标准的logrus日志不支持来你在其他平台使用的细粒度的控制,例如通过配置修改某些给定包以调试模式进行日志。然而,可以创建范围话的日志实例,使得更细粒度的配置成为可能,例如:
var LOGGER = logrus.Logger{} // <-- Create logger instance func init() { // Some other init code... // Example 1 - using global logrus API logrus.Infof("Successfully initialized") // Example 2 - using logger instance LOGGER.Infof("Successfully initialized") }
这里只是示例代码,仓库中是不存在的。
通过使用LOGGER实例,就可以配置更细粒度的应用级别的日志。然而,我已经选择使用全局日志,使用logrus.X作为本文中代码使用的日志记录。
2. Docker Gelf驱动器
Gelf是什么? 它是Greylog Extended Log Format的首字母缩写,是logstash的标准格式。
基本上来说,他的日志数据以JSON格式的数据。在Docker上下文,我们可以配置Docker Swarm模式服务使用各种不同驱动器来进行日志, 实际上意味着在某个容器中写到stdout,stderr的东西会被Docker引擎捡起,交给日志驱动器来处理。 这些处理包括添加大量容器、Swarm节点、服务等相关的元数据。这些都是针对docker的。大概样子如下:
{ "version":"1.1","host":"swarm-manager-0","short_message":"Starting HTTP service at 6868","timestamp":1.487625824614e+09,"level":6,"_command":"./vipservice-linux-amd64 -profile=test","_container_id":"894edfe2faed131d417eebf77306a0386b43027e0bdf75269e7f9dcca0ac5608","_container_name":"vipservice.1.jgaludcy21iriskcu1fx9nx2p","_created":"2017-02-20T21:23:38.877748337Z","_image_id":"sha256:1df84e91e0931ec14c6fb4e559b5aca5afff7abd63f0dc8445a4e1dc9e31cfe1","_image_name":"someprefix/vipservice:latest","_tag":"894edfe2faed" }
让我们看看如何修改copyall.sh脚本中的docker service create让它支持Gelf驱动的:
docker service create \ --log-driver=gelf \ --log-opt gelf-address=udp://192.168.99.100:12202 \ --log-opt gelf-compression-type=none \ --name=accountservice --replicas=1 --network=my_network -p=6767:6767 someprefix/accountservice
-
--log-driver=gelf
: 告诉Docker使用gelf驱动器。 -
--log-opt gelf-address=udp://192.168.99.100:12202
: 告诉Docker朝哪里发送所有日志语句。在gelf的情况中,我们使用UDP协议,并告诉Docker将日志语句发送定义的IP:port的服务。这个服务一般就是类似logstash的东西,但是我们这个例子中,我们使用了下一节构建的轻量日志聚合服务。 -
--log-op gelf-compression-type
: 告诉Docker在发送日志语句之前是否需要压缩。 为了简单起见,本文不对日志语句进行压缩。
3. 日志集合和使用Gelftail进行日志聚合
4. Loggly
总结
本文我们看了集中化日志方面的东西 - 为什么它很重要,如何对Go微服务进行格式化日志,如何使用容器编排里边的日志驱动器在日志状态上传到LaaS提供商之前对日志进行预处理。
下一节,是时候使用Netflix Hystrix为我们微服务添加断路器和弹性(resilience)。
中英文对照
- 日志即服务: Logging as a Service(LaaS).
- ELK: Electic Search、LogStash、Kibana三个首字母组合。通常三个配合使用,构成ELK协议栈。
参考链接
- Logrus: Go语言中的结构化、可插拔日志功能。
- Elastic Stack: 集中化、转换和储存你的数据。是一个开源的、服务端数据处理流水线,它同时从多个源中采集数据,转换它,然后将它发送到你最喜欢的Stash中(对于我们来说自然是Elastic Search)。
- Kibana: Kibana可以让你可视化你的Elastic Search(弹性搜索)数据,并浏览Elastic Stack,这样你就可以了解为什么在凌晨两点的时候被分页来理解雨季对你季度数字的影响。
- ElasticSearch: Elastic Stack的核心。具有解决不断壮大用例的分布式、RESTful搜索和分析引擎能力。作为Elastic Stack的核心,它集中化存储数据,因此你可以发现预期和发现意外情况。
- Docker Gelf日志驱动器: 是一种方便的格式,可以被很多工具理解,例如Graylog,Fluentd等等。
- Loggly: 这是一个日志数据管理的SaaS解决方案。使用它可以将日志从整个基础设施的深处带到一个可以跟踪活动和分析趋势的地方。最重要的是,Loggly是一种托管服务,你不需要任何额外的硬件或软件就可以使用Loggly,并且可以动态根据操作进行扩展。
- 英文第10部分
- 系列文章首页
- 下一节