最近在本身開發的go語言web框架 Bingo 中須要一個日誌處理功能 , 看了看標準庫的log
包, 發現功能過於簡單,因此想從新造個輪子,單獨抽出來做爲一個模塊,輔助框架進行開發git
[bingo-log] 是爲了完成 bingo 的日誌功能而開發的一個第三方包,不依賴框架,可單獨在其餘項目中使用,github
Github地址: bingo-loggolang
安裝和使用在 README.md
中已經寫的很清楚了,這裏再也不贅述,主要記錄開發流程。web
我但願這個包包含的功能:框架
準備使該日誌包支持(FATAL
,ERROR
,WARNING
,DEBUG
,INFO
) 5種報錯級別,異步
寫一個日誌結構體做爲基礎,在其中設置一個接口類型的數據,將容許自定義的方法放在這個接口中,這樣全部實現該接口的對象均可以做爲參數傳入日誌結構體中函數
如何實現異步功能?ui
爲了能夠限制資源消耗,使用協程鏈接池將每一個輸出放入協程池中,達到異步的效果,spa
鏈接池我就不重複造輪子了,使用一個現成的github項目: grpool.net
首先聲明兩個常量,用來標記同步輸出仍是異步輸出
const (
LogSyncMode = iota
LogPoolMode
)
複製代碼
構建結構體
type Log struct {
Connector // 內嵌鏈接器,用來定製化功能
sync.Mutex
initialized bool // 該日誌對象是否初始化
mode int // 日誌記錄模式 同步記錄 or 協程池記錄
pool *grpool.Pool // 協程池
poolExpiredTime int // 協程池模式下,每一個空閒協程的存活時間(秒)
poolWorkerNum int // 協程池模式下,容許的最高協程數
}
複製代碼
咱們但願使用鏈接器來設定每種輸出,因此這個接口應該實現以下幾種方法
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的地方須要加鎖
}
複製代碼
實現鏈接器接口:
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"
}
複製代碼
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()
便可
這樣,一個基本的鏈接器就製做好了,咱們能夠隨時自行擴展
使用方式相似於:
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 ~~~