基於go開發日誌處理包
基於go開發日誌處理包
最近在自己開發的go語言web框架ofollow,noindex" target="_blank">Bingo
中需要一個日誌處理功能 , 看了看標準庫的log
包,
發現功能過於簡單,所以想重新造個輪子,單獨抽出來作為一個模組,輔助框架進行開發
[bingo-log] 是為了完成bingo 的日誌功能而開發的一個第三方包,不依賴框架,可單獨在其他專案中使用,
安裝和使用在README.md
中已經寫的很清楚了,這裡不再贅述,主要記錄開發流程。
1. 預期效果
我希望這個包包含的功能:
- 支援多種報錯級別
- 日誌自定義配置並自動分割
- 可非同步輸出日誌
2. 實現思路
準備使該日誌包支援(FATAL
,ERROR
,WARNING
,DEBUG
,INFO
) 5種報錯級別,
寫一個日誌結構體作為基礎,在其中設定一個介面型別的資料,將允許自定義的方法放在這個介面中,這樣所有實現該介面的物件都可以作為引數傳入日誌結構體中
如何實現非同步功能?
為了可以限制資源消耗,使用協程連線池將每個輸出放入協程池中,達到非同步的效果,
連線池我就不重複造輪子了,使用一個現成的github專案:grpool
開始開發
- 構建最基礎的底:日誌結構體
首先宣告兩個常量,用來標記同步輸出還是非同步輸出
const ( LogSyncMode = iota LogPoolMode )
構建結構體
type Log struct { Connector// 內嵌聯結器,用來定製化功能 sync.Mutex initializedbool// 該日誌物件是否初始化 modeint// 日誌記錄模式同步記錄 or 協程池記錄 pool*grpool.Pool // 協程池 poolExpiredTime int// 協程池模式下,每個空閒協程的存活時間(秒) poolWorkerNumint// 協程池模式下,允許的最高協程數 }
- 構建聯結器介面
我們希望使用聯結器來設定每種輸出,所以這個介面應該實現如下幾種方法
type Connector interface { Fatal(message ...interface{}) Error(message ...interface{}) Warning(message ...interface{}) Debug(message ...interface{}) Info(message ...interface{})// 列印 Output(message string)// 將資訊輸出到檔案中 GetMessage(degree int, message ...interface{}) string // 將輸入的資訊新增抬頭(例如新增列印時間等) GetFile(config map[string]string) *os.File// 當前日誌要輸出到的檔案位置,傳入一個map 代表配置 }
上面5種方法是5種報錯級別要做的事情,主要做的事情,就是將要輸出的日誌,先呼叫GetMessage()
將資訊進行包裝,包裝成我們希望的結構,再在控制檯列印輸出,然後再呼叫Output
方法,將日誌列印到日誌檔案中一份
而Output()
方法中要呼叫GetFile()
方法得到要輸出的檔案指標,我們可以在GetFile()
方法中設定分割檔案的方式,如果需要動態分割,那麼其中的map
引數就是外部傳進來的引數
-
為
Log
結構體新增方法:
-
先寫如何建立一個日誌物件:
func NewLog(mode int) *Log { l := &Log{} l.SetMode(mode) l.initialize()// 這裡對結構體中的資料做初始化 return l }
-
然後載入聯結器
// 載入聯結器 func (l *Log) LoadConnector(conn Connector) { l.Connector = conn// 所有實現了聯結器介面的物件都可以作為引數傳入 }
-
然後寫5種報錯級別:
// 重寫5種日誌級別的列印函式 func (l *Log) Fatal(message string) { // 根據模式 l.exec(l.Connector.Fatal, message) } func (l *Log) Error(message string) { l.exec(l.Connector.Error, message) } func (l *Log) Warning(message string) { l.exec(l.Connector.Warning, message) } func (l *Log) Debug(message string) { l.exec(l.Connector.Debug, message) } func (l *Log) Info(message string) { l.exec(l.Connector.Info, message) }
-
上方的
exec
方法就是根據輸出模式選擇直接輸出,還是使用協程池輸出:func (l *Log) exec(f func(message ...interface{}), message string) { // 同步模式 if l.mode == LogSyncMode { l.Lock() defer l.Unlock() f(message) } else if l.mode == LogPoolMode { // 協程池非同步模式 l.initialize() // 先初始化 l.Lock() defer l.Unlock() l.AddWaitCount(1)// 向池中新增計數器,可以計算池中有多少協程正在被使用 l.pool.JobQueue <- func() { f(message) defer l.pool.JobDone() } } }
從上面的程式碼可以看出,Log
結構體只是負責同步還是非同步執行,最重要的地方是聯結器Connector
, 我實現了兩種Connector
(BaseConnector
和KirinConnector
)那麼我們就實現一個基礎聯結器BaseConnector
:
-
建立一個結構體
type BaseConnector struct { sync.Mutex// 這裡是因為有用到map的地方需要加鎖 }
-
實現聯結器介面:
-
先實現GetFile介面,實際就是在當前路徑下建立
bingo.log
檔案,並返回檔案指標:
// 返回一個檔案控制代碼,用來寫入資料 func (b BaseConnector) GetFile(config map[string]string) *os.File { // 預設情況下,輸出到當前路徑下的bingo.log檔案中 dir, err := os.Getwd() if err != nil { panic(err) } path := dir + "/bingo.log" // 真實要儲存的檔案位置 // 判斷檔案是否存在 if _, err := os.Stat(path); err != nil { // 檔案不存在,建立 f, err := os.Create(path) //defer f.Close()// 關閉操作要放在呼叫位置 if err != nil { panic(err) } return f } // 開啟該檔案,追加模式 f, err := os.OpenFile(path, os.O_WRONLY, os.ModeAppend) if err != nil { panic(err) } return f }
-
實現
Output
方法:
func (b BaseConnector) Output(message string) { // 獲取到要輸出的檔案路徑 file := b.GetFile(make(map[string]string)) defer file.Close() n, _ := file.Seek(0, os.SEEK_END)// 向檔案末尾追加資料 // 寫入資料 file.WriteAt([]byte(message), n) }
-
實現
GetMessage
方法,這裡是將要輸出的日誌包裝成 期望的格式:
// 輸出格式為 [日誌級別][時間][日誌內容] func (b BaseConnector) GetMessage(degree int, message ...interface{}) string { var title string switch degree { case FATAL: title = "[FATAL] " case ERROR: title = "[ERROR] " case WARNING: title = "[WARNING]" case DEBUG: title = "[DEBUG] " case INFO: title = "[INFO]" default: title = "[UNKNOWN]" } // 將傳入的資訊擴充套件一下 // 預設添加當前時間 return title + "[" + time.Now().Format("2006-01-02 15:04:05") + "] " + fmt.Sprint(message...) + "\n" }
- 實現5種日誌級別:
func (b BaseConnector) Info(message ...interface{}) { // 綠色輸出在控制檯 m := b.GetMessage(INFO, message...) fmt.Print(clcolor.Green(m)) // 輸出在檔案中 b.Output(m) }
為了在控制檯中達到以不同的顏色輸出不同級別的日誌,我們要在列印函式中加上顏色,具體方式在這裡給終端來點彩色(c語言和Golang版)
我這裡直接使用了一個別人寫好的第三方包xcltapestry/xclpkg
直接使用
clcolor.Green()
即可這樣,一個基本的聯結器就製作好了,我們可以隨時自行擴充套件
-
先實現GetFile介面,實際就是在當前路徑下建立
小結
使用方式類似於:
log := bingo_log.NewLog(bingo_log.LogSyncMode) conn := new(bingo_log.BaseConnector) log.LoadConnector(conn) log.Info("testing") log.Debug("testing") log.Warning("testing") log.Error("testing") log.Fatal("testing")
介面是golang種極其強大的特性,我們可以利用介面完成很多動態結構
最後再推薦一下自己的 WEB 框架Bingo ,求 star,求 PR ~~~