はじめに
Goの標準パッケージのlogはシンプルで使いやすいのですが、機能が貧弱としばしば言われます。例えば、他言語では当たり前なログレベルなどの機能はあえて組み込まれていません。
本記事では、乱立するlogの外部パッケージの中で最も有名なパッケージの1つであるlogrus
の使い方をまとめます。
インストール
$ go get github.com/sirupsen/logrus
機能
メッセージと変数の分離
logrusでは、 WithFields
を使って「メッセージ」と「変数」を分離して実装と表示させる方法が推奨されています。こちらの方が長い文字列を延々と書くよりも生産的だろ?という思想とのことです。
↓msgがログメッセージ、それ以降が変数です
time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
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")
WithFieldsを使うかどうかは任意です。
従来にあるような文字列の中に変数を埋め込むこともできます。
デフォルト変数
全てのログに共通的に出力するような変数は Fields
で一度だけの宣言にまとめることができます。
func main() {
requestID := 1
userIP := "192.168.10.1"
requestLogger := log.WithFields(log.Fields{"request_id": requestID, "user_ip": userIP})
requestLogger.Info("something happened on that request")
requestLogger.Warn("something not great happened")
}
INFO[0000] something happened on that request request_id=1 user_ip=192.168.10.1
WARN[0000] something not great happened request_id=1 user_ip=192.168.10.1
ログレベル
Trace, Debug, Info, Warning, Error, Fatal, Panicの7つのレベルが用意されています。
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.")
log.Fatal("Bye.") // Calls os.Exit(1) after logging
log.Panic("I'm bailing.") // Calls panic() after logging
SetLevel
で、セットしたレベル以上のレベルを出力するようにします。
log.SetLevel(log.InfoLevel)
ちなみに、TTYが有効になっているとログレベルに合わせてこんな風にカラフルに表示されます。
Fatalハンドラー
RegisterExitHandler
で、Fatal以上のレベルのメッセージを出力した際に、logrusがos.Exit(1)してしまう前に自動で実行される1つ以上の関数を登録しておくことができます。
handler := func() {
// gracefully shutdown something...
}
logrus.RegisterExitHandler(handler)
フォーマット
logrusの標準ではテキストフォーマットとJSONフォーマットが用意されています。
テキストフォーマット
デフォルトのフォーマットです。
何も指定しなかった場合はテキストフォーマットになります。
package main
import (
log "github.com/sirupsen/logrus"
)
func main() {
log.WithFields(log.Fields{
"animal": "walrus",
}).Info("A walrus appears")
}
INFO[0000] A walrus appears animal=walrus number=1 size=10
JSONフォーマット
SetFormatter
で&log.JSONFormatter{}
を指定すれば、JSONフォーマットになります。
例えばdatadogやLogstashなどでログを監視する場合は、JSONフォーマットを使うことが多いと思います。
func main() {
log.SetFormatter(&log.JSONFormatter{})
log.WithFields(log.Fields{
"animal": "walrus",
}).Info("A walrus appears")
}
{"animal":"walrus","level":"info","msg":"A walrus appears","time":"2019-06-16T09:27:28+09:00"}
環境
環境毎にログの設定を変える機能はlogrusには用意されていません。
プロダクション環境とその他の環境でログ設定を変更したい場合は、パッケージ変数などを活用して自分でハンドリングする必要があります。
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には用意されていません。
具体例
具体例1(init関数で共通設定)
package main
import (
"os"
log "github.com/sirupsen/logrus"
)
func init() {
// JSONフォーマット
log.SetFormatter(&log.JSONFormatter{})
// 標準エラー出力でなく標準出力とする
log.SetOutput(os.Stdout)
// Warningレベル以上を出力
log.SetLevel(log.WarnLevel)
}
func main() {
// ログ例1(出力されない)
log.WithFields(log.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
// ログ例2(出力される)
log.WithFields(log.Fields{
"omg": true,
"number": 122,
}).Warn("The group's number increased tremendously!")
// ログ例3(出力される)
log.WithFields(log.Fields{
"omg": true,
"number": 100,
}).Fatal("The ice breaks!")
// 共通的に使用する変数の設定
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")
}
{"level":"warning","msg":"The group's number increased tremendously!","number":122,"omg":true,"time":"2019-06-17T07:18:09+09:00"}
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,"time":"2019-06-17T07:18:09+09:00"}
exit status 1
具体例2(ログインスタンスで共通設定)
package main
import (
"os"
"github.com/sirupsen/logrus"
)
// ログインスタンス生成
var log = logrus.New()
func main() {
// 標準出力に設定
log.Out = os.Stdout
// ファイル出力の場合の例
// 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")
}
INFO[0000] A group of walrus emerges from the ocean animal=walrus size=10
最後に
logrusは簡単に導入できる反面、zap
などの他の外部パッケージに比べてると速度が遅いという短所もあります。
私が導入したプロジェクトではとりあえずJSONで出力できるようにしたかっただけで、それほどリクエスト数も多くなく、ログの出力数も多くはなかったのでlogrus
を導入しました。ケースを見定めてパッケージを選定する必要がありそうです。