Go語(yǔ)言編程實(shí)現(xiàn)支持六種級(jí)別的日志庫(kù)?
前言
Golang標(biāo)準(zhǔn)日志庫(kù)提供的日志輸出方法有Print、Fatal、Panic等,沒(méi)有常見(jiàn)的Debug、Info、Error等日志級(jí)別,用起來(lái)不太順手。這篇文章就來(lái)手?jǐn)]一個(gè)自己的日志庫(kù),可以記錄不同級(jí)別的日志。
其實(shí)對(duì)于追求簡(jiǎn)單來(lái)說(shuō),Golang標(biāo)準(zhǔn)日志庫(kù)的三個(gè)輸出方法也夠用了,理解起來(lái)也很容易:
- Print用于記錄一個(gè)普通的程序日志,開(kāi)發(fā)者想記點(diǎn)什么都可以。
- Fatal用于記錄一個(gè)導(dǎo)致程序崩潰的日志,并會(huì)退出程序。
- Panic用于記錄一個(gè)異常日志,并觸發(fā)panic。
不過(guò)對(duì)于用慣了Debug、Info、Error的人來(lái)說(shuō),還是有點(diǎn)不習(xí)慣;對(duì)于想更細(xì)致的區(qū)分日志級(jí)別的需求,標(biāo)準(zhǔn)日志庫(kù)還提供了一個(gè)通用的Output方法,開(kāi)發(fā)者在要輸出的字符串中加入級(jí)別也是可以的,但總是有點(diǎn)別扭,不夠直接。
目前市面上也已經(jīng)有很多優(yōu)秀的三方日志庫(kù),比如uber開(kāi)源的zap,常見(jiàn)的還有zerolog、logrus等。不過(guò)我這里還是想自己手?jǐn)]一個(gè),因?yàn)榇蠖鄶?shù)開(kāi)源產(chǎn)品都不會(huì)完全貼合自己的需求,有很多自己用不上的功能,這會(huì)增加系統(tǒng)的復(fù)雜性,有沒(méi)有隱藏的坑也很難說(shuō),當(dāng)然自己入坑的可能性也很大;再者看了官方日志庫(kù)的實(shí)現(xiàn)之后,感覺(jué)可以簡(jiǎn)單封裝下即可實(shí)現(xiàn)自己想要的功能,能夠hold住。
初始需求
我這里的初始需求是:
- 將日志寫(xiě)入磁盤(pán)文件,每個(gè)月一個(gè)文件夾,每個(gè)小時(shí)一個(gè)文件。
- 支持常見(jiàn)日志級(jí)別:Trace、Debug、Info、Warn、Error、Fatal,并且程序能夠設(shè)置日志級(jí)別。
我給這個(gè)日志庫(kù)取名為ylog,預(yù)期的使用方法如下:
ylog.SetLevel(LevelInfo) ylog.Debug("I am a debug log.") ylog.Info("I am a Info log.")
技術(shù)實(shí)現(xiàn)
類型定義
需要定義一個(gè)結(jié)構(gòu)體,保存日志級(jí)別、要寫(xiě)入的文件等信息。
type FileLogger struct { lastHour int64 file *os.File Level LogLevel mu sync.Mutex iLogger *log.Logger Path string }
來(lái)看一下這幾個(gè)參數(shù):
lastHour 用來(lái)記錄創(chuàng)建日志文件時(shí)的小時(shí)數(shù),如果小時(shí)變了,就要?jiǎng)?chuàng)建新的日志文件。
file 當(dāng)前使用的日志文件。
Level 當(dāng)前使用的日志級(jí)別。
mu 因?yàn)榭赡茉诓煌膅o routine中寫(xiě)日志,需要一個(gè)互斥體保證日志文件不會(huì)重復(fù)創(chuàng)建。
iLogger 標(biāo)準(zhǔn)日志庫(kù)實(shí)例,因?yàn)檫@里是封裝了標(biāo)準(zhǔn)日志庫(kù)。
Path 日志輸出的最上層目錄,比如程序根目錄下的logs目錄,這里就保存一個(gè)字符串:logs。
日志級(jí)別
先把日志級(jí)別定義出來(lái),這里日志級(jí)別其實(shí)是int類型,從0到5,級(jí)別不斷升高。
如果設(shè)置為T(mén)oInfo,則Info級(jí)別及比Info級(jí)別高的日志都能輸出。
type LogLevel int const ( LevelTrace LogLevel = iota LevelDebug LevelInfo LevelWarn LevelError LevelFatal )
上文提到可以在Output方法的參數(shù)中加入日志級(jí)別,這里就通過(guò)封裝Output方法來(lái)實(shí)現(xiàn)不同級(jí)別的日志記錄方法。這里貼出其中一個(gè)方法,封裝的方式都一樣,就不全都貼出來(lái)了:
func (l *FileLogger) CanInfo() bool { return l.Level <= LevelInfo } func (l *FileLogger) Info(v ...any) { if l.CanInfo() { l.ensureFile() v = append([]any{"Info "}, v...) l.iLogger.Output(2, fmt.Sprintln(v...)) } }
輸出日志前做了三件事:
- 判斷日志級(jí)別,如果設(shè)置的日志級(jí)別小于等于當(dāng)前輸出級(jí)別,則可以輸出。
- 確保日志文件已經(jīng)創(chuàng)建好,后邊會(huì)講如何確保。
- 將日志級(jí)別前插到日志字符串中。
然后調(diào)用標(biāo)準(zhǔn)庫(kù)的Output函數(shù)輸出日志,這里第一個(gè)參數(shù)是為了獲取到當(dāng)前正在寫(xiě)日志的程序文件名,傳入的是在程序調(diào)用棧中進(jìn)行查找的深度值,這里用2就正好。
寫(xiě)到文件
標(biāo)準(zhǔn)庫(kù)的log是支持輸出到多種目標(biāo)的,只要實(shí)現(xiàn)了io.Write接口:
type Writer interface { Write(p []byte) (n int, err error) }
因?yàn)槲募?duì)象也實(shí)現(xiàn)了這個(gè)接口,所以這里可以創(chuàng)建os.File的實(shí)例,并把它設(shè)置到內(nèi)嵌的標(biāo)準(zhǔn)日志庫(kù)實(shí)例,也就是設(shè)置到前邊創(chuàng)建的FileLogger中的iLogger中。這個(gè)操作在ensureFile方法中,看一下這個(gè)文件的實(shí)現(xiàn):
func (l *FileLogger) ensureFile() (err error) { currentTime := time.Now() if l.file == nil { l.mu.Lock() defer l.mu.Unlock() if l.file == nil { l.file, err = createFile(&l.Path, ¤tTime) l.iLogger.SetOutput(l.file) l.iLogger.SetFlags(log.Lshortfile | log.Ldate | log.Ltime | log.Lmicroseconds) l.lastHour = getTimeHour(¤tTime) } return } currentHour := getTimeHour(¤tTime) if l.lastHour != currentHour { l.mu.Lock() defer l.mu.Unlock() if l.lastHour != currentHour { _ = l.file.Close() l.file, err = createFile(&l.Path, ¤tTime) l.iLogger.SetOutput(l.file) l.iLogger.SetFlags(log.Llongfile | log.Ldate | log.Ltime) l.lastHour = getTimeHour(¤tTime) } } return }
這里稍微有點(diǎn)復(fù)雜,基本邏輯是:如果文件實(shí)例不存在,則創(chuàng)建;如果需要?jiǎng)?chuàng)建新的文件,則先關(guān)閉舊的文件再創(chuàng)建新的文件。
更改文件實(shí)例時(shí)需要加鎖,否則可能多次操作,出現(xiàn)預(yù)期之外的情況。
設(shè)置輸出到文件后,標(biāo)準(zhǔn)log庫(kù)的Output方法就會(huì)將日志輸出到這個(gè)文件了。
默認(rèn)實(shí)現(xiàn)
經(jīng)過(guò)上邊一系列操作,這個(gè)FileLogger就可以使用了:
var logger = NewFileLogger(LevelInfo, "logs") logger.Info("This is a info.")
不過(guò)和最初設(shè)想的用法有點(diǎn)差別:ylog.Info("xxxx")
這需要在ylog包中再定義一個(gè)名為Info的公開(kāi)函數(shù),可以在這個(gè)公開(kāi)函數(shù)中調(diào)用一個(gè)默認(rèn)創(chuàng)建的FileLogger實(shí)例,代碼是這樣的:
var stdPath = "logs" var std = NewFileLogger(LevelInfo, stdPath) func Trace(v ...any) { if std.CanTrace() { std.ensureFile() v = append([]any{"Trace"}, v...) std.iLogger.Output(2, fmt.Sprintln(v...)) } }
注意這里沒(méi)有調(diào)用std的Trace方法,這是因?yàn)镺utput中的第一個(gè)參數(shù),如果嵌套調(diào)用std.Trace,則多了一層,這個(gè)參數(shù)就得設(shè)置為3,但是自己創(chuàng)建實(shí)例調(diào)用Trace時(shí)這個(gè)參數(shù)需要為2,這就產(chǎn)生沖突了。
經(jīng)過(guò)以上這些操作,就可以實(shí)現(xiàn)預(yù)期的日志操作了:
ylog.SetLevel(LevelInfo) ylog.Debug("I am a debug log.") ylog.Info("I am a Info log.")
完整的程序代碼:https://github.com/bosima/ylog/tree/v1.0.1
下篇文章將繼續(xù)改造這個(gè)日志庫(kù),支持輸出Json格式的日志,以及輸出日志到Kafka,更多關(guān)于Golang日志庫(kù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
淺析Go語(yǔ)言的數(shù)據(jù)類型及數(shù)組
Golang是一種靜態(tài)強(qiáng)類型、編譯型語(yǔ)言。Go?語(yǔ)言語(yǔ)法與?C?相近,但功能上有:內(nèi)存安全,GC(垃圾回收),結(jié)構(gòu)形態(tài)及?CSP-style?并發(fā)計(jì)算。本文主要和大家聊聊Go語(yǔ)言的數(shù)據(jù)類型及數(shù)組,希望對(duì)大家有所幫助2022-11-11Go存儲(chǔ)基礎(chǔ)使用direct io方法實(shí)例
這篇文章主要介紹了Go存儲(chǔ)基礎(chǔ)之如何使用direct io方法實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12golang利用不到20行代碼實(shí)現(xiàn)路由調(diào)度詳解
這篇文章主要給大家介紹了關(guān)于golang利用不到20行代碼實(shí)現(xiàn)路由調(diào)度的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08基于Golang實(shí)現(xiàn)YOLO目標(biāo)檢測(cè)算法
目標(biāo)檢測(cè)是計(jì)算機(jī)視覺(jué)領(lǐng)域的重要任務(wù),它不僅可以識(shí)別圖像中的物體,還可以標(biāo)記出物體的位置和邊界框,YOLO是一種先進(jìn)的目標(biāo)檢測(cè)算法,以其高精度和實(shí)時(shí)性而聞名,本文將介紹如何使用Golang實(shí)現(xiàn)YOLO目標(biāo)檢測(cè)算法,文中有相關(guān)的代碼示例供大家參考,需要的朋友可以參考下2023-11-11Go語(yǔ)言網(wǎng)站使用異步編程和Goroutine提高Web的性能
作為一門(mén)現(xiàn)代化編程語(yǔ)言,Go語(yǔ)言提供了強(qiáng)大的異步編程能力,使得程序員可以以更高效的方式處理并發(fā)任務(wù),在Go語(yǔ)言中,使用Goroutine在單個(gè)進(jìn)程中實(shí)現(xiàn)多任務(wù)并行處理,以及如何使用協(xié)程池來(lái)進(jìn)一步提高Web服務(wù)器的處理能力,2024-01-01Go語(yǔ)言中的匿名結(jié)構(gòu)體用法實(shí)例
這篇文章主要介紹了Go語(yǔ)言中的匿名結(jié)構(gòu)體用法,實(shí)例分析了匿名結(jié)構(gòu)體的使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02Golang中crypto/rand庫(kù)的使用技巧與最佳實(shí)踐
在Golang的眾多隨機(jī)數(shù)生成庫(kù)中,crypto/rand?是一個(gè)專為加密安全設(shè)計(jì)的庫(kù),本文主要介紹了Golang中crypto/rand庫(kù)的使用技巧與最佳實(shí)踐,感興趣的可以了解一下2024-02-02