1. 程式人生 > >Go語言11-日誌系統客戶端相關元件

Go語言11-日誌系統客戶端相關元件

tailf 元件

檢視log日誌,會經常使用到tail -f命令實時跟蹤檔案變化。也可以用Go語言的程式碼來實現同樣的功能,這樣就可以直接用到專案中去了。這裡不用重複造輪子,有一個第三方的庫已經實現了這個功能:

import "github.com/hpcloud/tail"

HP團隊出的tail庫,常用於日誌收集。這裡主要就是看看如何使用。

示例程式碼

package main

import (
    "os"
    "fmt"
    "github.com/hpcloud/tail"
    "time"
)

func main () {
    filename := "tailf_test.txt"  // 指定檢視哪個檔案
    tails, err := tail.TailFile(filename, tail.Config{
        // 下面的2行配置,相當於命令 tail -F 的效果
        // 追蹤檔案並保持重試,即該檔案被刪除或改名後,如果再次建立相同的檔名,會繼續追蹤。
        ReOpen: true,
        Follow: true,

        Location: &tail.SeekInfo{Offset: 0, Whence: os.SEEK_END},  // 從哪個位置開始讀,這裡的設定是從檔案結尾
        MustExist: false,  // 如果檔案不存在,會失敗。這是設為fales,允許檔案不存在,等檔案一建立就會開始追蹤
        Poll: true,  // 設為true,檢查檔案的變化。false是通過inotify來檢查
    })
    if err != nil {
        fmt.Fprintf(os.Stderr, "tail file: %s , ERROR: %v\n", tails.Filename, err)
    }
    for {
        msg, ok := <-tails.Lines
        if ! ok {
            fmt.Fprintf(os.Stderr, "檔案不存在,嘗試重新開啟檔案: %s", tails.Filename)
            time.Sleep(time.Millisecond * 1000)
            continue
        }
        fmt.Println("msg:", msg.Text)
        fmt.Println("time:", msg.Time)
        // windows系統裡的換行是\r\n,輸出的時候可能只去掉了\n,導致字串會以\r結尾
        // 如果以\r結尾,後面還有字串的話,就會從頭開始把之前的內容覆蓋掉
        fmt.Printf("%q\n", msg.Text)  // 輸出有問題的原因是這樣的,這裡可以看出來
        // 可以手動把 msg.Text 最後的 \r 去掉,即使沒有,也不影響
        fmt.Printf("msg: %s time: %s\n", strings.TrimRight(msg.Text, "\r"), msg.Time)
    }
}

這裡最後踩了個小坑,應該是windows系統才會有的問題。

配置檔案庫

解析配置檔案使用的第三方庫是屬於beego框架裡的一個模組。

beego 框架

beego是一個快速開發Go應用的HTTP框架,他可以用來快速開發API、Web及後端服務等各種應用,是一個 RESTful的框架。
安裝:

go get github.com/astaxie/beego

beego是基於八大獨立的模組構建的,是一個高度解耦的框架:

  • cache : 做快取
  • config : 解析各種格式的配置檔案
  • context
  • httplibs
  • logs : 記錄操作資訊
  • orm
  • session
  • toolbox

接下來只是單獨把某個模組拿來使用,不學習框架的使用。安裝的話只能完整的全部裝上了。

使用示例

基本用法:
配置檔案如下:

[server]
host = "1.1.1.1"
port = 23

示例程式碼:

package main

import (
    "github.com/astaxie/beego/config"
    "fmt"
    "os"
)

func main() {
    conf, err := config.NewConfig("ini", "test.conf")  // 配置檔案格式和檔案路徑
    if err != nil {
        fmt.Fprintf(os.Stderr, "New Confit ERROR: %v\n", err)
        return
    }
    port, err := conf.Int("server::port")  // 獲取數值型資料可能會返回錯誤
    if err != nil {
        fmt.Fprintf(os.Stderr, "get conf ERROR: %v\n", err)
        return
    }
    fmt.Println("port:", port)
    host := conf.String("server::host")  // 獲取字串資料,不會返回錯誤,讀不到會返回空字串
    fmt.Println("host:", host)
    ip := conf.String("server::ip")  // 讀不到就會返回空
    fmt.Println("ip:", ip)
    ip2 := conf.DefaultString("server::ip", "1.1.1.2")  // 程式碼層面指定的預設值
    fmt.Println("ip2", ip2)
}

預設值
上面的預設值是在程式碼層面實現的。但是ini配置本身是支援預設值的,定義的時候可以寫在最外面,也可以寫在[default] 裡面,效果都是一樣的:

key1 = vale1

[default]
key2 = value2

[server]
host = "1.1.1.1"
port = 23
key1 = v1

示例程式碼:

package main

import (
    "github.com/astaxie/beego/config"
    "fmt"
    "os"
)

func main() {
    conf, err := config.NewConfig("ini", "test.conf")  // 配置檔案格式和檔案路徑
    if err != nil {
        fmt.Fprintf(os.Stderr, "New Confit ERROR: %v\n", err)
        return
    }
    server, err := conf.GetSection("server")  // 可以獲取到所有的引數,返回map
    if err != nil {
        fmt.Fprintf(os.Stderr, "get section ERROR: %v\n", err)
    }
    fmt.Println(server)
    df, err := conf.GetSection("default")  // default裡的以及最外層的引數都會解析存到這個map裡
    if err != nil {
        fmt.Fprintf(os.Stderr, "get section ERROR: %v\n", err)
    }
    fmt.Println(df)

    key1a, key1b := conf.String("key1"), conf.String("default::key1")  //  前面是否加上 default:: 都是一樣的
    key2 := conf.String("key2")
    fmt.Println(key1a, key1b, key2)
    // 解析獲取預設值可能就是這麼做的吧,網上沒有找到相關的示例
    k1 := conf.DefaultString("server::key1", conf.String("key1"))
    k2 := conf.DefaultString("server::key2", conf.String("key2"))
    fmt.Println(k1, k2)
}

日誌庫

這個還是beego框架裡的一個元件

基本使用

先把log輸出到終端:

package main

import (
    "github.com/astaxie/beego/logs"
)

func main() {
    logs.SetLogger(logs.AdapterConsole)  // 設定日誌輸出到哪裡, 引數是個常數。這裡的效果是輸出到終端
    logs.SetLevel(logs.LevelInfo)  // 設定日誌等級
    logs.Debug("DEBUG msg")  // 這條等級不夠,不會顯示
    logs.Info("INFO msg")
}

這裡使用了console引擎,就是輸出到終端,底層是到os.Stdout。

輸出到檔案

要把log輸出到檔案,只需要設定一個新的file引擎:

package main

import (
    "github.com/astaxie/beego/logs"
)

func main() {
    logs.SetLogger(logs.AdapterFile, `{"filename":"test.log","level":6}`)  // logs.LevelInfo = 6
    logs.SetLogger(logs.AdapterConsole, `{"level":4,"◦color":true}`)  // logs.LevelWarning = 4
    logs.SetLevel(logs.LevelInfo)
    logs.Info("INFO msg")
    logs.Warn("WARN msg")
}

SetLogger接收2個引數。
第一個引數就是引擎名,這裡用到了2個 "console" 和 "file" 。上面的程式碼裡寫的是常量名。
第二個引數是可選引數(上個例子沒有用,這裡都用上了),用來表示配置資訊,所有配置寫在一個json字串裡。

更多引擎和引數

這裡可參考官網的引擎配置設定:
https://beego.me/docs/module/logs.md

這裡只列出2個
console 主要引數說明:

  • level 輸出的日誌級別
  • color 是否開啟列印日誌彩色列印(需環境支援彩色輸出)

file 主要引數說明:

  • filename 儲存的檔名
  • maxlines 每個檔案儲存的最大行數,預設值 1000000
  • maxsize 每個檔案儲存的最大尺寸,預設值是 1 << 28, //256 MB
  • daily 是否按照每天 logrotate,預設是 true
  • maxdays 檔案最多儲存多少天,預設儲存 7 天
  • rotate 是否開啟 logrotate,預設是 true
  • level 日誌儲存的時候的級別,預設是 Trace 級別
  • perm 日誌檔案許可權

所有的引擎有這些,左邊是常量,右邊是對應的名字:

const (
    AdapterConsole   = "console"
    AdapterFile      = "file"
    AdapterMultiFile = "multifile"
    AdapterMail      = "smtp"
    AdapterConn      = "conn"
    AdapterEs        = "es"
    AdapterJianLiao  = "jianliao"
    AdapterSlack     = "slack"
    AdapterAliLS     = "alils"
)

註冊引擎名

所有的引擎,都會自動進行註冊。具體就是寫在引擎的程式碼的init方法裡:

// beego/logs/console.go
func init() {
    Register(AdapterConsole, NewConsole)
}

不過很多太多方法都是小寫的,導致藉口沒有暴露出來用不了。只有 console 和 conn 引擎可以註冊。

package main

import (
    "github.com/astaxie/beego/logs"
    "encoding/json"
    "fmt"
    "os"
)

func main() {
    logs.Register("new", logs.NewConsole)
    config := make(map[string]interface{})  // 先定義一個map來存放配置引數
    config["level"] = 4  // LevelWarning = 4
    config["color"] = true  // map的value可能是字串、×××或bool等不同的型別,所以定義的時候型別是空介面
    configStr, err := json.Marshal(config)
    if err != nil {
        fmt.Fprintf(os.Stderr, "json Marshal ERROR %v\n", err)
        return
    }
    logs.SetLogger("new", string(configStr))  // 用map來設定引數,到這裡再轉成json字串
    logs.SetLogger(logs.AdapterConsole)  // 再設定一個預設的console引擎
    logs.Info("INFO msg")  // 滿足1個logger
    logs.Error("ERROR msg")  // 兩個logger都會輸出這條
}

使用map來設定配置資訊
配置資訊是要傳遞json字串進去的,但是人工拼接josn也是很不友好的。所以先把配置寫在map裡,然後再序列化成json傳遞給函式。並且map的value是空介面,因為配置可能是字串、整數或布林等不同型別。
無法註冊file引擎
註冊不了新的file引擎貌似沒啥用。原始碼沒有把對應的方法暴露出來應該就沒有辦法了,就是 func newFileWriter() Logger{} 這個函式,函式名是小寫的。只能去改原始碼,還不方便把上面的函式名改掉,因為在別處還有呼叫這個方法。所以最方便的修改方法是給原來的小寫的函式名定義一個大寫的別名:

func newFileWriter() Logger {
    // 省略函式體部分
}

var NewFileWriter func() Logger = newFileWriter