本文介紹咱們的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)。