第十部分: 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",
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作為本文中程式碼使用的日誌記錄。
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, Logstash, Fluentd等等。
- Loggly: 這是一個日誌資料管理的SaaS解決方案。使用它可以將日誌從整個基礎設施的深處帶到一個可以跟蹤活動和分析趨勢的地方。最重要的是,Loggly是一種託管服務,你不需要任何額外的硬體或軟體就可以使用Loggly,並且可以動態根據操作進行擴充套件。
- 英文第10部分
- 系列文章首頁
- 下一節