Golang標準日志庫提供的日志輸出方法有Print、Fatal、Panic等,沒有常見的Debug、Info、Error等日志級別,用起來不太順手。這篇文章就來手擼一個自己的日志庫,可以記錄不同級別的日志。
專注于為中小企業(yè)提供成都網(wǎng)站制作、做網(wǎng)站服務(wù),電腦端+手機端+微信端的三站合一,更高效的管理,為中小企業(yè)金溪免費做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動了上千多家企業(yè)的穩(wěn)健成長,幫助中小企業(yè)通過網(wǎng)站建設(shè)實現(xiàn)規(guī)模擴充和轉(zhuǎn)變。
其實對于追求簡單來說,Golang標準日志庫的三個輸出方法也夠用了,理解起來也很容易:
不過對于用慣了Debug、Info、Error的人來說,還是有點不習(xí)慣;對于想更細致的區(qū)分日志級別的需求,標準日志庫還提供了一個通用的Output方法,開發(fā)者在要輸出的字符串中加入級別也是可以的,但總是有點別扭,不夠直接。
目前市面上也已經(jīng)有很多優(yōu)秀的三方日志庫,比如uber開源的zap,常見的還有zerolog、logrus等。不過我這里還是想自己手擼一個,因為大多數(shù)開源產(chǎn)品都不會完全貼合自己的需求,有很多自己用不上的功能,這會增加系統(tǒng)的復(fù)雜性,有沒有隱藏的坑也很難說,當然自己入坑的可能性也很大;再者看了官方日志庫的實現(xiàn)之后,感覺可以簡單封裝下即可實現(xiàn)自己想要的功能,能夠hold住。
我這里的初始需求是:
我給這個日志庫取名為ylog,預(yù)期的使用方法如下:
ylog.SetLevel(LevelInfo)
ylog.Debug("I am a debug log.")
ylog.Info("I am a Info log.")
需要定義一個結(jié)構(gòu)體,保存日志級別、要寫入的文件等信息。
type FileLogger struct {
lastHour int64
file *os.File
Level LogLevel
mu sync.Mutex
iLogger *log.Logger
Path string
}
來看一下這幾個參數(shù):
lastHour 用來記錄創(chuàng)建日志文件時的小時數(shù),如果小時變了,就要創(chuàng)建新的日志文件。
file 當前使用的日志文件。
Level 當前使用的日志級別。
mu 因為可能在不同的go routine中寫日志,需要一個互斥體保證日志文件不會重復(fù)創(chuàng)建。
iLogger 標準日志庫實例,因為這里是封裝了標準日志庫。
Path 日志輸出的最上層目錄,比如程序根目錄下的logs目錄,這里就保存一個字符串:logs。
先把日志級別定義出來,這里日志級別其實是int類型,從0到5,級別不斷升高。
如果設(shè)置為ToInfo,則Info級別及比Info級別高的日志都能輸出。
type LogLevel int
const (
LevelTrace LogLevel = iota
LevelDebug
LevelInfo
LevelWarn
LevelError
LevelFatal
)
上文提到可以在Output方法的參數(shù)中加入日志級別,這里就通過封裝Output方法來實現(xiàn)不同級別的日志記錄方法。這里貼出其中一個方法,封裝的方式都一樣,就不全都貼出來了:
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...))
}
}
輸出日志前做了三件事:
然后調(diào)用標準庫的Output函數(shù)輸出日志,這里第一個參數(shù)是為了獲取到當前正在寫日志的程序文件名,傳入的是在程序調(diào)用棧中進行查找的深度值,這里用2就正好。
標準庫的log是支持輸出到多種目標的,只要實現(xiàn)了io.Write接口:
type Writer interface {
Write(p []byte) (n int, err error)
}
因為文件對象也實現(xiàn)了這個接口,所以這里可以創(chuàng)建os.File的實例,并把它設(shè)置到內(nèi)嵌的標準日志庫實例,也就是設(shè)置到前邊創(chuàng)建的FileLogger中的iLogger中。這個操作在ensureFile方法中,看一下這個文件的實現(xiàn):
func (l *FileLogger) ensureFile() (err error) {
curTime := time.Now()
curHour := getTimeHour(curTime)
if atomic.LoadInt64(&l.lastHour) != curHour {
return l.ensureFileSlow(curTime, curHour)
}
return
}
func (l *FileLogger) ensureFileSlow(curTime time.Time, curHour int64) (err error) {
l.mu.Lock()
defer l.mu.Unlock()
if l.lastHour != curHour {
defer atomic.StoreInt64(&l.lastHour, curHour)
l.createFile(curTime, curHour)
}
return
}
func (l *FileLogger) createFile(curTime time.Time, curHour int64) (err error) {
if l.file == nil {
l.file, err = createFile(l.Path, curTime)
if err != nil {
return err
}
l.iLogger.SetOutput(l.file)
l.iLogger.SetFlags(log.Lshortfile | log.Ldate | log.Ltime | log.Lmicroseconds)
} else {
_ = l.file.Close()
l.file, err = createFile(l.Path, curTime)
if err != nil {
return err
}
l.iLogger.SetOutput(l.file)
l.iLogger.SetFlags(log.Lshortfile | log.Ldate | log.Ltime | log.Lmicroseconds)
}
return
}
這里稍微有點復(fù)雜,基本邏輯是:如果文件實例不存在,則創(chuàng)建;如果需要創(chuàng)建新的文件,則先關(guān)閉舊的文件再創(chuàng)建新的文件。
這里使用了雙檢鎖的模式,避免了每次訪問都使用互斥鎖的開銷,同時考慮到Golang中對變量的讀寫也不是協(xié)程安全的,會出現(xiàn)同時讀寫的情況,所以使用了原子操作,寫法參考了sync.Once的源碼。
設(shè)置輸出到文件后,標準log庫的Output方法就會將日志輸出到這個文件了。
經(jīng)過上邊一系列操作,這個FileLogger就可以使用了:
var logger = NewFileLogger(LevelInfo, "logs")
logger.Info("This is a info.")
不過和最初設(shè)想的用法有點差別: ylog.Info("xxxx")
這需要在ylog包中再定義一個名為Info的公開函數(shù),可以在這個公開函數(shù)中調(diào)用一個默認創(chuàng)建的FileLogger實例,代碼是這樣的:
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...))
}
}
注意這里沒有調(diào)用std的Trace方法,這是因為Output中的第一個參數(shù),如果嵌套調(diào)用std.Trace,則多了一層,這個參數(shù)就得設(shè)置為3,但是自己創(chuàng)建實例調(diào)用Trace時這個參數(shù)需要為2,這就產(chǎn)生沖突了。
經(jīng)過以上這些操作,就可以實現(xiàn)預(yù)期的日志操作了:
ylog.SetLevel(LevelInfo)
ylog.Debug("I am a debug log.")
ylog.Info("I am a Info log.")
完整的程序已經(jīng)上傳到Github,歡迎訪問:https://github.com/bosima/ylog/tree/v1.0.2
下篇文章將繼續(xù)改造這個日志庫,支持輸出Json格式的日志,以及輸出日志到Kafka。
收獲更多架構(gòu)知識,請關(guān)注微信公眾號 螢火架構(gòu)。原創(chuàng)內(nèi)容,轉(zhuǎn)載請注明出處。
分享名稱:Golang:手擼一個支持六個級別的日志庫
當前網(wǎng)址:http://chinadenli.net/article4/dsoisoe.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供營銷型網(wǎng)站建設(shè)、App設(shè)計、自適應(yīng)網(wǎng)站、ChatGPT、網(wǎng)站設(shè)計、動態(tài)網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)