如何使用 Golang 日誌監控你的應用程式?

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
導讀 你是否厭煩了那些使用複雜語言編寫的、難以部署的、總是在不停構建的解決方案?Golang 是解決這些問題的好方法,它和 C 語言一樣快,又和 Python 一樣簡單。

但是你是如何使用 Golang 日誌監控你的應用程式的呢?Golang 沒有異常,只有錯誤。因此你的第一印象可能就是開發 Golang 日誌策略並不是一件簡單的事情。不支援異常事實上並不是什麼問題,異常在很多程式設計語言中已經失去了其異常性:它們過於被濫用以至於它們的作用都被忽視了。

在進一步深入之前,我們首先會介紹 Golang 日誌的基礎,並討論 Golang 日誌標準、中繼資料意義、以及最小化 Golang 日誌對效能的影響。通過日誌,你可以追蹤使用者在你應用中的活動,快速識別你項目中失效的組件,並監控總體效能以及使用者體驗。

I. Golang 日誌基礎

使用 Golang “log” 庫

Golang 給你提供了一個稱為 “log” 的原生日誌庫。它的日誌器完美適用於追蹤簡單的活動,例如通過使用可用的選項在錯誤資訊之前添加一個時間戳記。

下面是一個 Golang 中如何記錄錯誤記錄檔的簡單例子:

package mainimport (    "log"    "errors"    "fmt"    )func main() {   /* 定義局部變數 */  ...   /* 除法函數,除以 0 的時候會返回錯誤 */   ret,err = div(a, b)if err != nil { log.Fatal(err)    }    fmt.Println(ret)}

如果你嘗試除以 0,你就會得到類似下面的結果:

為了快速測試一個 Golang 函數,你可以使用go playground。

為了確保你的日誌總是能輕易訪問,我們建議你把它們寫到一個檔案:

package mainimport (        "log"        "os")func main() {        // 按照所需讀寫權限建立檔案        f, err := os.OpenFile("filename", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)        if err != nil {                log.Fatal(err)        }           // 完成後延遲關閉,而不是習慣!        defer f.Close()        //設定日誌輸出到 f        log.SetOutput(f)        //測試案例        log.Println("check to make sure it works")}

你可以找到 Golang 日誌的完整指南,以及 “log”庫內可用函數的完整列表。

現在你就可以記錄它們的錯誤以及根本原因啦。

另外,日誌也可以幫你將活動流拼接在一起,尋找需要修複的錯誤上下文,或者調查在你的系統中單個請求如何影響其它應用程式層和 API。

為了獲得更好的日誌效果,你首先需要在你的項目中使用儘可能多的上下文豐富你的 Golang 日誌,並標準化你使用的格式。這就是 Golang 原生庫能達到的極限。使用最廣泛的庫是glog和logrus。必須承認還有很多好的庫可以使用。如果你已經在使用支援 JSON 格式的庫,你就不需要再換其它庫了,後面我們會解釋。

II. 為你 Golang 日誌統一格式

1) JSON 格式的結構優勢

在一個項目或者多個微服務中結構化你的 Golang 日誌可能是最困難的事情,但一旦完成就很輕鬆了。結構化你的日誌能使機器可讀(參考我們收集日誌的最佳實務博文)。靈活性和層級是 JSON 格式的核心,因此資訊能夠輕易被人類和機器解析以及處理。

下面是一個使用Logrus/Logmatic.io如何用 JSON 格式記錄日誌的例子:

package mainimport (  log "github.com/Sirupsen/logrus"  "github.com/logmatic/logmatic-go")func main() {    // 使用 JSONFormatter    log.SetFormatter(&logmatic.JSONFormatter{})        // 使用 logrus 像往常那樣記錄事件    log.WithFields(log.Fields{"string": "foo", "int": 1, "float": 1.1 }).Info("My first ssl event from golang")}

會輸出結果:

{       "date":"2016-05-09T10:56:00+02:00",    "float":1.1,    "int":1,    "level":"info",    "message":"My first ssl event from golang",    "String":"foo"}

2) 標準化 Golang 日誌

同一個錯誤出現在你代碼的不同部分,卻以不同形式被記錄下來是一件可恥的事情。下面是一個由於一個變數錯誤導致無法確定 web 頁面載入狀態的例子。一個開發人員日誌格式是:

message: 'unknown error: cannot determine loading status from unknown error: missing or invalid arg value client'</span>

另一個人的格式卻是:

unknown error: cannot determine loading status - invalid client</span>

強制日誌標準化的一個好的解決辦法是在你的代碼和日誌庫之間建立一個介面。這個標準化介面會包括所有你想添加到你日誌中的可能行為的預定義日誌訊息。這麼做可以防止出現不符合你想要的標準格式的自訂日誌資訊。這麼做也便於日誌調查。

由於日誌格式都被統一處理,使它們保持更新也變得更加簡單。如果出現了一種新的錯誤類型,它只需要被添加到一個介面,這樣每個組員都會使用完全相同的資訊。

最常使用的簡單例子就是在 Golang 日誌資訊前面添加日誌器名稱和 id。你的代碼然後就會發送 “事件” 到你的標準化介面,它會繼續講它們轉化為 Golang 日誌訊息。

// 主要部分,我們會在這裡定義所有訊息。// Event 結構體很簡單。為了當所有資訊都被記錄時能檢索它們,// 我們維護了一個 Idvar (    invalidArgMessage = Event{1, "Invalid arg: %s"}    invalidArgValueMessage = Event{2, "Invalid arg value: %s => %v"}    missingArgMessage = Event{3, "Missing arg: %s"})// 在我們應用程式中可以使用的所有日誌事件func (l *Logger)InvalidArg(name string) {    l.entry.Errorf(invalidArgMessage.toString(), name)}func (l *Logger)InvalidArgValue(name string, value interface{}) {    l.entry.WithField("arg." + name, value).Errorf(invalidArgValueMessage.toString(), name, value)}func (l *Logger)MissingArg(name string) {    l.entry.Errorf(missingArgMessage.toString(), name)}

因此如果我們使用前面例子中無效的參數值,我們就會得到相似的日誌資訊:

time="2017-02-24T23:12:31+01:00" level=error msg="LoadPageLogger00003 - Missing arg: client - cannot determine loading status" arg.client=<nil> logger.name=LoadPageLogger

JSON 格式如下:

{"arg.client":null,"level":"error","logger.name":"LoadPageLogger","msg":"LoadPageLogger00003 - Missing arg: client - cannot determine loading status", "time":"2017-02-24T23:14:28+01:00"}

III. Golang 日誌內容相關的力量

現在 Golang 日誌已經按照特定結構和標準格式記錄,時間會決定需要添加哪些上下文以及相關資訊。為了能從你的日誌中抽取資訊,例如追蹤一個使用者活動或者工作流程,上下文和中繼資料的順序非常重要。

例如在 logrus 庫中可以按照下面這樣使用 JSON 格式添加hostname、appname和session 參數:

// 對於中繼資料,通常做法是通過複用來重用日誌語句中的欄位。  contextualizedLog := log.WithFields(log.Fields{    "hostname": "staging-1",    "appname": "foo-app",    "session": "1ce3f6v"  })contextualizedLog.Info("Simple event with global metadata")

中繼資料可以視為 javascript 片段。為了更好地說明它們有多麼重要,讓我們看看幾個 Golang 微服務中中繼資料的使用。你會清楚地看到是怎麼在你的應用程式中跟蹤使用者的。這是因為你不僅需要知道一個錯誤發生了,還要知道是哪個執行個體以及什麼模式導致了錯誤。假設我們有兩個按順序調用的微服務。上下文資訊儲存在頭部(header)中傳輸:

func helloMicroService1(w http.ResponseWriter, r *http.Request) {client := &http.Client{}// 該服務負責接收所有到來的使用者請求// 我們會檢查是否是一個新的會話還是已有會話的另一次調用session := r.Header.Get("x-session")if ( session == "") {session = generateSessionId()// 為新會話記錄日誌}// 每個請求的 Track Id 都是唯一的,因此我們會為每個會話產生一個track := generateTrackId()// 調用你的第二個微服務,添加 session/trackreqService2, _ := http.NewRequest("GET", "http://localhost:8082/", nil)reqService2.Header.Add("x-session", session)reqService2.Header.Add("x-track", track)resService2, _ := client.Do(reqService2)….

當調用第二個服務時:

func helloMicroService2(w http.ResponseWriter, r *http.Request) {// 類似之前的微服務,我們檢查會話並產生新的 tracksession := r.Header.Get("x-session")track := generateTrackId()// 這一次,我們檢查請求中是否已經設定了一個 track id,// 如果是,它變為父 trackparent := r.Header.Get("x-track")if (session == "") {w.Header().Set("x-parent", parent)}// 為響應添加 meta 資訊w.Header().Set("x-session", session)w.Header().Set("x-track", track)if (parent == "") {w.Header().Set("x-parent", track)}// 填充響應w.WriteHeader(http.StatusOK)io.WriteString(w, fmt.Sprintf(aResponseMessage, 2, session, track, parent))}

現在第二個微服務中已經有和初始查詢相關的上下文和資訊,一個 JSON 格式的日誌訊息看起來類似如下。

在第一個微服務:

{"appname":"go-logging","level":"debug","msg":"hello from ms 1","session":"eUBrVfdw","time":"2017-03-02T15:29:26+01:00","track":"UzWHRihF"}

在第二個微服務:

{"appname":"go-logging","level":"debug","msg":"hello from ms 2","parent":"UzWHRihF","session":"eUBrVfdw","time":"2017-03-02T15:29:26+01:00","track":"DPRHBMuE"}

如果在第二個微服務中出現了錯誤,多虧了 Golang 日誌中儲存的上下文資訊,現在我們就可以確定它是怎樣被調用的以及什麼模式導致了這個錯誤。

如果你想進一步深挖 Golang 的追蹤能力,這裡還有一些庫提供了追蹤功能,例如Opentracing。這個庫提供了一種簡單的方式在或複雜或簡單的架構中添加追蹤的實現。它通過不同步驟允許你追蹤使用者的查詢,就像下面這樣:

IV. Golang 日誌對效能的影響

1) 不要在 Goroutine 中使用日誌

在每個 goroutine 中建立一個新的日誌器看起來很誘人。但最好別這麼做。Goroutine 是一個輕量級線程管理器,它用於完成一個 “簡單的” 任務。因此它不應該負責日誌。它可能導致並發問題,因為在每個 goroutine 中使用

log.New()

會重複介面,所有日誌器會並發嘗試訪問同一個 io.Writer。

為了限制對效能的影響以及避免並發調用 io.Writer,庫通常使用一個特定的 goroutine 用於日誌輸出。

2) 使用非同步庫

儘管有很多可用的 Golang 日誌庫,要注意它們中的大部分都是同步的(事實上是偽非同步)。原因很可能是到現在為止它們中沒有一個會由於日誌嚴重影響效能。

但正如 Kjell Hedström 在他的實驗中展示的,使用多個線程建立成千上萬日誌,即便是在最壞情況下,非同步 Golang 日誌也會有 40% 的效能提升。因此日誌是有開銷的,也會對你的應用程式效能產生影響。如果你並不需要處理大量的日誌,使用偽非同步 Golang 日誌庫可能就足夠了。但如果你需要處理大量的日誌,或者很關注效能,Kjell Hedström 的非同步解決方案就很有趣(儘管事實上你可能需要進一步開發,因為它只包括了最小的功能需求)。

3)使用嚴重等級管理 Golang 日誌

一些日誌庫允許你啟用或停用特定的日誌器,這可能會派上用場。例如在生產環境中你可能不需要一些特定等級的日誌。下面是一個如何在 glog 庫中停用日誌器的例子,其中日誌器被定義為布爾值:

type Log boolfunc (l Log) Println(args ...interface{}) {    fmt.Println(args...)}var debug Log = falseif debug {    debug.Println("DEBUGGING")}

然後你就可以在設定檔中定義這些布爾參數來啟用或者停用日誌器。

沒有一個好的 Golang 日誌策略,Golang 日誌可能開銷很大。開發人員應該抵制記錄幾乎所有事情的誘惑 - 儘管它非常有趣!如果日誌的目的是為了擷取儘可能多的資訊,為了避免包含無用元素的日誌的白噪音,必須正確使用日誌。

V. 集中化 Golang 日誌

如果你的應用程式是部署在多台伺服器上的,這樣可以避免為了調查一個現象需要串連到每一台伺服器的麻煩。日誌集中確實有用。

使用日誌裝箱工具,例如 windows 中的 Nxlog,linux 中的 Rsyslog(預設安裝了的)、Logstash 和 FluentD 是最好的實現方式。日誌裝箱工具的唯一目的就是發送日誌,因此它們能夠處理串連失效以及其它你很可能會遇到的問題。

這裡甚至有一個Golang syslog 軟體包幫你將 Golang 日誌發送到 syslog 守護進程。

希望你享受你的 Golang 日誌之旅

在你項目一開始就考慮你的 Golang 日誌策略非常重要。如果在你代碼的任意地方都可以獲得所有的上下文,追蹤使用者就會變得很簡單。從不同服務中閱讀沒有標準化的日誌是已經很痛苦的事情。一開始就計劃在多個微服務中擴充相同使用者或請求 id,後面就會允許你比較容易地過濾資訊並在你的系統中跟蹤活動。

你是在構架一個很大的 Golang 項目還是幾個微服務也會影響你的日誌策略。一個大項目的主要組件應該有按照它們功能命名的特定 Golang 日誌器。這使你可以立即判斷出日誌來自你的哪一部分代碼。然而對於微服務或者小的 Golang 項目,只有較少的核心組件需要它們自己的日誌器。但在每種情形中,日誌器的數目都應該保持低於核心功能的數目。

你現在已經可以使用 Golang 日誌量化決定你的效能或者使用者滿意度啦!

via: https://logmatic.io/blog/our-guide-to-a-golang-logs-world/

作者:Nils 譯者:ictlyh 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出

原文來自:https://www.linuxprobe.com/go-language-log.html

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.