Logrus 是 Go (golang) 的結構化記錄器,與標準庫記錄器 API 完全相容。
Logrus 處於維護模式。我們不會引進新功能。以一種不會破壞很多人的專案的方式來做到這一點太難了,這是您最不想從日誌庫中獲得的東西(再次...)。
這並不意味著 Logrus 已經死了。 Logrus 將繼續維護安全性、(向後相容)錯誤修復和效能(我們受到介面的限制)。
我相信 Logrus 最大的貢獻是在當今 Golang 結構化日誌記錄的廣泛使用中發揮了作用。似乎沒有理由對 Logrus V2 進行重大、突破性的迭代,因為出色的 Go 社群已經獨立建構了這些迭代。許多奇妙的替代方案如雨後春筍般湧現。如果使用我們今天對 Go 中結構化日誌的了解重新設計,Logrus 看起來就會像那些。例如,請查看 Zerolog、Zap 和 Apex。
看到奇怪的區分大小寫的問題嗎?過去可以以大寫和小寫形式匯入 Logrus。由於Go包環境的原因,這在社區中引起了問題,我們需要一個標準。某些環境遇到了大寫變體的問題,因此決定使用小寫變體。使用logrus
的所有內容都需要使用小寫: github.com/sirupsen/logrus
。任何不是的包都應該更改。
要修復 Glide,請參閱這些評論。有關外殼問題的深入解釋,請參閱此評論。
在開發中很好地進行了顏色編碼(當附加 TTY 時,否則只是純文字):
使用log.SetFormatter(&log.JSONFormatter{})
,可以透過logstash或Splunk輕鬆解析:
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
{"level":"warning","msg":"The group's number increased tremendously!",
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
當未附加 TTY 時,使用預設的log.SetFormatter(&log.TextFormatter{})
,輸出與 logfmt 格式相容:
time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
為了確保即使連接了 TTY 也能實現此行為,請如下設定格式化程序:
log . SetFormatter ( & log. TextFormatter {
DisableColors : true ,
FullTimestamp : true ,
})
如果您希望將呼叫方法新增為字段,請透過以下方式指示記錄器:
log . SetReportCaller ( true )
這會將呼叫者加入為“方法”,如下所示:
{ "animal" : " penguin " , "level" : " fatal " , "method" : " github.com/sirupsen/arcticcreatures.migrate " , "msg" : " a penguin swims by " ,
"time" : " 2014-03-10 19:57:38.562543129 -0400 EDT " }
time="2015-03-26T01:27:38-04:00" level=fatal method=github.com/sirupsen/arcticcreatures.migrate msg="a penguin swims by" animal=penguin
請注意,這確實增加了可測量的開銷 - 成本取決於 Go 的版本,但在最近的 1.6 和 1.7 測試中介於 20% 到 40% 之間。您可以透過基準在您的環境中驗證這一點:
go test -bench=.*CallerTracing
組織的名稱已更改為小寫字母,並且不會改回來。如果您因區分大小寫而遇到匯入衝突,請使用小寫匯入: github.com/sirupsen/logrus
。
使用 Logrus 最簡單的方法就是包級匯出記錄器:
package main
import (
log "github.com/sirupsen/logrus"
)
func main () {
log . WithFields (log. Fields {
"animal" : "walrus" ,
}). Info ( "A walrus appears" )
}
請注意,它與 stdlib 記錄器完全 api 相容,因此您可以用log "github.com/sirupsen/logrus"
替換任何地方的log
匯入,現在您將擁有 Logrus 的靈活性。您可以根據需要進行自訂:
package main
import (
"os"
log "github.com/sirupsen/logrus"
)
func init () {
// Log as JSON instead of the default ASCII formatter.
log . SetFormatter ( & log. JSONFormatter {})
// Output to stdout instead of the default stderr
// Can be any io.Writer, see below for File example
log . SetOutput ( os . Stdout )
// Only log the warning severity or above.
log . SetLevel ( log . WarnLevel )
}
func main () {
log . WithFields (log. Fields {
"animal" : "walrus" ,
"size" : 10 ,
}). Info ( "A group of walrus emerges from the ocean" )
log . WithFields (log. Fields {
"omg" : true ,
"number" : 122 ,
}). Warn ( "The group's number increased tremendously!" )
log . WithFields (log. Fields {
"omg" : true ,
"number" : 100 ,
}). Fatal ( "The ice breaks!" )
// A common pattern is to re-use fields between logging statements by re-using
// the logrus.Entry returned from WithFields()
contextLogger := log . WithFields (log. Fields {
"common" : "this is a common field" ,
"other" : "I also should be logged always" ,
})
contextLogger . Info ( "I'll be logged with common and other field" )
contextLogger . Info ( "Me too" )
}
對於更進階的用法,例如從相同應用程式記錄到多個位置,您也可以建立logrus
Logger 的實例:
package main
import (
"os"
"github.com/sirupsen/logrus"
)
// Create a new instance of the logger. You can have any number of instances.
var log = logrus . New ()
func main () {
// The API for setting attributes is a little different than the package level
// exported logger. See Godoc.
log . Out = os . Stdout
// You could set this to any `io.Writer` such as a file
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
// if err == nil {
// log.Out = file
// } else {
// log.Info("Failed to log to file, using default stderr")
// }
log . WithFields (logrus. Fields {
"animal" : "walrus" ,
"size" : 10 ,
}). Info ( "A group of walrus emerges from the ocean" )
}
Logrus 鼓勵透過日誌欄位進行仔細、結構化的日誌記錄,而不是冗長的、無法解析的錯誤訊息。例如,您應該記錄更容易發現的內容,而不是: log.Fatalf("Failed to send event %s to topic %s with key %d")
:
log . WithFields (log. Fields {
"event" : event ,
"topic" : topic ,
"key" : key ,
}). Fatal ( "Failed to send event" )
我們發現這個 API 迫使您考慮以產生更有用的日誌訊息的方式進行日誌記錄。我們遇到過無數的情況,只要在已有的日誌語句中新增一個欄位就可以節省我們的時間。 WithFields
呼叫是可選的。
一般來說,Logrus 使用任何printf
系列函數都應該被視為您應該添加欄位的提示,但是,您仍然可以將printf
系列函數與 Logrus 一起使用。
通常,將欄位始終附加到應用程式或應用程式的一部分中的日誌語句會很有幫助。例如,您可能希望始終在請求的上下文中記錄request_id
和user_ip
。您可以建立一個logrus.Entry
來傳遞,而不是在每一行上編寫log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
requestLogger := log . WithFields (log. Fields { "request_id" : request_id , "user_ip" : user_ip })
requestLogger. Info ( "something happened on that request" ) # will log request_id and user_ip
requestLogger . Warn ( "something not great happened" )
您可以新增用於記錄層級的掛鉤。例如,將Error
、 Fatal
和Panic
上的錯誤傳送到異常追蹤服務,將資訊傳送到 StatsD 或同時記錄到多個位置,例如 syslog。
Logrus 內建鉤子。在init
中新增這些或您的自訂掛鉤:
import (
log "github.com/sirupsen/logrus"
"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake"
logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
"log/syslog"
)
func init () {
// Use the Airbrake hook to report errors that have Error severity or above to
// an exception tracker. You can create custom hooks, see the Hooks section.
log . AddHook ( airbrake . NewHook ( 123 , "xyz" , "production" ))
hook , err := logrus_syslog . NewSyslogHook ( "udp" , "localhost:514" , syslog . LOG_INFO , "" )
if err != nil {
log . Error ( "Unable to connect to local syslog daemon" )
} else {
log . AddHook ( hook )
}
}
注意:Syslog hook 也支援連接到本機 syslog(例如“/dev/log”或“/var/run/syslog”或“/var/run/log”)。有關詳細信息,請查看 syslog 掛鉤 README。
目前已知的服務掛鉤清單可以在此 wiki 頁面中找到
Logrus 有七個日誌記錄等級:Trace、Debug、Info、Warning、Error、Fatal 和 Panic。
log . Trace ( "Something very low level." )
log . Debug ( "Useful debugging information." )
log . Info ( "Something noteworthy happened!" )
log . Warn ( "You should probably take a look at this." )
log . Error ( "Something failed but I'm not quitting." )
// Calls os.Exit(1) after logging
log . Fatal ( "Bye." )
// Calls panic() after logging
log . Panic ( "I'm bailing." )
您可以在Logger
上設定日誌記錄級別,然後它只會記錄具有該嚴重性或高於其嚴重性的條目:
// Will log anything that is info or above (warn, error, fatal, panic). Default.
log . SetLevel ( log . InfoLevel )
如果您的應用程式具有偵錯或詳細環境,則在偵錯或詳細環境中設定log.Level = logrus.DebugLevel
可能會很有用。
注意:如果您希望全域 ( log.SetLevel(...)
) 和 syslog 日誌記錄使用不同的日誌級別,請檢查 syslog 掛鉤自述檔案。
除了使用WithField
或WithFields
新增的欄位之外,有些欄位會自動新增到所有日誌記錄事件中:
time
。建立條目時的時間戳。msg
。 AddFields
呼叫後,日誌訊息傳遞到{Info,Warn,Error,Fatal,Panic}
。例如, Failed to send event.
level
。日誌記錄等級。例如info
。 Logrus 沒有環境的概念。
如果您希望掛鉤和格式化程式僅在特定環境中使用,您應該自己處理。例如,如果您的應用程式有一個全域變數Environment
,它是環境的字串表示形式,您可以執行以下操作:
import (
log "github.com/sirupsen/logrus"
)
func init () {
// do something here to set environment depending on an environment variable
// or command-line flag
if Environment == "production" {
log . SetFormatter ( & log. JSONFormatter {})
} else {
// The TextFormatter is default, you don't actually have to do this.
log . SetFormatter ( & log. TextFormatter {})
}
}
此配置是logrus
預期使用方式,但生產中的 JSON 大多數情況下只有在使用 Splunk 或 Logstash 等工具進行日誌聚合時才有用。
內建的日誌格式化程式是:
logrus.TextFormatter
。如果 stdout 是 tty,則以顏色記錄事件,否則不記錄顏色。ForceColors
欄位設為true
。要強制沒有彩色輸出,即使存在 TTY,也將DisableColors
欄位設為true
。對於 Windows,請參閱 github.com/mattn/go-colorable。DisableLevelTruncation
欄位設為true
。PadLevelText
欄位設為true
可透過向等級文字新增填充來啟用此行為。logrus.JSONFormatter
。將欄位記錄為 JSON。第三方日誌格式化程式:
FluentdFormatter
。格式化可由 Kubernetes 和 Google Container Engine 解析的條目。GELF
。設定條目格式,使其符合 Graylog 的 GELF 1.1 規格。logstash
。將欄位記錄為 Logstash 事件。prefixed
.顯示日誌條目來源以及替代佈局。zalgo
。呼喚扎爾戈的力量。nested-logrus-formatter
。將 logrus 欄位轉換為嵌套結構。powerful-logrus-formatter
。列印日誌時取得檔案名稱、日誌行號以及最新函數名稱;將日誌保存到文件。caption-json-formatter
。 logrus 的訊息 json 格式化程序,新增了人類可讀的標題。您可以透過實作Formatter
介面來定義格式化程序,這需要一個Format
方法。 Format
採用*Entry
。 entry.Data
是一個Fields
類型( map[string]interface{}
),包含所有欄位以及預設欄位(請參閱上面的「條目」部分):
type MyJSONFormatter struct {
}
log . SetFormatter ( new ( MyJSONFormatter ))
func ( f * MyJSONFormatter ) Format ( entry * Entry ) ([] byte , error ) {
// Note this doesn't include Time, Level and Message which are available on
// the Entry. Consult `godoc` on information about those fields or read the
// source of the official loggers.
serialized , err := json . Marshal ( entry . Data )
if err != nil {
return nil , fmt . Errorf ( "Failed to marshal fields to JSON, %w" , err )
}
return append ( serialized , 'n' ), nil
}
io.Writer
記錄器Logrus 可以轉換為io.Writer
。該 writer 是io.Pipe
的末端,您有責任關閉它。
w := logger . Writer ()
defer w . Close ()
srv := http. Server {
// create a stdlib log.Logger that writes to
// logrus.Logger.
ErrorLog : log . New ( w , "" , 0 ),
}
寫入該寫入器的每一行都將使用格式化程式和掛鉤以通常的方式列印。這些條目的等級是info
。
這意味著我們可以輕鬆覆蓋標準庫記錄器:
logger := logrus . New ()
logger . Formatter = & logrus. JSONFormatter {}
// Use logrus for standard log output
// Note that `log` here references stdlib's log
// Not logrus imported under the name `log`.
log . SetOutput ( logger . Writer ())
Logrus 不提供日誌輪替。日誌輪替應該由可以壓縮和刪除舊日誌條目的外部程式(如logrotate(8)
)來完成。它不應該是應用程式級記錄器的功能。
工具 | 描述 |
---|---|
洛格魯斯伴侶 | Logrus mate是Logrus管理記錄器的工具,您可以透過設定檔初始記錄器的層級、掛鉤和格式化程序,在不同的環境下會產生不同的配置的記錄器。 |
Logrus 毒蛇助手 | Logrus 周圍的助手,可與 spf13/Viper 一起包裝,以使用尖牙加載配置!為了簡化 Logrus 配置,請使用 Logrus Mate 的一些行為。樣本 |
Logrus 有一個內建工具用來斷言日誌訊息的存在。這是透過test
掛鉤來實現的,並提供:
test.NewLocal
和test.NewGlobal
)基本上只是添加test
鉤子test.NewNullLogger
),僅記錄日誌訊息(並且不輸出任何內容): import (
"github.com/sirupsen/logrus"
"github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/assert"
"testing"
)
func TestSomething ( t * testing. T ){
logger , hook := test . NewNullLogger ()
logger . Error ( "Helloerror" )
assert . Equal ( t , 1 , len ( hook . Entries ))
assert . Equal ( t , logrus . ErrorLevel , hook . LastEntry (). Level )
assert . Equal ( t , "Helloerror" , hook . LastEntry (). Message )
hook . Reset ()
assert . Nil ( t , hook . LastEntry ())
}
Logrus 可以註冊一個或多個在記錄任何fatal
等級訊息時會呼叫的函數。註冊的處理程序將在 logrus 執行os.Exit(1)
之前執行。如果呼叫者需要正常關閉,此行為可能會有所幫助。與可以透過延遲recover
攔截的panic("Something went wrong...")
呼叫不同,對os.Exit(1)
的呼叫無法被攔截。
...
handler := func() {
// gracefully shutdown something...
}
logrus.RegisterExitHandler(handler)
...
預設情況下,Logger 受到並發寫入互斥鎖的保護。呼叫鉤子和寫入日誌時會保留互斥體。如果您確定不需要這樣的鎖定,您可以呼叫 logger.SetNoLock() 來停用鎖定。
不需要加鎖的情況包括:
您沒有註冊鉤子,或者鉤子呼叫已經是線程安全的。
寫入 logger.Out 已經是線程安全的,例如:
logger.Out 受鎖保護。
logger.Out 是一個使用O_APPEND
標誌開啟的 os.File 處理程序,每次寫入都小於 4k。 (這允許多線程/多進程寫入)
(參考http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/)