新聞中心
Golang將日志同時輸出到控制臺和文件
日常開發(fā)當中需要將golang的log包打印的日志同時輸出到控制臺和文件,應該如何解決這個問題?
創(chuàng)新互聯建站專注于劍河網站建設服務及定制,我們擁有豐富的企業(yè)做網站經驗。 熱誠為您提供劍河營銷型網站建設,劍河網站制作、劍河網頁設計、劍河網站官網定制、重慶小程序開發(fā)公司服務,打造劍河網絡公司原創(chuàng)品牌,更為您提供劍河網站排名全網營銷落地服務。
log包可以通過SetOutput()方法指定日志輸出的方式(Writer),但是只能指定一個輸出的方式(Writer)。我們利用io.MultiWriter()將多個Writer拼成一個Writer使用的特性,把log.Println()輸出的內容分流到控制臺和文件當中。
原文地址
golang logger輸出格式怎么修改
1.Logger結構
首先來看下類型Logger的定義:
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個成員,其中3個我們比較熟悉,分別是表示Log前綴的 "prefix",表示Log頭標簽的 "flag" ,以及Log的輸出目的地out。 buf是一個字節(jié)數組,主要用來存放即將刷入out的內容,相當于一個臨時緩存,在對輸出內容進行序列化時作為存儲目的地。 mu是一個mutex主要用來作線程安全的實習,當有多個goroutine同時往一個目的刷內容的時候,通過mutex保證每次寫入是一條完整的信息。
2.std及整體結構
在前一篇文章中我們提到了log模塊提供了一套包級別的簡單接口,使用該接口可以直接將日志內容打印到標準錯誤。那么該過程是怎么實現的呢?其實就是通過一個內置的Logger類型的變量 "std" 來實現的。該變量使用:
var std = New(os.Stderr, "", LstdFlags)
進行初始化,默認輸出到系統(tǒng)的標準輸出 "os.Stderr" ,前綴為空,使用日期加時間作為Log抬頭。
當我們調用 log.Print的時候是怎么執(zhí)行的呢?我們看其代碼:
func Print(v ...interface{}) {
std.Output(2, fmt.Sprint(v...))
}
這里實際就是調用了Logger對象的 Output方法,將日志內容按照fmt包中約定的格式轉義后傳給Output。Output定義如下 :
func (l *Logger) Output(calldepth int, s string) error
其中s為日志沒有加前綴和Log抬頭的具體內容,xxxxx 。該函數執(zhí)行具體的將日志刷入到對應的位置。
3.核心函數的實現
Logger.Output是執(zhí)行具體的將日志刷入到對應位置的方法。
該方法首先根據需要獲得當前時間和調用該方法的文件及行號信息。然后調用formatHeader方法將Log的前綴和Log抬頭先格式化好 放入Logger.buf中,然后再將Log的內容存入到Logger.buf中,最后調用Logger.out.Write方法將完整的日志寫入到輸出目的地中。
由于寫入文件以及拼接buf的過程是線程非安全的,因此使用mutex保證每次寫入的原子性。
l.mu.Lock()
defer l.mu.Unlock()
將buf的拼接和文件的寫入放入這個后面,使得在多個goroutine使用同一個Logger對象是,不會弄亂buf,也不會雜糅的寫入。
該方法的第一個參數最終會傳遞給runtime.Caller的skip,指的是跳過的棧的深度。這里我記住給2就可以了。這樣就會得到我們調用log 是所處的位置。
在golang的注釋中說鎖住 runtime.Caller的過程比較重,這點我還是不很了解,只是從代碼中看到其在這里把鎖打開了。
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里面首先將前綴直接復制到Logger.buf中,然后根據flag選擇Log抬頭的內容,這里用到了一個log模塊實現的 itoa的方法,作用類似c的itoa,將一個整數轉換成一個字符串。只是其轉換后將結果直接追加到了buf的尾部。
縱觀整個實現,最值得學習的就是線程安全的部分。在什么位置合適做怎樣的同步操作。
4.對外接口的實現
在了解了核心格式化和輸出結構后,在看其封裝就非常簡單了,幾乎都是首先用Output進行日志的記錄,然后在必要的時候 做os.exit或者panic的操作,這里看下Fatal的實現。
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)
}
這里也驗證了我們之前做的Panic的結果,先做輸出日志操作。再進行panic。
Go語言基礎語法(一)
本文介紹一些Go語言的基礎語法。
先來看一個簡單的go語言代碼:
go語言的注釋方法:
代碼執(zhí)行結果:
下面來進一步介紹go的基礎語法。
go語言中格式化輸出可以使用 fmt 和 log 這兩個標準庫,
常用方法:
示例代碼:
執(zhí)行結果:
更多格式化方法可以訪問中的fmt包。
log包實現了簡單的日志服務,也提供了一些格式化輸出的方法。
執(zhí)行結果:
下面來介紹一下go的數據類型
下表列出了go語言的數據類型:
int、float、bool、string、數組和struct屬于值類型,這些類型的變量直接指向存在內存中的值;slice、map、chan、pointer等是引用類型,存儲的是一個地址,這個地址存儲最終的值。
常量是在程序編譯時就確定下來的值,程序運行時無法改變。
執(zhí)行結果:
執(zhí)行結果:
Go 語言的運算符主要包括算術運算符、關系運算符、邏輯運算符、位運算符、賦值運算符以及指針相關運算符。
算術運算符:
關系運算符:
邏輯運算符:
位運算符:
賦值運算符:
指針相關運算符:
下面介紹一下go語言中的if語句和switch語句。另外還有一種控制語句叫select語句,通常與通道聯用,這里不做介紹。
if語法格式如下:
if ... else :
else if:
示例代碼:
語法格式:
另外,添加 fallthrough 會強制執(zhí)行后面的 case 語句,不管下一條case語句是否為true。
示例代碼:
執(zhí)行結果:
下面介紹幾種循環(huán)語句:
執(zhí)行結果:
執(zhí)行結果:
也可以通過標記退出循環(huán):
--THE END--
標題名稱:go語言日志輸出,go 日志打印
文章分享:http://fisionsoft.com.cn/article/dsipeic.html