Golang 标准库log的实现
前一篇文章我們看到了Golang標(biāo)準(zhǔn)庫中l(wèi)og模塊的使用,那么它是如何實(shí)現(xiàn)的呢?下面我從log.Logger開始逐步分析其實(shí)現(xiàn)。 其源碼可以參考官方地址
1.Logger結(jié)構(gòu)
首先來看下類型Logger的定義:
| 1 2 3 4 5 6 7 | type Logger struct { ????mu???? sync.Mutex?// ensures atomic writes; protects the following fields ????prefix string?????// prefix to write at beginning of each line ????flag???int????????// properties ????out??? io.Writer??// destination for output ????buf??? []byte?????// for accumulating text to write } |
主要有5個(gè)成員,其中3個(gè)我們比較熟悉,分別是表示Log前綴的 "prefix",表示Log頭標(biāo)簽的 "flag" ,以及Log的輸出目的地out。 buf是一個(gè)字節(jié)數(shù)組,主要用來存放即將刷入out的內(nèi)容,相當(dāng)于一個(gè)臨時(shí)緩存,在對(duì)輸出內(nèi)容進(jìn)行序列化時(shí)作為存儲(chǔ)目的地。 mu是一個(gè)mutex主要用來作線程安全的實(shí)習(xí),當(dāng)有多個(gè)goroutine同時(shí)往一個(gè)目的刷內(nèi)容的時(shí)候,通過mutex保證每次寫入是一條完整的信息。
2.std及整體結(jié)構(gòu)
在前一篇文章中我們提到了log模塊提供了一套包級(jí)別的簡(jiǎn)單接口,使用該接口可以直接將日志內(nèi)容打印到標(biāo)準(zhǔn)錯(cuò)誤。那么該過程是怎么實(shí)現(xiàn)的呢?其實(shí)就是通過一個(gè)內(nèi)置的Logger類型的變量 "std" 來實(shí)現(xiàn)的。該變量使用:
| 1 | var?std = New(os.Stderr,?"", LstdFlags) |
進(jìn)行初始化,默認(rèn)輸出到系統(tǒng)的標(biāo)準(zhǔn)輸出 "os.Stderr" ,前綴為空,使用日期加時(shí)間作為L(zhǎng)og抬頭。
當(dāng)我們調(diào)用 log.Print的時(shí)候是怎么執(zhí)行的呢?我們看其代碼:
| 1 2 3 | func Print(v ...interface{}) { ????std.Output(2, fmt.Sprint(v...)) } |
這里實(shí)際就是調(diào)用了Logger對(duì)象的 Output方法,將日志內(nèi)容按照fmt包中約定的格式轉(zhuǎn)義后傳給Output。Output定義如下 :
?
| 1 | func (l *Logger) Output(calldepth?int, s string) error |
?
其中s為日志沒有加前綴和Log抬頭的具體內(nèi)容,xxxxx 。該函數(shù)執(zhí)行具體的將日志刷入到對(duì)應(yīng)的位置。
3.核心函數(shù)的實(shí)現(xiàn)
Logger.Output是執(zhí)行具體的將日志刷入到對(duì)應(yīng)位置的方法。
該方法首先根據(jù)需要獲得當(dāng)前時(shí)間和調(diào)用該方法的文件及行號(hào)信息。然后調(diào)用formatHeader方法將Log的前綴和Log抬頭先格式化好 放入Logger.buf中,然后再將Log的內(nèi)容存入到Logger.buf中,最后調(diào)用Logger.out.Write方法將完整的日志寫入到輸出目的地中。
由于寫入文件以及拼接buf的過程是線程非安全的,因此使用mutex保證每次寫入的原子性。
| 1 2 | l.mu.Lock() defer l.mu.Unlock() |
將buf的拼接和文件的寫入放入這個(gè)后面,使得在多個(gè)goroutine使用同一個(gè)Logger對(duì)象是,不會(huì)弄亂buf,也不會(huì)雜糅的寫入。
該方法的第一個(gè)參數(shù)最終會(huì)傳遞給runtime.Caller的skip,指的是跳過的棧的深度。這里我記住給2就可以了。這樣就會(huì)得到我們調(diào)用log 是所處的位置。
在golang的注釋中說鎖住 runtime.Caller的過程比較重,這點(diǎn)我還是不很了解,只是從代碼中看到其在這里把鎖打開了。
| 1 2 3 4 5 6 7 8 9 10 11 | if?l.flag&(Lshortfile|Llongfile) !=?0?{ ????// release lock while getting caller info - it's expensive. ????l.mu.Unlock() ????var?ok bool ????_, file, line, ok = runtime.Caller(calldepth) ????if?!ok { ????????file =?"???" ????????line =?0 ????} ????l.mu.Lock() } |
在formatHeader里面首先將前綴直接復(fù)制到Logger.buf中,然后根據(jù)flag選擇Log抬頭的內(nèi)容,這里用到了一個(gè)log模塊實(shí)現(xiàn)的 itoa的方法,作用類似c的itoa,將一個(gè)整數(shù)轉(zhuǎn)換成一個(gè)字符串。只是其轉(zhuǎn)換后將結(jié)果直接追加到了buf的尾部。
縱觀整個(gè)實(shí)現(xiàn),最值得學(xué)習(xí)的就是線程安全的部分。在什么位置合適做怎樣的同步操作。
4.對(duì)外接口的實(shí)現(xiàn)
在了解了核心格式化和輸出結(jié)構(gòu)后,在看其封裝就非常簡(jiǎn)單了,幾乎都是首先用Output進(jìn)行日志的記錄,然后在必要的時(shí)候 做os.exit或者panic的操作,這里看下Fatal的實(shí)現(xiàn)。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | func (l *Logger) Fatal(v ...interface{}) { ????l.Output(2, fmt.Sprint(v...)) ????os.Exit(1) } // Fatalf is equivalent to l.Printf() followed by a call to os.Exit(1). func (l *Logger) Fatalf(format string, v ...interface{}) { ????l.Output(2, fmt.Sprintf(format, v...)) ????os.Exit(1) } // Fatalln is equivalent to l.Println() followed by a call to os.Exit(1). func (l *Logger) Fatalln(v ...interface{}) { ????l.Output(2, fmt.Sprintln(v...)) ????os.Exit(1) } |
這里也驗(yàn)證了我們之前做的Panic的結(jié)果,先做輸出日志操作。再進(jìn)行panic。
?
5.Golang的log模塊設(shè)計(jì)
Golang的log模塊主要提供了三類接口 :
-
Print : 一般的消息輸出
-
Fatal : 類似assert一般的強(qiáng)行退出
-
Panic : 相當(dāng)于OO里面常用的異常捕獲
與其說log模塊提供了三類日志接口,不如說log模塊僅僅是對(duì)類C中的 printf、assert、try...catch...的簡(jiǎn)單封裝。Golang的log模塊 并沒有對(duì)log進(jìn)行分類、分級(jí)、過濾等其他類似log4j、log4c、zlog當(dāng)中常見的概念。當(dāng)然在使用中你可以通過添加prefix,來進(jìn)行簡(jiǎn)單的 分級(jí),或者改變Logger.out改變其輸出位置。但這些并沒有在API層面給出直觀的接口。
Golang的log模塊就像是其目前僅專注于為服務(wù)器編程一樣,他的log模塊也專注于服務(wù)器尤其是基礎(chǔ)組件而服務(wù)。就像nginx、redis、lighttpd、keepalived自己為自己寫了一個(gè)簡(jiǎn)單的日志模塊而沒有實(shí)現(xiàn)log4c那樣龐大且復(fù)雜的日志模塊一樣。他的日志模塊僅僅需要為 本服務(wù)按照需要的格式和方式提供接口將日志輸出到目的地即可。
Golang的log模塊可以進(jìn)行一般的信息記錄,assert時(shí)的信息輸出,以及出現(xiàn)異常時(shí)的日志記錄,通過對(duì)其Print的包裝可以實(shí)現(xiàn)更復(fù)雜的 輸出。因此這個(gè)log模塊可謂是語言層面上非常基礎(chǔ)的一層庫,反應(yīng)的是語言本身的特征而不是一個(gè)服務(wù)應(yīng)該怎樣怎樣。
本文出自 “Done_in_72_hours” 博客,請(qǐng)務(wù)必保留此出處http://gotaly.blog.51cto.com/8861157/1406905
轉(zhuǎn)載于:https://www.cnblogs.com/lvdongjie/p/6511029.html
總結(jié)
以上是生活随笔為你收集整理的Golang 标准库log的实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 每周总结02
- 下一篇: 机电传动控制第三次作业