本文介绍咱们的Go微服务基于Logrus、Docker Gelf日志驱动以及Loggly服务(Logging as a Service)的日志策略。html
日志。你根本不知道你会失去多少, 直到你这样作。为你的团队制定关于记录什么,何时记录以及如何记录,多是产生可维护应用程序的关键因素之一。而后,微服务就发生了。linux
虽然对于单体应用来讲处理一些日志文件一般都是可管理的(虽然存在例外...), 但考虑到对于基于微服务的应用程序来讲,一样可能使用数百个甚至数千个服务容器来产生日志。若是没有一个搜集和汇总日志的解决方案,基本上考虑不了变得更大的时候的问题了。git
谢天谢地,不少聪明人已经想到这一点 - 叫作ELK的著名栈可能就是开源社区中最著名的一个。它是ElasticSearch, LogStash和Kibana构成的Elastic Stack(ELK), 推荐能够在驻机和云主机上使用。然而ELK的文章遍地都是,因此本文咱们基于四个部分来探索集中日志记录解决方案LaaS:github
https://github.com/walkerqiao...golang
一般咱们的Go微服务直到如今都是使用的fmt或log包打的日志,通常都输出到stdout或stderr。咱们但愿能更好的控制日志级别和格式。在Java世界,咱们不少(大部分)都使用log4j、logback、slf4j之类的框架来处理日志。本文咱们选择使用Logrus做为日志API, 它大致上提供了我刚提到的关于日志级别、格式化仍是钩子API一样类型的功能。docker
使用logrus很好的特性就是它实现了目前咱们用于日志的fmt, log相同的接口。这就意味着咱们或多或少的可使用logrus做为无需太多改变的替换。首先确保你的GOPATH设置正确,而后使用下面的命令获取logrus:json
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。elasticsearch
鉴于咱们已经使用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.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", FullTimestamp: true, }) } 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做为本文中代码使用的日志记录。
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在发送日志语句以前是否须要压缩。 为了简单起见,本文不对日志语句进行压缩。本文咱们看了集中化日志方面的东西 - 为何它很重要,如何对Go微服务进行格式化日志,如何使用容器编排里边的日志驱动器在日志状态上传到LaaS提供商以前对日志进行预处理。
下一节,是时候使用Netflix Hystrix为咱们微服务添加断路器和弹性(resilience)。