https://github.com/cihub/seelog
文檔學習:https://github.com/cihub/seelog/wiki
1.安裝:
go get github.com/cihub/seelog
2.快速啟動
Seelog的設計非常方便。它的默認配置和包級別的日志記錄器是現成的,所以開始你只需要兩行代碼:
package main import log "github.com/cihub/seelog" func main() { defer log.Flush() log.Info("Hello from Seelog!") }
Info只是Seelog支持的日志級別之一。你還可以使用Trace, Debug, Info, Warn, Error, Critical級別。
運行返回:
bogon:~ user$ go run testGo.go 1551577771885754000 [Info] Hello from Seelog!
基本配置
這是seelog config的一個例子,它使用默認格式選項、約束等將輸出重定向到控制台,命名為seelog.xml。
<seelog> <outputs> <console /> </outputs> </seelog>
大多數wiki部分介紹使用configs進行的Seelog調優。
下載配置
在Seelog包中有幾個函數可以幫你加載configs。
logger, err := log.LoggerFromConfigAsFile("seelog.xml") if err != nil { return err } log.ReplaceLogger(logger)
這里還有'LoggerFromConfigAsBytes',和'LoggerFromConfigAsString'兩種類型的下載函數
你可以在任何時候運行log.ReplaceLogger。配置轉換可見Changing config on the fly
defer塊和刷新
在許多情況下,無法在主goroutine中處理生成的日志信息。
在這些情況下,我們建議異步日志記錄器在非阻塞模式下依次從隊列中接收緩沖消息。在這種情況下,確保在應用程序遭受緊急崩潰時不會丟失日志數據是至關重要的。我們在main函數的defer中使用log. Flush()函數解決了這個問題,它保證日志消息隊列中剩下的所有消息都將正常地獨立於應用程序不管panic是否進行處理。
注意:在使用Seelog構造之前,defer塊必須放在可執行文件的main函數中。在編寫包時,不要擔心延遲刷新,詳情可見Writing libraries with Seelog
ReplaceLogger 和 UseLogger
這兩個函數都更改了負責當前日志記錄器的包級別變量。此變量用於包級函數“Trace”、“Debug”等。但是,請注意區別。
前者正確地關閉前一個日志記錄器(使用刷新日志數據),然后用一個新的日志記錄器替換它。當你更改日志配置時,這是最推薦的方法。
后者只刷新前一個日志記錄器(不關閉它),然后用一個新的日志記錄器替換它。當你更改日志記錄器並且對關閉舊日志記錄器漠不關心時,應該使用此方法。
演示配置的所有功能
有一個演示配置,它在一個地方演示了大多數功能,可見下面的 9.Example config
你可以在深入研究所有特性之前檢查它。
3.日志級別
這一節展示了我們對Seelog級別層次、它們的含義和使用范圍的看法。當我們根據自己的概念對Seelog代碼進行調優時,建議遵循以下規則。
支持的日志級別有:
- Trace -查找關於所有基本構造的狀態的普遍信息。使用“Trace”進行深度調試,查找函數的問題部分,檢查臨時變量的值,等等。
- Debug——用於詳細的系統行為報告和診斷消息,以幫助定位開發過程中的問題。
- Info-關於應用程序工作的一般信息。在代碼中使用“Info”級別,這樣即使在生產環境中也可以啟用它。所以這是一個“生產日志級別”。
- Warn-用於指示以安全方式自動處理的小錯誤、奇怪情況和故障。
- Error-嚴重故障影響應用程序的工作流程,但不是致命的(不強迫應用程序關閉)。
- Critical——在應用程序死亡之前生成最后的消息。注意:Critical消息強制立即刷新,因為Critical情況下,如果應用程序崩潰,避免日志消息丟失是很重要的。
- Off—用於關閉日志記錄的特殊日志級別
配置文件的日志級別標識符
- "trace"——低級別
- "debug"
- "info"
- "warn"
- "error"
- "critical"——高級別
日志消息示例
- Trace
- "Entered parse function validation block"
- "Validation: entered second 'if'"
- "Dictionary 'Dict' is empty. Using default value"
- Debug
- "Web page requested: http://somesite.com Params='...'"
- "Response generated. Response size: 10000. Sending."
- "New file received. Type:PNG Size:20000"
- Info
- "Web server restarted"
- "Hourly statistics: Requested pages: 12345 Errors: 123 ..."
- "Service paused. Waiting for 'resume' call"
- Warn
- "Cache corrupted for file='test.file'. Reading from back-end"
- "Database 192.168.0.7/DB not responding. Using backup 192.168.0.8/DB"
- "No response from statistics server. Statistics not sent"
- Error
- "Internal error. Cannot process request #12345 Error:...."
- "Cannot perform login: credentials DB not responding"
- Critical
- "Critical panic received: .... Shutting down"
- "Fatal error: ... App is shutting down to prevent data corruption or loss"
例子:
下面的示例演示了Seelog級別的概念用法。
注1:這個例子實際上在計算方面沒有任何意義。它只是突出了日志級別使用上下文中的差異。
注2:有時人們會將Info與Debug甚至Trace混淆。我們試圖找出最引人注目的案例。請注意“Info”用例:它是一個生產日志級別,我們讓它在不影響性能(即使在生產中)的情況下運行。
package main import ( log "github.com/cihub/seelog" "time" "errors" ) type inputData struct { x, y int } type outputData struct { result int error bool } var inputs chan inputData var outputs chan outputData var criticalChan chan int func internalCalculationFunc(x, y int) (result int, err error) { log.Debugf("calculating z. x:%d y:%d", x, y) //報告系統行為,定位開發過程 z := y switch { case x == 3 : log.Trace("x == 3")//進行深度調試:查找函數的問題部分,檢查臨時變量的值等 panic("Failure.") case y == 1 : log.Trace("y == 1") return 0, errors.New("Error!") case y == 2 : log.Trace("y == 2") z = x default : log.Trace("default") z += x } log.Tracef("z:%d",z) retVal := z-3 log.Debugf("Returning %d", retVal) return retVal, nil } func generateInputs(dest chan inputData) { time.Sleep(1e9) log.Debug("Sending 2 3") dest <- inputData{x : 2, y : 3} time.Sleep(1e9) log.Debug("Sending 2 1") dest <- inputData{x : 2, y : 1} time.Sleep(1e9) log.Debug("Sending 3 4") dest <- inputData{x : 3, y : 4} time.Sleep(1e9) log.Debug("Sending critical") criticalChan <- 1 } func consumeResults(res chan outputData) { for { select { case <- outputs: //在這一點上,我們得到並輸出結果值 } } } func processInput(input inputData) { defer func() { if r := recover(); r != nil {//獲取panic中的錯誤信息 log.Errorf("Unexpected error occurred: %v", r) //記錄錯誤信息 outputs <- outputData{result : 0, error : true} } }() log.Infof("Received input signal. x:%d y:%d", input.x, input.y) //關於應用程序工作的一般信息 res, err := internalCalculationFunc(input.x, input.y) if err != nil { log.Warnf("Error in calculation: %s", err.Error())//用於指示以安全方式自動處理的小錯誤、奇怪情況和故障 } log.Infof("Returning result: %d error: %t", res, err != nil) outputs <- outputData{result : res, error : err != nil} } func main() { inputs = make(chan inputData) outputs = make(chan outputData) criticalChan = make(chan int) log.Info("App started.") go consumeResults(outputs) //outputs通道等待結果,並將結果輸出 log.Info("Started receiving results.") go generateInputs(inputs) log.Info("Started sending signals.")//三次將值發送到inputs通道中,並輸入1給criticalChan for { select { case input := <- inputs: //generateInputs每次值輸入到inputs時就並發在此輸出 processInput(input) //進行內部計算並將結果輸入通道outputs case <- criticalChan: //直到generateInputs的最后輸入1給criticalChan log.Critical("Caught value from criticalChan: Go shut down.") //在應用程序死亡之前生成最后的消息 panic("Shut down due to critical fault.") } } }
返回:
bogon:~ user$ go run testGo.go 1551581657401394000 [Info] App started. 1551581657401416000 [Info] Started receiving results. 1551581657401419000 [Info] Started sending signals. 1551581658406575000 [Debug] Sending 2 3 1551581658406686000 [Info] Received input signal. x:2 y:3 1551581658406827000 [Debug] calculating z. x:2 y:3 1551581658406850000 [Trace] default 1551581658406860000 [Trace] z:5 1551581658406870000 [Debug] Returning 2 1551581658407009000 [Info] Returning result: 2 error: false 1551581659412207000 [Debug] Sending 2 1 1551581659412273000 [Info] Received input signal. x:2 y:1 1551581659412357000 [Debug] calculating z. x:2 y:1 1551581659412368000 [Trace] y == 1 1551581659412499000 [Warn] Error in calculation: Error! 1551581659412528000 [Info] Returning result: 0 error: true 1551581660414490000 [Debug] Sending 3 4 1551581660414708000 [Info] Received input signal. x:3 y:4 1551581660414760000 [Debug] calculating z. x:3 y:4 1551581660414774000 [Trace] x == 3 1551581660414787000 [Error] Unexpected error occurred: Failure. 1551581661420124000 [Debug] Sending critical 1551581661420188000 [Critical] Caught value from criticalChan: Go shut down. panic: Shut down due to critical fault. goroutine 1 [running]: main.main() /Users/user/testGo.go:109 +0x3bc exit status 2
4.約束和例外
限制
約束限制了規范日志級別的規則。如果沒有指定約束,則允許所有日志級別。
- Min/max約束允許包含最小值和最大值之間的級別(例如,info thru error)。min和max都不需要在場。因此,你可以允許所有日志級別高於或低於最小級別1。可使用關鍵字“minlevel”和“maxlevel”設置這些約束。
- 約束列表只允許在列表中指定級別。例如,你可以輸入字符串“debug, info, critical”來表示logger需要這三個級別。使用關鍵字“levels”來設置這種約束。
有兩種類型的約束:全局約束和例外約束
全局約束
全局約束用於整個應用程序,它們使用的是“定期應用的”規則(而不是“例外”)。這些約束是在seelog根元素屬性中設置的。
舉例說明
若要只允許日志級別“info”及以上,請使用以下命令啟動配置:
<seelog minlevel="info">
允許級別從 info到error(即 info, warn, error),使用:
<seelog minlevel="info" maxlevel="error">
要只允許特定的級別集(例如trace, info, and critical級別),請使用以下命令啟動配置:
<seelog levels="trace,info,critical">
例外約束
例外,與一般規則相反,被認為是打破(放松或加強)常規規則(一般約束)的特殊情況。例如,你可能希望限制特定文件或文件組的日志記錄。反之亦然:你確實有限制全局約束,並且你希望允許特定的文件或函數在更深的層次上進行日志記錄。
例外包括“filepattern”、“funcpattern”和約束(“minlevel”/“maxlevel”或“levels”)。因此,如果你希望使用特定的名稱模式覆蓋函數或文件(或兩者)的一般規則,那么可以在“filepattern”/“funcpattern”字段中指定模式,並使用覆蓋約束。
例外如何使用
當你在運行時為每個日志執行日志記錄時。在底層調用調用方函數以獲取當前上下文。然后我們發現匹配模式file/func名的第一個例外。如果發現這樣的例外,則其約束將覆蓋常規約束。
建議
根據上面所說的,有一些簡單的建議:
- 例外要適度。當我們在每次記錄某樣東西時都運行例外列表時,用規則填充它不是一個好主意,只有少數可以這么做。我們稱它們為例外是有原因的!
- 避免生產配置中允許“trace”或“debug”級別的約束。如果這樣做,你將告訴分析器“允許在某些地方進行traces/debugs”,並且所有Trace/Debug調用不會立即返回,而是強制約束檢查器在每次運行時運行。然后調用者將進入例外列表等。然而,對於性能並不重要的開發或生產系統,這些限制是可以接受的。(參見下面的例子)
- 首先使用更具體的規則。這只是因為我們恰好使用滿足file/func名稱模式的第一個例外。因此,如果你的文件名符合例外的“filepattern”—例外子系統將立即使用此例外的約束,而不會查看隨后出現的例外。所以首先使用更具體的規則,最后使用不那么具體的規則(參見下面的例子)
舉例說明:
讓我們從“test”開始,為所有文件創建更多的限制規則。
<seelog minlevel="info"> <exceptions> <exception filepattern="test*" minlevel="error"/> </exceptions>
通過這種方式,你將獲得所有文件的“info”、“warn”、“error”、“critical”消息,但以“test”開頭的文件除外。對於以“test”開頭的文件,你只會得到“error”和“critical”消息。
另一個例子。現在讓我們創建一個相反的情況:讓我們只允許“critical”消息作為一般規則,但允許“main.testFunc”函數為“warn, error, critical”級別(package 'main', func 'testFunc'):
<seelog levels="critical"> <exceptions> <exception funcpattern="main.testFunc" minlevel="warn"/> </exceptions>
讓我們創建一個生產就緒配置:
<seelog minlevel="info"> <exceptions> <exception funcpattern="main.testFunc" minlevel="warn"/> <exception funcpattern="main.testFunc2" minlevel="error"/> <exception funcpattern="*test*" filepattern="tests.go" levels="off"/> <exception funcpattern="*perfCritical" minlevel="critical"/> </exceptions> ...
這個配置完全可以用於生產,因為它沒有任何允許級別“trace”或“debug”的例外
讓我們先測試一下“更常見的例外情況”規則:
<seelog minlevel="info"> <exceptions> <exception funcpattern="main.testFunc" levels="critical"/> <exception funcpattern="main.test*" minlevel="error"/> <exception funcpattern="main.*" minlevel="warn"/> </exceptions> ...
這個配置將像它看起來的那樣工作。但如果你以另一種順序寫這些例外,它就不一樣了。例如,如果你將“main.*”放在例外的前面,那么其他兩個例外將被忽略。
“off”日志級別
“off”是一個特殊的日志級別,它意味着禁用日志記錄。它可以在minlevel和level約束中使用,因此你可以在全局約束或例外約束中寫入'minlevel= “off”'和'levels= “off”'來禁用日志。
示例
package main import ( "fmt" log "github.com/cihub/seelog" ) func main() { defer log.Flush() testMinMax() testMin() testMax() testList() testFuncException() testFileException() } func testMinMax() { //只會輸出日志級別在info和error之間的日志的內容,即info\warn\error fmt.Println("testMinMax") testConfig := ` <seelog type="sync" minlevel="info" maxlevel="error"> <outputs><console/></outputs> </seelog>` logger, _ := log.LoggerFromConfigAsBytes([]byte(testConfig)) log.ReplaceLogger(logger) log.Trace("NOT Printed") log.Debug("NOT Printed") log.Info("Printed") log.Warn("Printed") log.Error("Printed") log.Critical("NOT Printed") } func testMin() { //會輸出大於info日志級別的日志內容,即info\warn\error\critical fmt.Println("testMin") testConfig := ` <seelog type="sync" minlevel="info"> <outputs><console/></outputs> </seelog>` logger, _ := log.LoggerFromConfigAsBytes([]byte(testConfig)) log.ReplaceLogger(logger) log.Trace("NOT Printed") log.Debug("NOT Printed") log.Info("Printed") log.Warn("Printed") log.Error("Printed") log.Critical("Printed") } func testMax() {//會輸出級別不大於error的日志文件的信息,即trace、debug、info、warn和error fmt.Println("testMax") testConfig := ` <seelog type="sync" maxlevel="error"> <outputs><console/></outputs> </seelog>` logger, _ := log.LoggerFromConfigAsBytes([]byte(testConfig)) log.ReplaceLogger(logger) log.Trace("Printed") log.Debug("Printed") log.Info("Printed") log.Warn("Printed") log.Error("Printed") log.Critical("NOT Printed") } func testList() {//只輸出日志級別為info, trace, critical的日志 fmt.Println("testList") testConfig := ` <seelog type="sync" levels="info, trace, critical"> <outputs><console/></outputs> </seelog>` logger, _ := log.LoggerFromConfigAsBytes([]byte(testConfig)) log.ReplaceLogger(logger) log.Trace("Printed") log.Debug("NOT Printed") log.Info("Printed") log.Warn("NOT Printed") log.Error("NOT Printed") log.Critical("Printed") } //主限制是輸出大於info日志級別的日志內容, //但是這里有個例外,要求滿足函數名為"*main.test*Except*"的函數中的日志輸出的是日志級別大於error的日志信息 //所以這里最后輸出的是error、critical日志 func testFuncException() { fmt.Println("testFuncException") testConfig := ` <seelog type="sync" minlevel="info"> <exceptions> <exception funcpattern="*main.test*Except*" minlevel="error"/> </exceptions> <outputs> <console/> </outputs> </seelog>` logger, _ := log.LoggerFromConfigAsBytes([]byte(testConfig)) log.ReplaceLogger(logger) log.Trace("NOT Printed") log.Debug("NOT Printed") log.Info("NOT Printed") log.Warn("NOT Printed") log.Error("Printed") log.Critical("Printed") log.Current.Trace("NOT Printed") log.Current.Debug("NOT Printed") log.Current.Info("NOT Printed") log.Current.Warn("NOT Printed") log.Current.Error("Printed") log.Current.Critical("Printed") } //這里因為testFileException名不滿足例外中的"*main.go",所以返回的內容為大於info日志級別的日志內容,即info\warn\error\critical func testFileException() { fmt.Println("testFileException") testConfig := ` <seelog type="sync" minlevel="info"> <exceptions> <exception filepattern="*main.go" minlevel="error"/> </exceptions> <outputs> <console/> </outputs> </seelog>` logger, _ := log.LoggerFromConfigAsBytes([]byte(testConfig)) log.ReplaceLogger(logger) log.Trace("NOT Printed") log.Debug("NOT Printed") log.Info("NOT Printed") log.Warn("NOT Printed") log.Error("Printed") log.Critical("Printed") }
返回:
userdeMBP:go-learning user$ go run test.go testMinMax 1551696134714593000 [Info] Printed 1551696134714624000 [Warn] Printed 1551696134714643000 [Error] Printed testMin 1551696134714668000 [Info] Printed 1551696134714674000 [Warn] Printed 1551696134714679000 [Error] Printed 1551696134714684000 [Critical] Printed testMax 1551696134714700000 [Trace] Printed 1551696134714708000 [Debug] Printed 1551696134714714000 [Info] Printed 1551696134714718000 [Warn] Printed 1551696134714723000 [Error] Printed testList 1551696134714745000 [Trace] Printed 1551696134714751000 [Info] Printed 1551696134714758000 [Critical] Printed testFuncException 1551696134714822000 [Error] Printed 1551696134714837000 [Critical] Printed 1551696134714847000 [Error] Printed 1551696134714852000 [Critical] Printed testFileException 1551696134714888000 [Info] NOT Printed 1551696134714895000 [Warn] NOT Printed 1551696134714904000 [Error] Printed 1551696134714909000 [Critical] Printed
5.Dispatchers and receivers分配器和接收器
1)
接收器Receivers
我們對后端字節接收器使用“receiver”術語,如日志文件、網絡通道等。
分配器Dispatchers
我們使用“dispatcher”術語表示向多個底層 接收者receivers/分配器dispatchers發送消息的中間元素。
舉例說明:
進行dispatcher/receiver配置的主要目標是使用公共格式選項或允許的日志級別創建不同的組。例如,讓我們創建一個示例配置:
<seelog> <outputs> <splitter formatid="common"> <console/> <file path="file.log"/> <conn addr="192.168.0.2:8123"/> </splitter> <filter levels="critical"> <file path="critical.log" formatid="critical"/> <smtp formatid="criticalemail" senderaddress="noreply-notification-service@none.org" sendername="Automatic notification service" hostname="mail.none.org" hostport="587" username="nns" password="123"> <recipient address="john-smith@none.com"/> <recipient address="hans-meier@none.com"/> </smtp> </filter> </outputs> <formats> <format id="common" format="[%LEV] %Msg"/> <format id="critical" format="%Time %Date %RelFile %Func %Msg"/> <format id="criticalemail" format="Critical error on our server!\n %Time %Date %RelFile %Func %Msg \nSent by Seelog"/> </formats> </seelog>
因此,在這里我們使用一個“splitter”元素來按格式(“common”)將三個接收器分組,其他兩個接收器使用“filter”按允許的日志級別分組。注意,頂部的元素“output”本身就是一個拆分器splitter,因此我們可以簡化配置:
<seelog> <outputs formatid="common"> <console/> <file path="file.log"/> <conn addr="192.168.0.2:8123"/> <filter levels="critical"> <file path="critical.log" formatid="critical"/> <smtp formatid="criticalemail" senderaddress="noreply-notification-service@none.org" sendername="Automatic notification service" hostname="mail.none.org" hostport="587" username="nns" password="123"> <recipient address="john-smith@none.com"/> <recipient address="hans-meier@none.com"/> </smtp> </filter> </outputs> <formats> <format id="common" format="[%LEV] %Msg"/> <format id="critical" format="%Time %Date %RelFile %Func %Msg"/> <format id="criticalemail" format="Critical error on our server!\n %Time %Date %RelFile %Func %Msg \nSent by Seelog"/> </formats> </seelog>
Formatting格式化
格式僅在寫入字節接收器時應用。如果沒有設置“formatid”,Dispatchers將繼承格式標識符。如果設置了“formatid”,分配器和字節接收器將覆蓋任何繼承的格式。
讓我們使用上面例子中的配置:
<seelog> <outputs formatid="common"> <console/> <file path="file.log"/> <network address="192.168.0.2" port="8123"/> <filter levels="critical"> <file path="critical.log" formatid="critical"/> <smtp formatid="criticalemail" senderaddress="noreply-notification-service@none.org" sendername="Automatic notification service" hostname="mail.none.org" hostport="587" username="nns" password="123"> <recipient address="john-smith@none.com"/> <recipient address="hans-meier@none.com"/> </smtp> </filter> </outputs> <formats> <format id="common" format="[%LEV] %Msg"/> <format id="critical" format="%Time %Date %RelFile %Func %Msg"/> <format id="criticalemail" format="Critical error on our server!\n %Time %Date %RelFile %Func %Msg \nSent by Seelog"/> </formats> </seelog>
它演示了繼承/覆蓋(inheritance/overriding)特性。最上面的splitter有formatid=“common”,因此它的所有子組件都繼承它:console, file, network,和filter。filter中的file和smtp接收器不會繼承它,因為它們用自己的格式文件覆蓋它。如果smtp沒有一個“formatid”屬性集,那么它將從其父組件“filter”分配器中繼承formatid=“common”。
示例

package main import ( "fmt" log "github.com/cihub/seelog" "time" ) func main() { defer log.Flush() runExample(consoleWriter) runExample(fileWriter) runExample(rollingFileWriter) runExample(rollingFileWriterManyRolls) runExample(bufferedWriter) runExample(bufferedWriterWithFlushPeriod) runExample(bufferedWriterWithOverflow) runExample(splitDispatcher) runExample(filterDispatcher) //runExample(smtpWriter) } func runExample(exampleFunc func()) { exampleFunc() fmt.Println() } //在終端標准輸出中輸出"Console writer" //並在終端輸出5個trace日志信息 func consoleWriter() { testConfig := ` <seelog> <outputs> <console /> </outputs> </seelog> ` logger, _ := log.LoggerFromConfigAsBytes([]byte(testConfig)) log.ReplaceLogger(logger) fmt.Println("Console writer") doLog() } //將5個trace日志信息寫到文件./log/log.log中 func fileWriter() { testConfig := ` <seelog> <outputs> <file path="./log/log.log"/> </outputs> </seelog> ` logger, _ := log.LoggerFromConfigAsBytes([]byte(testConfig)) log.ReplaceLogger(logger) fmt.Println("File writer") doLog() } //將日志信息寫到回滾文件./log/roll.log中 //以大小size存儲,文件最大為100個字節,文件個數最多為5 func rollingFileWriter() { testConfig := ` <seelog> <outputs> <rollingfile type="size" filename="./log/roll.log" maxsize="100" maxrolls="5" /> </outputs> </seelog> ` logger, _ := log.LoggerFromConfigAsBytes([]byte(testConfig)) log.ReplaceLogger(logger) fmt.Println("Rolling file writer") doLog() } func rollingFileWriterManyRolls() { testConfig := ` <seelog> <outputs> <rollingfile type="size" filename="./log/manyrolls.log" maxsize="100" maxrolls="4" /> </outputs> </seelog> ` logger, _ := log.LoggerFromConfigAsBytes([]byte(testConfig)) log.ReplaceLogger(logger) fmt.Println("Rolling file writer. Many rolls") doLogBig() } func bufferedWriter() { testConfig := ` <seelog> <outputs> <buffered size="10000"> <file path="./log/bufFile.log"/> </buffered> </outputs> </seelog> ` logger, _ := log.LoggerFromConfigAsBytes([]byte(testConfig)) log.ReplaceLogger(logger) fmt.Println("Buffered file writer. NOTE: file modification time not changed until next test (buffered)") time.Sleep(3e9) for i := 0; i < 3; i++ { doLog() time.Sleep(5e9) } time.Sleep(2e9) } func bufferedWriterWithFlushPeriod() { testConfig := ` <seelog> <outputs> <buffered size="10000" flushperiod="1000"> <file path="./log/bufFileFlush.log"/> </buffered> </outputs> </seelog> ` logger, _ := log.LoggerFromConfigAsBytes([]byte(testConfig)) log.ReplaceLogger(logger) fmt.Println("Buffered file writer with flush period. NOTE: file modification time changed after each 'doLog' because of small flush period.") time.Sleep(3e9) for i := 0; i < 3; i++ { doLog() time.Sleep(5e9) } time.Sleep(2e9) } //雖然溢出,但是日志信息仍全部存儲 func bufferedWriterWithOverflow() { testConfig := ` <seelog> <outputs> <buffered size="20"> <file path="./log/bufOverflow.log"/> </buffered> </outputs> </seelog> ` logger, _ := log.LoggerFromConfigAsBytes([]byte(testConfig)) log.ReplaceLogger(logger) fmt.Println("Buffered file writer with overflow. NOTE: file modification time changes after each 'doLog' because of overflow") time.Sleep(3e9) for i := 0; i < 3; i++ { doLog() time.Sleep(5e9) } time.Sleep(1e9) } //日志信息在輸入日志文件split.log同時也輸出到標准輸出中 func splitDispatcher() { testConfig := ` <seelog> <outputs> <file path="./log/split.log"/> <console /> </outputs> </seelog> ` logger, _ := log.LoggerFromConfigAsBytes([]byte(testConfig)) log.ReplaceLogger(logger) fmt.Println("Split dispatcher") doLog() } //只有trace日志級別的信息存儲到filter.log文件中,但是trace和debug的日志信息都會輸出到標准輸出中 func filterDispatcher() { testConfig := ` <seelog> <outputs> <filter levels="trace"> <file path="./log/filter.log"/> </filter> <console /> </outputs> </seelog> ` logger, _ := log.LoggerFromConfigAsBytes([]byte(testConfig)) log.ReplaceLogger(logger) fmt.Println("Filter dispatcher") for i:=0; i < 5; i++ { log.Trace("This message on console and in file") log.Debug("This message only on console") } } func smtpWriter() { testConfig := ` <seelog> <outputs> <smtp senderaddress="noreply-notification-service@none.org" sendername="Automatic notification service" hostname="mail.none.org" hostport="587" username="nns" password="123"> <recipient address="john-smith@none.com"/> </smtp> </outputs> </seelog> ` logger, _ := log.LoggerFromConfigAsBytes([]byte(testConfig)) log.ReplaceLogger(logger) fmt.Println("SMTP writer is now sending emails to the specified recipients") doLog() } func doLog() { for i:=0; i < 5; i++ { log.Tracef("%d", i) } } func doLogBig() { for i:=0; i < 50; i++ { log.Tracef("%d", i) } }
返回:
userdeMBP:go-learning user$ go run test.go Console writer 1551697317265791000 [Trace] 0 1551697317265815000 [Trace] 1 1551697317265818000 [Trace] 2 1551697317265819000 [Trace] 3 1551697317265820000 [Trace] 4 File writer Rolling file writer Rolling file writer. Many rolls Buffered file writer. NOTE: file modification time not changed until next test (buffered) Buffered file writer with flush period. NOTE: file modification time changed after each 'doLog' because of small flush period. Buffered file writer with overflow. NOTE: file modification time changes after each 'doLog' because of overflow Split dispatcher 1551697376322223000 [Trace] 0 1551697376322243000 [Trace] 1 1551697376322250000 [Trace] 2 1551697376322256000 [Trace] 3 1551697376322262000 [Trace] 4 Filter dispatcher 1551697376322864000 [Trace] This message on console and in file 1551697376322876000 [Debug] This message only on console 1551697376322891000 [Trace] This message on console and in file 1551697376322897000 [Debug] This message only on console 1551697376322900000 [Trace] This message on console and in file 1551697376322903000 [Debug] This message only on console 1551697376322906000 [Trace] This message on console and in file 1551697376322910000 [Debug] This message only on console 1551697376323322000 [Trace] This message on console and in file 1551697376323329000 [Debug] This message only on console
一個文件中最多有100個字節,因此這里一個文件中只寫入了4行日志信息,其他的日志信息將另起一個文件存儲,之前的文件將被重命名為roll.log.1,后來的日志內容仍會存在roll.log中,如果再超出,目前roll.log中的內容將存在名為roll.log.2的文件中,新內容仍存儲在roll.log中
比如函數rollingFileWriter運行第一次的日志文件有兩個,其內容為:
userdeMBP:go-learning user$ cat ./log/roll.log 1551698497839015000 [Trace] 4 userdeMBP:go-learning user$ cat ./log/roll.log.1 1551698497838979000 [Trace] 0 1551698497839007000 [Trace] 1 1551698497839010000 [Trace] 2 1551698497839013000 [Trace] 3
再運行一次函數rollingFileWriter,可見日志文件變成了三個,內容分別為:
userdeMBP:go-learning user$ cat ./log/roll.log.1 1551698497838979000 [Trace] 0 1551698497839007000 [Trace] 1 1551698497839010000 [Trace] 2 1551698497839013000 [Trace] 3 userdeMBP:go-learning user$ cat ./log/roll.log.2 1551698497839015000 [Trace] 4 1551698882929867000 [Trace] 0 1551698882929892000 [Trace] 1 1551698882929894000 [Trace] 2 userdeMBP:go-learning user$ cat ./log/roll.log 1551698882929896000 [Trace] 3 1551698882929897000 [Trace] 4
運行函數rollingFileWriterManyRolls后,日志文件為:
可見該日志文件一直保持5個的數量,后面的日志將前面的日志覆蓋
userdeMBP:go-learning user$ cat ./log/manyrolls.log.9 1551697317268125000 [Trace] 32 1551697317268127000 [Trace] 33 1551697317268128000 [Trace] 34 1551697317269659000 [Trace] 35 userdeMBP:go-learning user$ cat ./log/manyrolls.log.12 1551697317271189000 [Trace] 44 1551697317271192000 [Trace] 45 1551697317271194000 [Trace] 46 1551697317271196000 [Trace] 47 userdeMBP:go-learning user$ cat ./log/manyrolls.log 1551697317271197000 [Trace] 48 1551697317271198000 [Trace] 49
2)分配器(dispatchers)列表:Reference
下面不同的dispatcher是用於指明發送消息的方式
1》Splitter dispatcher
功能:將接收到的消息發送到所有子節點。用於細分<outputs>日志格式,內部支持:file(文件), rollingfile(滾動文件,自動清除過期)
元素名:Splitter
允許屬性:
- formatid—不覆蓋它的所有子節點將繼承的格式
舉例說明:
<seelog> <outputs> <splitter formatid="format1"> <file path="log.log"/> <file path="log2.log"/> </splitter> <splitter formatid="format2"> <file path="log3.log"/> <file path="log4.log"/> </splitter> </outputs> <formats> ... </formats> </seelog>
注:“output”元素也是一個Splitter,這只是頂部Splitter的一個特殊別名,因此,可以寫成:
<seelog> <outputs> <file path="split.log"/> <console /> </outputs> </seelog>
上面的例子等價於:
<seelog> <splitter> <file path="split.log"/> <console /> </splitter> </seelog>
但后者在句法上並不正確。
2》Filter dispatcher
功能:僅當接收到的消息的日志級別在“allowed”列表中時,才向所有子節點發送接收到的消息。用於單獨處理某個或某些級別日志
元素名:Filter
允許屬性:
formatid—不覆蓋它的所有子節點將繼承的格式
levels—“allowed”列表中的逗號分隔的日志級別標識符(日志級別)列表
舉例說明:
<seelog> <outputs> <filter levels="trace,debug"> <file path="filter.log"/> </filter> <console /> </outputs> </seelog>
3)接收器(receivers)列表:Reference
下面的不同種類的writer的目的其實就是將接收到的信息寫入不同的地方
1》File writer
功能:將收到的消息寫到一個文件中
元素名:file
允許屬性:
- formatid—此接收器將使用的格式
- path——日志文件的路徑
<seelog> <outputs> <file path="log.log"/> </outputs> </seelog>
注意:
- 不要使用文件名中不允許的任何特殊符號。
- 不要在多進程環境中使用相同的文件,以避免日志不一致。
2》Console writer
功能:將收到的消息寫到標准輸出中
元素名:Console
允許屬性:
- formatid—此接收器將使用的格式
<seelog> <outputs> <console/> </outputs> </seelog>
3》Rolling file writer (or "Rotation file writer")
功能:將接收到的消息寫入文件,直到日期更改或文件超過指定的限制。在此之后,將重命名當前日志文件並開始將日志寫到新文件中。如果按大小滾動,可以設置重命名文件計數的限制(如果需要的話),然后當文件計數超過指定的限制時,滾動寫入器將刪除舊的文件。
這樣做的好處就是能夠控制日志的大小,對於一個高流量的Web應用來說,日志的增長是十分可怕的。這樣就能夠保證日志文件不會因為不斷變大而導致我們的磁盤空間不夠引起問題
元素名:rollingfile
允許屬性:
- formatid—此接收器將使用的格式
- filename-到日志文件的路徑。在創建時,該路徑被分為文件夾路徑和實際文件名。后者將作為所有文件的公共部分,在滾動中起作用:
- 在按“date”滾動的情況下,文件名將以這種方式形成:filename.DATE_PATTERN。例如(如果未設置全名標志,請參見下面):app.log, app.log.11.Aug.14, app.log.11.Aug.15
- 在按'size'滾動的情況下,文件名將以這種方式形成:"filename.#"。例如app.log, app.log.1, app.log.2, app.log.3
- type-旋轉類型:"date"或 "size"
- namemode-滾動文件的命名模式。可能的值:"postfix"(后綴), "prefix"(前綴)。如果設置為“postfix”,滾動的文件看起來像'file.log.1', 'file.log.02.01.2006';如果設置為"prefix",則看起來像是'1.file.log', '02.01.2006.file.log'。默認為"postfix"
- maxrolls -重命名文件的最大計數。當超過此限制時,將刪除舊卷。值應該是>= 0。注意:在2.3版本之前,此屬性不適用於“date”滾動寫入器。
- archivetype—一個用於指定存儲舊卷而不是刪除舊卷的存檔的類型的屬性。可能的值:“none”、“zip”、“gzip”。如果設置為“none”,則不執行存檔(只刪除舊卷)。
- archiveexploded——一個用於指定日志是應該被分解還是分組到同一個歸檔文件中的屬性。
- archivepath——當archivetype未設置為“none”時使用的屬性。指定存儲舊卷的存檔的路徑。
當允許'size'類型時的屬性:
- maxsize——這是導致滾動的超出大小限制(以字節為單位)。
當允許'date'類型時的屬性:
- datepattern—這是使用在“time.LocalTime().Format”去形成一個文件名的模式。當'time. localtime (). format (rollFileWriter.datePattern)'返回與當前文件名不同的內容時,將發生“date”(實際上,這意味着日期和時間)滾動。這意味着你可以使用帶有日期標識符(如“02.01.2006”)的格式創建日滾動。或者你可以使用帶有小時標識符(如“02.01.2006.15”)的格式創建小時滾動。
- fullname — 一個影響當前文件名的布爾屬性。如果設置為“true”,則當前文件名將使用與卷名相同的命名約定。例如,日志文件列表將類似於app.log.11.Aug.13, app.log.11.Aug.14, app.log.11.Aug.15, ...,而不是app.log, app.log.11.Aug.14, app.log.11.Aug.15, ....。默認設置為false
⚠️
- 不要使用文件名中不允許的任何特殊符號。
- 不要在多進程環境中使用相同的文件,以避免日志不一致。
舉例說明:
<seelog> <outputs> <rollingfile type="size" filename="logs/roll.log" maxsize="1000" maxrolls="5" /> </outputs> </seelog>
<seelog> <outputs> <rollingfile type="date" filename="logs/roll.log" datepattern="02.01.2006" maxrolls="7" /> </outputs> </seelog>
4》Buffered writer
功能:充當緩沖區包裝其他寫入器。緩沖寫入器將數據存儲在內存中,並在每次刷新周期或緩沖區滿時刷新數據。將日志先存在內存中,定期寫入文件,適合日志並發量較大或 IO 比較緊張的場合
元素名:buffered
允許的屬性:
- formatid—此接收器將使用的格式
- size-緩沖區大小(以字節為單位)
- flushperiod——緩沖區刷新之間的間隔(單位為毫秒)
舉例說明:
<seelog> <outputs> <buffered size="10000" flushperiod="1000"> <file path="bufFile.log"/> </buffered> </outputs> </seelog>
<seelog> <outputs> <buffered size="10000" flushperiod="1000" formatid="someFormat"> <rollingfile type="date" filename="logs/roll.log" datepattern="02.01.2006" /> </buffered> </outputs> <formats> ... </formats> </seelog>
注意:該寫入器使用特定格式累計寫入的數據,然后將其刷新到內部寫入器中。因此,內部寫入器不能有自己的格式:僅為緩沖元素設置“formatid”,如上一個示例中所示。
5》SMTP writer
功能:在給定的post服務器上使用密碼保護(但通常不安全)的電子郵件帳戶向指定的收件人發送電子郵件。通過郵件smtp方式將日志文件發送出去(一般會發給相應的運維人員)
元素名:smtp
允許使用的屬性:
- senderaddress-發件人的電子郵件地址
- sendername—發送方的名稱
- hostname——post服務器的主機名(通常是mail.XXX.YYY)
- hostport - post服務器的TCP端口(通常為587)
- username——用於登錄到post服務器的用戶名
- password——post服務器的密碼
- subject-電子郵件的主題
子元素名:recipient
允許使用的屬性:
- address-收件人的電子郵件地址(接收來自通知者的消息)
子元素名:cacertdirpath
- path-到帶有PEM證書文件的目錄的路徑。
舉例說明:
<seelog> <outputs> <smtp senderaddress="noreply-notification-service@none.org" sendername="Automatic notification service" hostname="mail.none.org" hostport="587" username="nns" password="123"> <recipient address="john-smith@none.com"/> <recipient address="hans-meier@none.com"/> </smtp> </outputs> </seelog>
<seelog> <outputs> <filter levels="error,critical"> <smtp senderaddress="nns@none.org" sendername="ANS" hostname="mail.none.org" hostport="587" username="nns" password="123" subject="test"> <cacertdirpath path="cacdp1"/> <recipient address="hans-meier@none.com"/> <header name="Priority" value="Urgent" /> <header name="Importance" value="high" /> <header name="Sensitivity" value="Company-Confidential" /> <header name="Auto-Submitted" value="auto-generated" /> </smtp> </filter> </outputs> </seelog>
注意:
查看上面的第一個示例,了解如何使用SMTP writer。請記住,電子郵件noreply-notific-service@none.org不能被認為是安全可靠的。由於配置中顯式的發送密碼,它可能會受到黑客攻擊,我們強烈建議你不要為SMTP writer使用個人或公司的post帳戶。最好的做法是專門設置一個獨立的郵政帳戶,特別是電子郵件通知服務。
第二個示例演示了使用這個編寫器的最合理的方法——在(罕見的)特殊情況下使用應用程序的通知。從技術上講,你可以設置其他過濾級別,但也要做好被診斷郵件淹沒的准備。
可以設置當發生了錯誤的時候我們就能夠將錯誤信息發送給運維,這樣就就能夠在遇到問題時及時處理
6》Conn writer
功能:將接收到的消息寫入網絡連接。
元素名:conn
允許使用的屬性:
- formatid— 此接收器將使用的格式
- net— 使用的網絡(“tcp”, “udp”,“tcp4”、“udp4”……)
- addr -網絡地址(":1000","127.0.0.1:8888",…)
- reconnectonmsg -如果為true,連接將在每次寫入時打開,否則在第一次寫入時打開。默認為false。
- usetls -如果為true,將使用TLS。
- insecureskipverify — 設置tls.Config的InsecureSkipVerify標志。如果設置了useTLS就使用它
舉例說明:
<seelog type="sync"> <outputs> <conn net="tcp" addr=":8888" /> </outputs> </seelog>
<outputs> <conn formatid="syslog" net="tcp4" addr="server.address:5514" tls="true" insecureskipverify="true" /> </outputs> <formats> <format id="syslog" format="%CustomSyslogHeader(20) %Msg%n"/> </formats>
%CustomSyslogHeader的示例代碼可以查看下面的 4)Custom formatters自定義格式器 - 2》
6.Formatting格式化
1)
Seelog提供了更改發送到字節接收器的消息格式的功能。格式設置在“formats”配置部分,如下:
<formats> <format id="common" format="[%LEV] %Msg"/> <format id="critical" format="%Time %Date %RelFile %Func %Msg"/> <format id="criticalemail" format="Critical error on our server!\n %Time %Date %RelFile %Func %Msg \nSent by Seelog"/> </formats>
每個“format”節點都有一個“id”和“format”屬性。Id是用於將格式鏈接到dispatchers/receiver的格式的唯一標識符。“format”屬性用於用特殊的格式化對象指定格式字符串,以“%”符號開頭。當將消息寫入字節接收器時,這些特殊符號將被上下文值或特殊字符串替換。
要查看格式是如何鏈接到dispatchers/receiver的,查看上面 Dispatchers and receivers分配器和接收器 的“Formatting”部分
2)formatters列表
1》Message context消息上下文
- %Level - 日志級別 (Trace, Debug, Info, Warn, Error, Critical)
- %Lev - 短日志級別 (Trc, Dbg, Inf, Wrn, Err, Crt)
- %LEVEL - 大寫的日志級別 (TRACE, DEBUG, INFO, WARN, ERROR, CRITICAL)
- %LEV - 縮寫版大寫日志級別 (TRC, DBG, INF, WRN, ERR, CRT)
- %l - 超簡寫版日志級別(t, d, i, w, e, c)
- %Msg - 消息文本 (string)
- %FullPath - 完整的調用者文件路徑
- %File - 只調用文件名
- %RelFile - 相對於應用程序運行目錄的調用者路徑
- %Func - 調用函數名
- %FuncShort - 調用函數名的最后一個點后面的部分
- %Line - 日志記錄器被調用時的行號
2》日期和時間
- %Ns - time.Now().UnixNano()
- %Date - 指明日期格式為‘2006-01-02’的快捷方式
- %Time - 指明時間格式為‘15:04:05’的快捷方式
- %Date(...) - 日期的格式,指定在括號中。使用標准time.Format,因此請檢查http://golang.org/src/pkg/time/format.go中的標識符列表。可以這樣使用:“%Date(2006-01-02)”(或任何其他格式)
- %UTCNs - time.Now().UTC().UnixNano()
- %UTCDate - 指明日期格式為‘2006-01-02’ (UTC)的快捷方式
- %UTCTime - 指明時間格式為‘15:04:05’ (UTC)的快捷方式
- %UTCDate(...) - UTC日期的格式,指定在括號中。使用標准時間。格式,因此請檢查http://golang.org/src/pkg/time/format.go中的標識符列表。可以這樣使用:“%UTCDate(2006-01-02)”(或任何其他格式)
3》特殊符號
- %EscN - terminal ANSI CSI n [;k] m escape. 檢查彩色輸出的詳細信息
- %n - 換行符
- %t - 制表符
3)Predefined formats預定義格式
Seelog配置解析器識別一組特殊的格式標識符,稱為預定義格式。引入這些標識符是為了避免每次在配置文件中顯式創建“xml”或“json”等公共格式。
1》使用
使用任何輸出節點的“formatid”屬性中的預定義格式標識符之一。
格式標識符的全名由前綴std:和下面列出的標識符之一組成。以下是完整的id-format對的列表:
- xml-debug:
<time>%Ns</time><lev>%Lev</lev><msg>%Msg</msg><path>%RelFile</path><func>%Func</func>
- xml-debug-short:
<t>%Ns</t><l>%l</l><m>%Msg</m><p>%RelFile</p><f>%Func</f>
- xml:
<time>%Ns</time><lev>%Lev</lev><msg>%Msg</msg>
- xml-short:
<t>%Ns</t><l>%l</l><m>%Msg</m>
- json-debug:
{"time":%Ns,"lev":"%Lev","msg":"%Msg","path":"%RelFile","func":"%Func"}
- json-debug-short:
{"t":%Ns,"l":"%Lev","m":"%Msg","p":"%RelFile","f":"%Func"}
- json:
{"time":%Ns,"lev":"%Lev","msg":"%Msg"}
- json-short:
{"t":%Ns,"l":"%Lev","m":"%Msg"}
- debug:
[%LEVEL] %RelFile:%Func %Date %Time %Msg%n
- debug-short:
[%LEVEL] %Date %Time %Msg%n
- fast:
%Ns %l %Msg%n
注意:
使用你自己的格式覆蓋預定義的格式並使用以std:開頭的標識符創建你自己的格式是完全合法的,但是不建議使用這兩種做法。
舉例說明:
<seelog> <outputs formatid="std:json"> <console/> </outputs> </seelog>
seelog.Info("Hello world!")輸出為:
{"time":1341218159882230900,"lev":"Inf","msg":"Hello world!"}
將以json的格式輸出信息到標准輸出中
4)Custom formatters自定義格式器
如果你覺得上面2)formatters列表 中的標准格式化集還不夠,你可以指定自己的自定義格式化程序。
要做到這一點,你必須使用seelog。使用RegisterCustomFormatter函數去為你要使用的新格式別名注冊工廠。在此之后(如果返回的錯誤為nil),你可以在隨后創建的任何日志記錄器中使用指定的格式別名。
自定義格式化器可以參數化。參數字符串(括號內)如果存在,則傳遞給你注冊的工廠func。
讓我們看幾個例子。
1》Alternative log level names可選日志級別名稱
注冊你的新格式器:
var myLevelToString = map[log.LogLevel]string{ //這是我自定義的格式,重命名對應的日志級別 log.TraceLvl: "MyTrace", log.DebugLvl: "MyDebug", log.InfoLvl: "MyInfo", log.WarnLvl: "MyWarn", log.ErrorLvl: "MyError", log.CriticalLvl: "MyCritical", log.Off: "MyOff", } func createMyLevelFormatter(params string) log.FormatterFunc { return func(message string, level log.LogLevel, context log.LogContextInterface) interface{} { levelStr, ok := myLevelToString[level] if !ok { return "Broken level!" } return levelStr } } func init() { err := log.RegisterCustomFormatter("MyLevel", createMyLevelFormatter) //注冊 if err != nil { ... } }
現在你可以在你的配置中使用注冊的“MyLevel”:
<seelog type="sync"> <outputs formatid="main"> <console/> </outputs> <formats> <format id="main" format="%MyLevel %Msg%n"/> </formats> </seelog>
使用這個配置后一個日志記錄器將創建:
log.Trace("Test message!")
輸出為:
MyTrace Test message!
2》用於TLS連接並寫入syslog的格式器
注冊你的格式器:
var hostName string var appName = "test" var pid int var levelToSyslogSeverity = map[log.LogLevel]int{ // Mapping to RFC 5424 where possible log.TraceLvl: 7, log.DebugLvl: 7, log.InfoLvl: 6, log.WarnLvl: 4, log.ErrorLvl: 3, log.CriticalLvl: 2, log.Off: 7, } func createSyslogHeaderFormatter(params string) log.FormatterFunc { facility := 20 i, err := strconv.Atoi(params) if err == nil && i >= 0 && i <= 23 { facility = i } return func(message string, level log.LogLevel, context log.LogContextInterface) interface{} { return fmt.Sprintf("<%d>1 %s %s %s %d - -", facility*8+levelToSyslogSeverity[level], time.Now().Format("2006-01-02T15:04:05Z07:00"), hostName, appName, pid) } } func init() { hostName, _ = os.Hostname() pid = os.Getpid() err := log.RegisterCustomFormatter("CustomSyslogHeader", createSyslogHeaderFormatter) if err != nil { ... } }
現在你可以在你的配置中使用注冊的“CustomSyslogHeader”:
<outputs> <conn formatid="syslog" net="tcp4" addr="server.address:5514" tls="true" insecureskipverify="true" /> </outputs> <formats> <format id="syslog" format="%CustomSyslogHeader(20) %Msg%n"/> </formats>
使用這個配置后一個日志記錄器將被創建:
log.Info("Test message!")
輸出為:
<167>1 2014-05-14T21:39:00+04:00 imp-pcl test 7624 - - Test message!
示例:

package main import ( log "github.com/cihub/seelog" "fmt" ) func main() { defer log.Flush() defaultFormat() stdFormat() dateTimeFormat() dateTimeCustomFormat() logLevelTypesFormat() fileTypesFormat() funcFormat() xmlFormat() } //測試的是默認的格式 func defaultFormat() { fmt.Println("Default format") testConfig := ` <seelog type="sync" />` logger, err := log.LoggerFromConfigAsBytes([]byte(testConfig)) if err != nil { fmt.Println(err) } log.ReplaceLogger(logger) log.Trace("Test message!") } //標准格式 func stdFormat() { fmt.Println("Standard fast format") testConfig := ` <seelog type="sync"> <outputs formatid="main"> <console/> </outputs> <formats> <format id="main" format="%Ns [%Level] %Msg%n"/> </formats> </seelog>` logger, _ := log.LoggerFromConfigAsBytes([]byte(testConfig)) log.ReplaceLogger(logger) log.Trace("Test message!") } //日期和時間的格式默認 func dateTimeFormat() { fmt.Println("Date time format") testConfig := ` <seelog type="sync"> <outputs formatid="main"> <console/> </outputs> <formats> <format id="main" format="%Date/%Time [%LEV] %Msg%n"/> </formats> </seelog>` logger, err := log.LoggerFromConfigAsBytes([]byte(testConfig)) if err != nil { fmt.Println(err) } loggerErr := log.ReplaceLogger(logger) if loggerErr != nil { fmt.Println(loggerErr) } log.Trace("Test message!") } //自定義的日期和時間格式 func dateTimeCustomFormat() { fmt.Println("Date time custom format") testConfig := ` <seelog type="sync"> <outputs formatid="main"> <console/> </outputs> <formats> <format id="main" format="%Date(2006 Jan 02/3:04:05.000000000 PM MST) [%Level] %Msg%n"/> </formats> </seelog>` logger, _ := log.LoggerFromConfigAsBytes([]byte(testConfig)) log.ReplaceLogger(logger) log.Trace("Test message!") } //日志級別類型格式 func logLevelTypesFormat() { fmt.Println("Log level types format") testConfig := ` <seelog type="sync"> <outputs formatid="main"> <console/> </outputs> <formats> <format id="main" format="%Level %Lev %LEVEL %LEV %l %Msg%n"/> </formats> </seelog>` logger, _ := log.LoggerFromConfigAsBytes([]byte(testConfig)) log.ReplaceLogger(logger) log.Trace("Test message!") } //文件類型格式 func fileTypesFormat() { fmt.Println("File types format") testConfig := ` <seelog type="sync"> <outputs formatid="main"> <console/> </outputs> <formats> <format id="main" format="%File %FullPath %RelFile %Msg%n"/> </formats> </seelog>` logger, _ := log.LoggerFromConfigAsBytes([]byte(testConfig)) log.ReplaceLogger(logger) log.Trace("Test message!") } //函數格式 func funcFormat() { fmt.Println("Func format") testConfig := ` <seelog type="sync"> <outputs formatid="main"> <console/> </outputs> <formats> <format id="main" format="%Func %Msg%n"/> </formats> </seelog>` logger, _ := log.LoggerFromConfigAsBytes([]byte(testConfig)) log.ReplaceLogger(logger) log.Trace("Test message!") } //xml格式 func xmlFormat() { fmt.Println("Xml format") //等價於<time>%Ns</time><lev>%Lev</lev><msg>%Msg</msg><path>%RelFile</path><func>%Func</func> testConfig := ` <seelog type="sync"> <outputs formatid="main"> <console/> </outputs> <formats> <format id="main" format="` + `<log>` + `<time>%Ns</time>` + `<lev>%l</lev>` + `<msg>%Msg</msg>` + `</log>"/> </formats> </seelog>` logger, _ := log.LoggerFromConfigAsBytes([]byte(testConfig)) log.ReplaceLogger(logger) log.Trace("Test message!") }
返回:
userdeMBP:go-learning user$ go run test.go Default format 1551702181896235000 [Trace] Test message! Standard fast format 1551702181896299000 [Trace] Test message! Date time format 2019-03-04/20:23:01 [TRC] Test message! Date time custom format 2019 Mar 04/8:23:01.896487000 PM CST [Trace] Test message! Log level types format Trace Trc TRACE TRC t Test message! File types format test.go /Users/user/go-learning/test.go test.go Test message! Func format main.funcFormat Test message! Xml format <log><time>1551702181896732000</time><lev>t</lev><msg>Test message!</msg></log>
7.Colorizing output着色輸出
有時你可能想要對終端輸出進行着色。這通常使用CSI n [;k] m序列來完成。
Seelog格式使用XML設置,因此無法在格式標識符中使用這些序列。但是有一種特殊格式的動詞叫做“EscM”,它以“n[;k]”部分作為參數。你可以使用它來為終端輸出執行圖形化選項定制。
舉例說明:
<seelog> <outputs> <console formatid="colored"/> </outputs> <formats> <format id="colored" format="%EscM(46)%Level%EscM(49) %Msg%n%EscM(0)"/> </formats> </seelog>
使用上面的配置,然后記錄任何消息,日志級別將有青色背景。46設置背景,49將其重置。
建議
在消息末尾使用%EscM(0)(以及%n)重置所有圖形更改
8.Migration from standard log package從標准日志包遷移
對於那些希望在已經通過標准pkg使用日志功能的應用程序中開始使用seelog的人來說,從日志包遷移到seelog是一項常見的任務。
1)不兼容性
Seelog概念與標准日志概念不同,因此它們不兼容。
例1 : log.Printf。在seelog中沒有類似的函數,因為Printf不攜帶任何日志級別的信息,所以如果要遷移,不清楚應該怎么替換log.Printf。可能是seelog.Infof或seelog.Debugf 或其他。
例2 : log.Panicf。它根據格式和panic創建消息,使用格式化的消息作為panic文本。在seelog中,對任何日志函數的一次調用都可能產生具有不同格式(取決於配置)的多個消息,因此不清楚應該使用什么作為panic文本。
例3 : log.SetPrefix。Seelog沒有類似的函數,因為對任何日志函數(例如Debug)的一次調用都可能產生具有不同格式的多個消息,因此使用一個這樣的全局函數是沒有意義的。此外,seelog的核心原則之一是避免通過代碼進行配置。所有配置都是通過配置文件進行的。這樣的函數會打破seelog的概念。
2)結論
目前(Go1.X), seelog不兼容標准日志包,不能有任何“標准日志pkg兼容性”的功能。
3)遷移注意點
盡管實際上你不能用seelog替換日志,並且具有相同的行為(如前所述,它們只是不兼容),但是你可能希望自動化從日志到seelog的遷移。我們建議為你的項目創建一個替代腳本
遷移腳本示例:
下面是一個Python腳本,它在多個文件中對多個日志包函數執行一些替換。它可以作為存根來滿足你自己的替換需求。
import os, sys,shutil,re changeDir= './test' openFlags = 'rb' writeFlags = 'r+b' encoding = 'utf-8' backupPostfix = '.backup' goFilesRx = r'.+\.go$' patterns = [ (re.compile(ur'''(?P<before>import[\s\S]*?)"log"''', re.U | re.M), ur'''\g<before>log "github.com/cihub/seelog"'''), # change import (re.compile(ur'''log.Print(?P<after>.*?)''', re.U), ur'''log.Info\g<after>'''), # Print -> Info (re.compile(ur'''log.Println(?P<after>.*?)''', re.U), ur'''log.Info\g<after>'''), # Println -> Info (re.compile(ur'''log.Printf(?P<after>.*?)''', re.U), ur'''log.Infof\g<after>'''), # Printf -> Infof (re.compile(ur'''(?P<ws>[\t ]*)log.Panic\((?P<values>.*?)\)''', re.U), ur'''\g<ws>log.Info(\g<values>)\n\g<ws>panic(fmt.Sprint(\g<values>))'''), # Panic -> Info + panic(...) (re.compile(ur'''(?P<ws>[\t ]*)log.Panicf\((?P<values>.*?)\)''', re.U), ur'''\g<ws>log.Infof(\g<values>)\n\g<ws>panic(fmt.Sprint(\g<values>))'''), # Panicf -> Info + panic(...) # ... and so on ] def rewriteFile(fl, text): fl.read() # To preserve file creation date fl.seek(0) fl.write(text.encode(encoding)) fl.truncate() def replacePatterns(filePath, backup): # Open file and get its contents input = open(filePath, openFlags) fileText = unicode(input.read(), encoding) input.close() found = False # Make replacements for all patterns for pc in patterns: origRx = pc[0] replRx = pc[1] replacedText = re.sub(origRx, replRx, fileText) if fileText != replacedText: found = True fileText = replacedText # If any replacements were made, write the changed file if found: if backup: bckName = filePath + backupPostfix shutil.copy2(filePath, bckName) outF = open(filePath,writeFlags) rewriteFile(outF, fileText) outF.close() def replaceFunc(a, dir, files): for f in files: fPath = dir + '/' + f if re.search(goFilesRx, f, re.U) and os.path.isfile(fPath): replacePatterns(fPath, True) os.path.walk(changeDir, replaceFunc, 3)
9.Example config
這個配置沒有任何實際價值,它只是在一個地方演示了大多數功能。有關詳細信息,請查看wiki,它包含每個seelog特性的描述。要快速參考,請查看參考reference部分。
<seelog type="asynctimer" asyncinterval="5000000" minlevel="debug" maxlevel="error"> <exceptions> <exception funcpattern="*main.test*Something*" minlevel="info"/> <exception filepattern="*main.go" minlevel="error"/> </exceptions> <outputs formatid="main"> //指明其將使用在<formats>中的<format id="main" 處指明的格式來輸出信息,如果里面的子元素中有定義自己的formatid,將會將其覆蓋 <console/> //指明將outputs中的日志內容在輸入到文件、內存、SMTP等的同時還輸出到標准輸出,即終端中 <splitter formatid="format1"> //指明將輸出以<format id="format1"格式寫到log.log和log2.log文件中 <file path="log.log"/> <file path="log2.log"/> </splitter> <splitter formatid="format2"> //splitter用於細分<outputs>日志格式 <file path="log3.log"/> <file path="log4.log"/> </splitter> <rollingfile formatid="someformat" type="size" filename="./log/roll.log" maxsize="100" maxrolls="5" /> //指明將輸出以<format id="someformat"格式寫到./log/roll.log 回滾文件中 <buffered formatid="testlevels" size="10000" flushperiod="1000"> //指明將輸出以<format id="testlevels"格式寫到緩沖區(即內存)中,然后再存儲到./log/bufFileFlush.log文件中 <file path="./log/bufFileFlush.log"/> </buffered> <filter levels="error"> //只顯示error日志級別 <file path="./log/error.log"/> //寫到該文件中 <smtp senderaddress="noreply-notification-service@none.org" //並將日志內容發送給下面<recipient指定的接收者 sendername="Automatic notification service" hostname="mail.none.org" hostport="587" username="nns" password="123"> <recipient address="john-smith@none.com"/> <recipient address="hans-meier@none.com"/> </smtp> <conn net="tcp4" addr="server.address:5514" tls="true" insecureskipverify="true" /> //同時也將這個日志內容通過網絡傳輸到地址server.address:5514上 </filter> </outputs> <formats> //這里就是指定上面對應的日志內容輸出應該分別使用什么樣的格式 <format id="main" format="%Date(2006 Jan 02/3:04:05.000000000 PM MST) [%Level] %Msg%n"/> <format id="someformat" format="%Ns [%Level] %Msg%n"/> <format id="testlevels" format="%Level %Lev %LEVEL %LEV %l %Msg%n"/> <format id="usetags" format="<msg>%Msg</time>"/> <format id="format1" format="%Date/%Time [%LEV] %Msg%n"/> <format id="format2" format="%File %FullPath %RelFile %Msg%n"/> </formats> </seelog>
10.Logger types reference類型
日志記錄器類型設置在頂部的“seelog”元素中。屬性名是“type”。
1)Synchronous同步
功能:在調用log func的相同goroutine中處理日志消息
類型屬性值:Sync
額外屬性:None
舉例說明:
<seelog type="sync"> ... </seelog>
2)Asynchronous loop異步循環
功能:在單獨的goroutine中處理日志消息。從“for”循環中的消息隊列獲取消息。
類型屬性值:asyncloop(這是默認類型,所以你可以忽略“type”屬性)
額外屬性:none
舉例說明,下面的兩種寫法是等價的:
<seelog type="asyncloop"> ... </seelog>
等價於:
<seelog>
...
</seelog>
3)Asynchronous timer異步計時器
功能:在單獨的goroutine中處理日志消息。獲取具有指定時間間隔的消息隊列中的消息。
類型屬性值:asynctimer
額外屬性:
- asyncinterval——計時器時間間隔(以納秒為單位)
舉例說明:
<seelog type="asynctimer" asyncinterval="5000"> ... </seelog>
示例:

package main import ( "fmt" log "github.com/cihub/seelog" "strings" "time" ) var longMessage = strings.Repeat("A", 1024*100) func main() { defer log.Flush() syncLogger() fmt.Println() asyncLoopLogger() fmt.Println() asyncTimerLogger() } func syncLogger() { fmt.Println("Sync test") testConfig := ` <seelog type="sync"> <outputs> <filter levels="trace"> <file path="log.log"/> </filter> <filter levels="debug"> <console /> </filter> </outputs> </seelog> ` logger, _ := log.LoggerFromConfigAsBytes([]byte(testConfig)) log.UseLogger(logger) doTest() } func asyncLoopLogger() { fmt.Println("Async loop test") testConfig := ` <seelog> <outputs> <filter levels="trace"> <file path="log.log"/> </filter> <filter levels="debug"> <console /> </filter> </outputs> </seelog>` logger, _ := log.LoggerFromConfigAsBytes([]byte(testConfig)) log.UseLogger(logger) doTest() time.Sleep(1e9) } func asyncTimerLogger() { fmt.Println("Async timer test") testConfig := ` <seelog type="asynctimer" asyncinterval="5000000"> <outputs> <filter levels="trace"> <file path="log.log"/> </filter> <filter levels="debug"> <console /> </filter> </outputs> </seelog>` logger, _ := log.LoggerFromConfigAsBytes([]byte(testConfig)) log.UseLogger(logger) doTest() time.Sleep(1e9) } func doTest() { start := time.Now() for i := 0; i < 50; i += 2 { fmt.Printf("%d\n", i) log.Trace(longMessage) log.Debugf("%d", i+1) } end := time.Now() dur := end.Sub(start) fmt.Printf("Test took %d ns\n", dur) }
返回
userdeMBP:go-learning user$ go run test.go Sync test //同步進行 0 1551702598965060000 [Debug] 1 2 1551702598965356000 [Debug] 3 4 1551702598965630000 [Debug] 5 6 1551702598965883000 [Debug] 7 8 1551702598966119000 [Debug] 9 10 1551702598966379000 [Debug] 11 12 1551702598966609000 [Debug] 13 14 1551702598966909000 [Debug] 15 16 1551702598967173000 [Debug] 17 18 1551702598967419000 [Debug] 19 20 1551702598968144000 [Debug] 21 22 1551702598968266000 [Debug] 23 24 1551702598968362000 [Debug] 25 26 1551702598968473000 [Debug] 27 28 1551702598968555000 [Debug] 29 30 1551702598968641000 [Debug] 31 32 1551702598968725000 [Debug] 33 34 1551702598968810000 [Debug] 35 36 1551702598968901000 [Debug] 37 38 1551702598968985000 [Debug] 39 40 1551702598969240000 [Debug] 41 42 1551702598969625000 [Debug] 43 44 1551702598969757000 [Debug] 45 46 1551702598969858000 [Debug] 47 48 1551702598969947000 [Debug] 49 Test took 5368269 ns Async loop test //循環同時獲取日志消息 0 2 4 1551702598970071000 [Debug] 1 1551702598970077000 [Debug] 3 6 8 10 1551702598970084000 [Debug] 5 1551702598970504000 [Debug] 7 1551702598970511000 [Debug] 9 12 14 16 18 20 1551702598970742000 [Debug] 11 1551702598970755000 [Debug] 13 1551702598970761000 [Debug] 15 1551702598970766000 [Debug] 17 1551702598970772000 [Debug] 19 22 24 1551702598972970000 [Debug] 21 1551702598973067000 [Debug] 23 26 28 1551702598973221000 [Debug] 25 1551702598973232000 [Debug] 27 30 1551702598973481000 [Debug] 29 32 1551702598973598000 [Debug] 31 34 1551702598973706000 [Debug] 33 36 38 1551702598973967000 [Debug] 35 1551702598973974000 [Debug] 37 40 1551702598974162000 [Debug] 39 42 44 1551702598974171000 [Debug] 41 1551702598974376000 [Debug] 43 46 48 1551702598974470000 [Debug] 45 1551702598974476000 [Debug] 47 Test took 4692732 ns 1551702598974482000 [Debug] 49 Async timer test 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 Test took 989547 ns //async Timer設置是5000000ns后才開始讀取日志消息 1551702599976687000 [Debug] 1 1551702599976713000 [Debug] 3 1551702599976736000 [Debug] 5 1551702599977358000 [Debug] 7 1551702599977372000 [Debug] 9 1551702599977386000 [Debug] 11 1551702599977404000 [Debug] 13 1551702599977418000 [Debug] 15 1551702599977431000 [Debug] 17 1551702599977444000 [Debug] 19 1551702599977458000 [Debug] 21 1551702599977471000 [Debug] 23 1551702599977484000 [Debug] 25 1551702599977497000 [Debug] 27 1551702599977510000 [Debug] 29 1551702599977523000 [Debug] 31 1551702599977537000 [Debug] 33 1551702599977550000 [Debug] 35 1551702599977563000 [Debug] 37 1551702599977576000 [Debug] 39 1551702599977589000 [Debug] 41 1551702599977602000 [Debug] 43 1551702599977615000 [Debug] 45 1551702599977628000 [Debug] 47 1551702599977641000 [Debug] 49
4)Asynchronous adaptive異步適應性
功能:類似於異步計時器日志記錄器,但其間隔取決於隊列中剩下的消息數量。創建這種類型的日志記錄器是為了避免日志消息隊列溢出:隊列中的消息越多,獲取它們的速度就越快。在seelogg -example:adaptive_main.go中進行了演示:
package main import ( "time" log "github.com/cihub/seelog" ) func main() { defer log.Flush() loadAdaptiveConfig() testMsgIntensity(1) testMsgIntensity(5) testMsgIntensity(10) } func testMsgIntensity(intensity int) { log.Default.Infof("Intensity test: %d", intensity) for j := 0; j < 4; j++ { for i := 0; i < intensity; i++ { log.Tracef("trace %d", i) <-time.After(time.Second / time.Duration(intensity)) //time.Second / time.Duration(5)表示1秒除以5的時間進行一次循環 } } log.Default.Info("Messages sent") <-time.After(time.Second * time.Duration(intensity)) } func loadAdaptiveConfig() { testConfig := `<seelog type="adaptive" mininterval="200000000" maxinterval="1000000000" critmsgcount="5"> <outputs formatid="msg"> <console/> </outputs> <formats> <format id="msg" format="%Time: %Msg%n"/> </formats> </seelog>` logger, _ := log.LoggerFromConfigAsBytes([]byte(testConfig)) log.ReplaceLogger(logger) }
返回:
userdeMBP:go-learning user$ go run test.go 1551693376581324000 [Info] Intensity test: 1 17:56:16: trace 0 17:56:17: trace 0 17:56:18: trace 0 17:56:19: trace 0 1551693380594712000 [Info] Messages sent 1551693381595019000 [Info] Intensity test: 5 17:56:21: trace 0 17:56:21: trace 1 17:56:21: trace 2 17:56:22: trace 3 17:56:22: trace 4 17:56:22: trace 0 17:56:22: trace 1 17:56:23: trace 2 17:56:23: trace 3 17:56:23: trace 4 17:56:23: trace 0 17:56:23: trace 1 17:56:24: trace 2 17:56:24: trace 3 1551693385632063000 [Info] Messages sent 17:56:24: trace 4 17:56:24: trace 0 17:56:24: trace 1 17:56:25: trace 2 17:56:25: trace 3 17:56:25: trace 4 17:56:30: trace 0 1551693390634801000 [Info] Intensity test: 10 17:56:30: trace 1 17:56:30: trace 2 17:56:30: trace 3 17:56:31: trace 4 17:56:31: trace 5 17:56:31: trace 6 17:56:31: trace 7 17:56:31: trace 8 17:56:31: trace 9 17:56:31: trace 0 17:56:31: trace 1 17:56:31: trace 2 17:56:31: trace 3 17:56:32: trace 4 17:56:32: trace 5 1551693394732474000 [Info] Messages sent 17:56:32: trace 6 17:56:32: trace 7 17:56:32: trace 8 17:56:32: trace 9 17:56:32: trace 0 17:56:32: trace 1 17:56:32: trace 2 17:56:33: trace 3 17:56:33: trace 4 17:56:33: trace 5 17:56:33: trace 6 17:56:33: trace 7 17:56:33: trace 8 17:56:33: trace 9 17:56:33: trace 0 17:56:33: trace 1 17:56:33: trace 2 17:56:34: trace 3 17:56:34: trace 4 17:56:34: trace 5 17:56:34: trace 6 17:56:34: trace 7 17:56:34: trace 8 17:56:34: trace 9
如果我們使用下面的概念:
- I - 獲取下一項之前的間隔
- m - 最小間隔
- M - 極大區間
- c - 當前消息計數
- C - 關鍵信息計數
計算間隔的公式為:
I = m + (C - Min(c, C)) / C * (M - m)
類型屬性值:adaptive
額外屬性:
- mininterval-最小間隔(以納秒為單位)
- maxinterval -最大間隔(以納秒為單位)
- critmsgcount -關鍵消息計數
舉例說明:
<seelog type="adaptive" mininterval="2000000" maxinterval="1000000000" critmsgcount="500"> <outputs formatid="msg"> <console/> </outputs> <formats> <format id="msg" format="%Time: %Msg%n"/> </formats> </seelog>
11.簡單舉例:
1)僅將日志輸出到終端
配置文件seelog.xml為,參考https://blog.csdn.net/luckytanggu/article/details/80345134:
<seelog type="asynctimer" asyncinterval="1000000" minlevel="debug" maxlevel="error"> <outputs formatid="main"> <!-- 僅實現將日志內容輸出到終端 --> <console/> </outputs> <formats> <!-- 設置格式,輸出UTC日期 UTC時間 - 縮寫版大寫日志級別 - 相對於應用程序運行目錄的調用者路徑 - 日志記錄器被調用時的行號 - 消息文本(最后換行) --> <format id="main" format="%UTCDate %UTCTime - [%LEV] - %RelFile - l%Line - %Msg%n"/> </formats> </seelog>
然后應用為:
package main import ( log "github.com/cihub/seelog" "fmt" ) func main() { logger, err := log.LoggerFromConfigAsFile("seelog.xml") if err != nil { fmt.Println("parse seelog.xml error") } log.ReplaceLogger(logger) defer log.Flush() log.Info("Hello from Seelog!") }
輸出為:
userdeMBP:go-learning user$ go run test.go 2019-03-04 09:19:11 - [INF] - test.go - l20 - Hello from Seelog!
2)將日志輸出到終端和文件中
<seelog type="asynctimer" asyncinterval="1000000" minlevel="debug" maxlevel="error"> <outputs formatid="main"> <!-- 僅實現將日志內容輸出到終端 --> <console/> <!-- 該文件將使用"format1"格式來覆蓋"main"格式,並將文件信息寫到log.log文件中--> <splitter formatid="format1"> <file path="log.log"/> </splitter> </outputs> <formats> <!-- 設置格式,輸出UTC日期 UTC時間 - 縮寫版大寫日志級別 - 相對於應用程序運行目錄的調用者路徑 - 日志記錄器被調用時的行號 - 消息文本(最后換行) --> <format id="main" format="%UTCDate %UTCTime - [%LEV] - %RelFile - l%Line - %Msg%n"/> <!-- 格式為以2006 Jan 02/3:04:05.000000000 PM MST格式輸出日期 正常的日志級別 消息文本(最后換行)--> <format id="format1" format="%Date(2006 Jan 02/3:04:05.000000000 PM MST) [%Level] %Msg%n"/> </formats> </seelog>
然后返回:
userdeMBP:go-learning user$ go run test.go 2019-03-04 09:28:35 - [INF] - test.go - l20 - Hello from Seelog!
日志文件中的輸出為:
userdeMBP:go-learning user$ cat log.log 2019 Mar 04/5:28:35.215823000 PM CST [Info] Hello from Seelog!