Go 實戰(zhàn):實現(xiàn)一個簡單的日志庫
編者按:本文實現(xiàn)的簡單日志庫不一定適合你,但可能會給你一些啟發(fā)、借鑒
前言
一個完整的日志庫不僅僅涵蓋日志記錄功能,還要包括日志 level、行號、文件切分,甚至包含統(tǒng)計與分析等,Go 語言中的日志庫也是很多,其中知名度比較高的有:
| 庫名 | star |
|---|---|
| logrus[1] | 14940 |
| zap[2] | 9827 |
| zerolog[3] | 3386 |
| seelog[4] | 1464 |
備注:star 數(shù)獲取時間為 2020-05-28 23:26:00
一千個人有一千個需求,不管是哪個開源日志庫,用著總有不順手的時候,沒關(guān)系,那就自己實現(xiàn)一個吧,相信自己,來,就讓咱們先從實現(xiàn)簡單的日志記錄功能開始吧~「手動狗頭」
思路
功能設(shè)計
根據(jù)自己的需求,我想要的日志記錄功能有:
按照 level 輸出日志 能夠同時輸出到文件和控制臺 控制臺能夠根據(jù) level 將內(nèi)容輸出為不同顏色 日志文件根據(jù)大小進行分割 輸出行號 API 設(shè)計
一般來說,根據(jù) level 不同,設(shè)計有不同的 API,level 大概可以分為: trace、warn、error、fatal, 也就是說對外的 API 可以概括為: T(...inter), W(...), E(...), F(...)
type?logger?interface{
????T(format?string,?v?...interface{})
????W(format?string,?v?...interface{})
????E(format?string,?v?...interface{})
????F(format?string,?v?...interface{})
}
結(jié)構(gòu)設(shè)計
根據(jù)需求,日志記錄器 logger 的結(jié)構(gòu)需要包含 writers、文件名、文件保存路徑、文件分割大小 完整結(jié)構(gòu)設(shè)計如下:
type?myLog?struct?{
???sync.Once
???sync.Mutex?????????????????????//用于outs并發(fā)訪問
???outs?????map[logType]io.Writer?//writer集合
???file?????*os.File??????????????//文件句柄
???fileName?string????????????????//日志名
???dir??????string????????????????//日志存放路徑
???size?????int64?????????????????//單個日志文件的大小限制
}
關(guān)鍵方法實現(xiàn)
日志文件大小檢測
func?(m?*myLog)?checkLogSize()?{
???if?m.file?==?nil?{
???????return
???}
???m.Lock()
???defer?m.Unlock()?//此處必須加鎖,否則會出現(xiàn)并發(fā)問題
???fileInfo,?err?:=?m.file.Stat()
???if?err?!=?nil?{
???????panic(err)
???}
???if?m.size?>?fileInfo.Size()?{
???????return
???}
???//需要分割,重新打開一個新的文件句柄替換老的,并關(guān)閉老的文件句柄,
???newName?:=?path.Join(m.dir,?time.Now().Format("2006_01_02_15:04:03")+".log")
???name?:=?path.Join(m.dir,?m.fileName)
???err?=?os.Rename(name,?newName)
???if?err?!=?nil?{
???????panic(err)
???}
???file,?err?:=?os.OpenFile(name,?os.O_CREATE|os.O_APPEND|os.O_WRONLY,?0755)
???if?err?!=?nil?{
???????panic(err)
???}
???m.file.Close()
???m.file?=?file
???m.outs[logTypeFile]?=?file
???return
}
控制臺帶顏色輸出內(nèi)容
func?setColor(msg?string,?text?int)?string?{
????return?fmt.Sprintf("%c[%dm%s%c[0m",?0x1B,?text,?msg,?0x1B)
}
獲取行號
func?shortFileName(file?string)?string?{
???short?:=?file
???for?i?:=?len(file)?-?1;?i?>?0;?i--?{
???????if?file[i]?==?'/'?{
???????????short?=?file[i+1:]
???????????break
???????}
???}
???return?short
}
完整代碼實現(xiàn)
package?logUtil
import?(
????"fmt"
????"io"
????"os"
????"path"
????"runtime"
????"strconv"
????"sync"
????"time"
)
const?(
????colorRed????=?31
????colorYellow?=?33
????colorBlue???=?34
????levelT?=?"[T]?"
????levelE?=?"[E]?"
????levelW?=?"[W]?"
????defaultFileSize?=?60?*?1024?*?1024
????minFileSize?????=?1?*?1024?*?1024
????defaultLogDir???=?"log"
????defaultLogName??=?"default.log"
????logTypeStd?logType?=?iota?+?1
????logTypeFile
)
type?(
????logType?int
????LogOption?func(log?*myLog)
????myLog?struct?{
????????sync.Once
????????sync.Mutex
????????outs?????map[logType]io.Writer?//writer集合
????????file?????*os.File??????????????//文件句柄
????????fileName?string????????????????//日志名
????????dir??????string????????????????//日志存放路徑
????????size?????int64?????????????????//單個日志文件的大小限制
????}
)
var?(
????defaultLogger?=?&myLog{}
)
func?(m?*myLog)?init()?{
????if?m.dir?==?""?{
????????m.dir?=?defaultLogDir
????}
????if?m.fileName?==?""?{
????????m.fileName?=?defaultLogName
????}
????if?m.size?==?0?{
????????m.size?=?defaultFileSize
????}?else?{
????????if?m.size?????????????panic(fmt.Sprintf("invalid?size:?%d",?m.size))
????????}
????}
????if?m.outs?==?nil?{
????????m.outs?=?make(map[logType]io.Writer)
????}
????if?!isExist(m.dir)?{
????????if?err?:=?os.Mkdir(m.dir,?0777);?err?!=?nil?{
????????????panic(err)
????????}
????}
????name?:=?path.Join(m.dir,?m.fileName)
????file,?err?:=?os.OpenFile(name,?os.O_CREATE|os.O_APPEND|os.O_WRONLY,?0755)
????if?err?!=?nil?{
????????panic(err)
????}
????m.file?=?file
????m.outs[logTypeStd]?=?os.Stdout
????m.outs[logTypeFile]?=?file
}
func?(m?*myLog)?checkLogSize()?{
????if?m.file?==?nil?{
????????return
????}
????m.Lock()
????defer?m.Unlock()
????fileInfo,?err?:=?m.file.Stat()
????if?err?!=?nil?{
????????panic(err)
????}
????if?m.size?>?fileInfo.Size()?{
????????return
????}
????//需要分割
????newName?:=?path.Join(m.dir,?time.Now().Format("2006_01_02_15:04:03")+".log")
????name?:=?path.Join(m.dir,?m.fileName)
????err?=?os.Rename(name,?newName)
????if?err?!=?nil?{
????????panic(err)
????}
????file,?err?:=?os.OpenFile(name,?os.O_CREATE|os.O_APPEND|os.O_WRONLY,?0755)
????if?err?!=?nil?{
????????panic(err)
????}
????m.file.Close()
????m.file?=?file
????m.outs[logTypeFile]?=?file
????return
}
func?(m?*myLog)?write(level?string,?content?string)?{
????m.checkLogSize()
????var?colorText?int
????switch?level?{
????case?levelT:
????????colorText?=?colorBlue
????case?levelW:
????????colorText?=?colorYellow
????case?levelE:
????????colorText?=?colorRed
????}
????for?k,?wr?:=?range?m.outs?{
????????if?k?==?logTypeStd?{
????????????fmt.Fprintf(wr,?setColor(content,?colorText))
????????}?else?{
????????????fmt.Fprintf(wr,?content)
????????}
????}
}
func?WithSize(size?int64)?LogOption?{
????return?func(log?*myLog)?{
????????log.size?=?size
????}
}
func?WithLogDir(dir?string)?LogOption?{
????return?func(log?*myLog)?{
????????log.dir?=?dir
????}
}
func?WithFileName(name?string)?LogOption?{
????return?func(log?*myLog)?{
????????log.fileName?=?name
????}
}
func?InitLogger(args?...LogOption)?{
????defaultLogger.Do(func()?{
????????for?_,?af?:=?range?args?{
????????????af(defaultLogger)
????????}
????????defaultLogger.init()
????})
}
//Info
func?T(format?string,?v?...interface{})?{
????_,?file,?line,?_?:=?runtime.Caller(1)
????timeStr?:=?time.Now().Format("2006-01-02?15:04:05.0000")?+?"?"
????codeLine?:=?"["?+?timeStr?+?shortFileName(file)?+?":"?+?strconv.Itoa(line)?+?"]"
????content?:=?levelT?+?codeLine?+?fmt.Sprintf(format,?v...)?+?"\n"
????defaultLogger.write(levelT,?content)
}
//Error
func?E(format?string,?v?...interface{})?{
????_,?file,?line,?_?:=?runtime.Caller(1)
????timeStr?:=?time.Now().Format("2006-01-02?15:04:05.0000")?+?"?"
????codeLine?:=?"["?+?timeStr?+?shortFileName(file)?+?":"?+?strconv.Itoa(line)?+?"]"
????content?:=?levelE?+?codeLine?+?fmt.Sprintf(format,?v...)?+?"\n"
????defaultLogger.write(levelE,?content)
}
//Warn
func?W(format?string,?v?...interface{})?{
????_,?file,?line,?_?:=?runtime.Caller(1)
????timeStr?:=?time.Now().Format("2006-01-02?15:04:05.0000")?+?"?"
????codeLine?:=?"["?+?timeStr?+?shortFileName(file)?+?":"?+?strconv.Itoa(line)?+?"]"
????content?:=?levelW?+?codeLine?+?fmt.Sprintf(format,?v...)?+?"\n"
????defaultLogger.write(levelW,?content)
}
func?isExist(path?string)?bool?{
????_,?err?:=?os.Stat(path)
????if?err?!=?nil?{
????????if?os.IsExist(err)?{
????????????return?true
????????}
????????return?false
????}
????return?true
}
func?shortFileName(file?string)?string?{
????short?:=?file
????for?i?:=?len(file)?-?1;?i?>?0;?i--?{
????????if?file[i]?==?'/'?{
????????????short?=?file[i+1:]
????????????break
????????}
????}
????return?short
}
func?setColor(msg?string,?text?int)?string?{
????return?fmt.Sprintf("%c[%dm%s%c[0m",?0x1B,?text,?msg,?0x1B)
}
最后
如有不足,還請不吝指教!
附上代碼地址:logUtil[5],歡迎指正!
參考資料
logrus: https://github.com/sirupsen/logrus
[2]zap: https://github.com/uber-go/zap
[3]zerolog: https://github.com/rs/zerolog
[4]seelog: https://github.com/cihub/seelog
[5]logUtil: https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2Fpyihe%2Futil%2Ftree%2Fmaster%2FlogUtil
本文作者:pyihe
原文鏈接:
https://pyihe.github.io/2020/05/31/Go%E8%AF%AD%E8%A8%80%E5%AE%9E%E7%8E%B0%E7%AE%80%E5%8D%95%E7%9A%84%E6%97%A5%E5%BF%97%E8%AE%B0%E5%BD%95%E5%8A%9F%E8%83%BD.html
推薦閱讀
站長 polarisxu
自己的原創(chuàng)文章
不限于 Go 技術(shù)
職場和創(chuàng)業(yè)經(jīng)驗
Go語言中文網(wǎng)
每天為你
分享 Go 知識
Go愛好者值得關(guān)注
