title: 【転載】Go 言語プロジェクトで Zap ログライブラリを使用する
date: 2021-08-09 16:37:33
comment: false
toc: true
category:
- Golang
tags: - 转载
- Go
- zap
- ログ
この記事は以下から転載されています:Go 言語プロジェクトで Zap ログライブラリを使用する | 李文周のブログ
この記事では、まず Go 言語のネイティブなログライブラリの使用法を紹介し、その後、非常に人気のある Uber のオープンソースの zap ログライブラリについて詳しく説明し、Lumberjack と組み合わせてログのローテーションとアーカイブを実現する方法を紹介します。
Go 言語プロジェクトで Zap ログライブラリを使用する#
イントロダクション#
多くの Go 言語プロジェクトでは、以下の機能を提供できる良いロガーが必要です:
- イベントをアプリケーションのコンソールではなく、ファイルに記録できること。
- ログのローテーション - ファイルサイズ、時間、または間隔に基づいてログファイルをローテーションできること。
- 異なるログレベルをサポートすること。例えば INFO、DEBUG、ERROR など。
- 呼び出し元のファイル / 関数名や行番号、ログの時間などの基本情報を印刷できること。
デフォルトの Go Logger#
Uber-go の zap パッケージを紹介する前に、まず Go 言語が提供する基本的なログ機能を見てみましょう。Go 言語が提供するデフォルトのログパッケージはhttps://golang.org/pkg/log/です。
Go Logger の実装#
Go 言語でロガーを実装するのは非常に簡単です —— 新しいログファイルを作成し、それをログの出力先として設定します。
Logger の設定#
以下のコードのようにロガーを設定できます。
func SetupLogger() {
logFileLocation, _ := os.OpenFile("/Users/q1mi/test.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0744)
log.SetOutput(logFileLocation)
}
Logger の使用#
このロガーを使用するために、いくつかの仮想コードを書いてみましょう。
現在の例では、URL への HTTP 接続を確立し、ステータスコード / エラーをログファイルに記録します。
func simpleHttpGet(url string) {
resp, err := http.Get(url)
if err != nil {
log.Printf("URL %s の取得中にエラーが発生しました: %s", url, err.Error())
} else {
log.Printf("%s のステータスコード: %s", url, resp.Status)
resp.Body.Close()
}
}
Logger の実行#
上記のコードを実行して、ロガーの動作を確認してみましょう。
func main() {
SetupLogger()
simpleHttpGet("www.google.com")
simpleHttpGet("http://www.google.com")
}
上記のコードを実行すると、test.logファイルが作成され、以下の内容がこのログファイルに追加されます。
2019/05/24 01:14:13 URL www.google.com の取得中にエラーが発生しました: Get www.google.com: unsupported protocol scheme ""
2019/05/24 01:14:14 http://www.google.com のステータスコード: 200 OK
Go Logger の利点と欠点#
利点#
最大の利点は非常に使いやすいことです。任意のio.Writerをログ出力として設定し、書き込むログを送信できます。
欠点#
- 基本的なログレベルに制限される
Printオプションが 1 つだけです。INFO/DEBUGなどの複数のレベルはサポートされていません。
- エラーログには
FatalとPanicがあります- Fatal ログは
os.Exit(1)を呼び出してプログラムを終了します - Panic ログはログメッセージを書き込んだ後に panic をスローします
- しかし、panic をスローしたりプログラムを終了したりせずにエラーを記録できる ERROR ログレベルが欠けています
- Fatal ログは
- 呼び出し元の関数名や行番号、日付と時間のフォーマットなど、ログのフォーマット能力が不足しています。
- ログのローテーション機能を提供しません。
Uber-go Zap#
Zapは非常に高速で、構造化された、ログレベル分けされた Go ログライブラリです。
なぜ Uber-go zap を選ぶのか#
- 構造化ログ記録と printf スタイルのログ記録の両方を提供します
- 非常に高速です
Uber-go Zap のドキュメントによると、そのパフォーマンスは類似の構造化ログパッケージよりも優れており —— 標準ライブラリよりも速いです。以下は Zap が発表したベンチマーク情報です。
メッセージと 10 のフィールドを記録する:
| パッケージ | 時間 | zap への時間 % | アロケートされたオブジェクト |
|---|---|---|---|
| ⚡️ zap | 862 ns/op | +0% | 5 allocs/op |
| ⚡️ zap (sugared) | 1250 ns/op | +45% | 11 allocs/op |
| zerolog | 4021 ns/op | +366% | 76 allocs/op |
| go-kit | 4542 ns/op | +427% | 105 allocs/op |
| apex/log | 26785 ns/op | +3007% | 115 allocs/op |
| logrus | 29501 ns/op | +3322% | 125 allocs/op |
| log15 | 29906 ns/op | +3369% | 122 allocs/op |
静的文字列を記録し、コンテキストや printf スタイルのテンプレートは使用しない:
| パッケージ | 時間 | zap への時間 % | アロケートされたオブジェクト |
|---|---|---|---|
| ⚡️ zap | 118 ns/op | +0% | 0 allocs/op |
| ⚡️ zap (sugared) | 191 ns/op | +62% | 2 allocs/op |
| zerolog | 93 ns/op | -21% | 0 allocs/op |
| go-kit | 280 ns/op | +137% | 11 allocs/op |
| 標準ライブラリ | 499 ns/op | +323% | 2 allocs/op |
| apex/log | 1990 ns/op | +1586% | 10 allocs/op |
| logrus | 3129 ns/op | +2552% | 24 allocs/op |
| log15 | 3887 ns/op | +3194% | 23 allocs/op |
インストール#
以下のコマンドを実行して zap をインストールします。
go get -u go.uber.org/zap
Zap Logger の設定#
Zap は 2 種類のロガーを提供します —Sugared LoggerとLogger。
パフォーマンスが良いがそれほど重要でないコンテキストでは、SugaredLoggerを使用します。これは他の構造化ログ記録パッケージよりも 4〜10 倍速く、構造化と printf スタイルのログ記録をサポートします。
毎マイクロ秒と毎メモリアロケーションが重要なコンテキストでは、Loggerを使用します。これはsugaredLoggerよりもさらに速く、メモリアロケーションの回数も少なくなりますが、強い型の構造化ログ記録のみをサポートします。
Logger#
zap.NewProduction()/zap.NewDevelopment()またはzap.Example()を呼び出して Logger を作成します。- 上記の各関数はロガーを作成します。唯一の違いは、記録される情報が異なることです。例えば、プロダクションロガーはデフォルトで呼び出し関数の情報、日付と時間などを記録します。
- Logger を通じて Info/Error などを呼び出します。
- デフォルトでは、ログはアプリケーションのコンソールインターフェースに印刷されます。
var logger *zap.Logger
func main() {
InitLogger()
defer logger.Sync()
simpleHttpGet("www.google.com")
simpleHttpGet("http://www.google.com")
}
func InitLogger() {
logger, _ = zap.NewProduction()
}
func simpleHttpGet(url string) {
resp, err := http.Get(url)
if err != nil {
logger.Error(
"URLの取得中にエラーが発生しました..",
zap.String("url", url),
zap.Error(err))
} else {
logger.Info("成功..",
zap.String("statusCode", resp.Status),
zap.String("url", url))
resp.Body.Close()
}
}
上記のコードでは、まずロガーを作成し、次に Info/Error などのロガーメソッドを使用してメッセージを記録します。
ロギングメソッドの構文は次のようになります:
func (log *Logger) MethodXXX(msg string, fields ...Field)
ここでMethodXXXは可変引数関数で、Info / Error / Debug / Panic などである可能性があります。各メソッドはメッセージ文字列と任意の数のzapcore.Field引数を受け取ります。
各zapcore.Fieldは実際には一組のキーと値のペアです。
上記のコードを実行すると、以下の出力が得られます:
{"level":"error","ts":1572159218.912792,"caller":"zap_demo/temp.go:25","msg":"URLの取得中にエラーが発生しました..","url":"www.sogo.com","error":"Get www.sogo.com: unsupported protocol scheme \"\"","stacktrace":"main.simpleHttpGet\n\t/Users/q1mi/zap_demo/temp.go:25\nmain.main\n\t/Users/q1mi/zap_demo/temp.go:14\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:203"}
{"level":"info","ts":1572159219.1227388,"caller":"zap_demo/temp.go:30","msg":"成功..","statusCode":"200 OK","url":"http://www.sogo.com"}
Sugared Logger#
次に、Sugared Logger を使用して同じ機能を実装してみましょう。
- 実装の大部分は基本的に同じです。
- 唯一の違いは、主ロガーの
.Sugar()メソッドを呼び出してSugaredLoggerを取得することです。 - その後、
SugaredLoggerを使用してprintf形式でログ記録します。
以下は、Loggerの代わりにSugaredLoggerを使用するように修正されたコードです:
var sugarLogger *zap.SugaredLogger
func main() {
InitLogger()
defer sugarLogger.Sync()
simpleHttpGet("www.google.com")
simpleHttpGet("http://www.google.com")
}
func InitLogger() {
logger, _ := zap.NewProduction()
sugarLogger = logger.Sugar()
}
func simpleHttpGet(url string) {
sugarLogger.Debugf("GETリクエストを試みています: %s", url)
resp, err := http.Get(url)
if err != nil {
sugarLogger.Errorf("URL %s の取得中にエラーが発生しました: エラー = %s", url, err)
} else {
sugarLogger.Infof("成功! ステータスコード = %s, URL = %s", resp.Status, url)
resp.Body.Close()
}
}
上記のコードを実行すると、以下の出力が得られます:
{"level":"error","ts":1572159149.923002,"caller":"logic/temp2.go:27","msg":"URL www.sogo.com の取得中にエラーが発生しました: エラー = Get www.sogo.com: unsupported protocol scheme \"\"","stacktrace":"main.simpleHttpGet\n\t/Users/q1mi/zap_demo/logic/temp2.go:27\nmain.main\n\t/Users/q1mi/zap_demo/logic/temp2.go:14\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:203"}
{"level":"info","ts":1572159150.192585,"caller":"logic/temp2.go:29","msg":"成功! ステータスコード = 200 OK, URL = http://www.sogo.com"}
これまでのところ、これらの 2 つのロガーは JSON 構造形式で出力を印刷しています。
このブログの後半では、SugaredLogger についてさらに詳しく説明し、どのようにしてさらに設定を行うかを学びます。
Logger のカスタマイズ#
ログを端末ではなくファイルに書き込む#
最初に行う変更は、ログをアプリケーションのコンソールに印刷するのではなく、ファイルに書き込むことです。
zap.New(…)メソッドを使用して手動ですべての設定を渡します。zap.NewProduction()のようなプリセットメソッドを使用してロガーを作成するのではなく。
func New(core zapcore.Core, options ...Option) *Logger
zapcore.Coreには 3 つの設定が必要です ——Encoder、WriteSyncer、LogLevel。
- Encoder : エンコーダー(ログの書き込み方法)。私たちは、開箱即用の
NewJSONEncoder()を使用し、事前設定されたProductionEncoderConfig()を使用します。
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
- WriterSyncer :ログがどこに書き込まれるかを指定します。
zapcore.AddSync()関数を使用し、開いたファイルハンドルを渡します。
file, _ := os.Create("./test.log")
writeSyncer := zapcore.AddSync(file)
- Log Level :どのレベルのログが書き込まれるか。
上記の部分の Logger コードを修正し、InitLogger()メソッドを書き直します。残りのメソッド —main() / SimpleHttpGet()はそのままにします。
func InitLogger() {
writeSyncer := getLogWriter()
encoder := getEncoder()
core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
logger := zap.New(core)
sugarLogger = logger.Sugar()
}
func getEncoder() zapcore.Encoder {
return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
}
func getLogWriter() zapcore.WriteSyncer {
file, _ := os.Create("./test.log")
return zapcore.AddSync(file)
}
これらの修正されたロガー設定を使用して上記の部分のmain()関数を呼び出すと、以下の出力がtest.logファイルに印刷されます。
{"level":"debug","ts":1572160754.994731,"msg":"GETリクエストを試みています: www.sogo.com"}
{"level":"error","ts":1572160754.994982,"msg":"URL www.sogo.com の取得中にエラーが発生しました: エラー = Get www.sogo.com: unsupported protocol scheme \"\""}
{"level":"debug","ts":1572160754.994996,"msg":"GETリクエストを試みています: http://www.sogo.com"}
{"level":"info","ts":1572160757.3755069,"msg":"成功! ステータスコード = 200 OK, URL = http://www.sogo.com"}
JSON エンコーダーを通常のログエンコーダーに変更#
次に、エンコーダーを JSON エンコーダーから通常のエンコーダーに変更したいと思います。そのためには、NewJSONEncoder()をNewConsoleEncoder()に変更する必要があります。
return zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
これらの修正されたロガー設定を使用して上記の部分のmain()関数を呼び出すと、以下の出力がtest.logファイルに印刷されます。
1.572161051846623e+09 debug GETリクエストを試みています: www.sogo.com
1.572161051846828e+09 error URL www.sogo.com の取得中にエラーが発生しました: エラー = Get www.sogo.com: unsupported protocol scheme ""
1.5721610518468401e+09 debug GETリクエストを試みています: http://www.sogo.com
1.572161052068744e+09 info 成功! ステータスコード = 200 OK, URL = http://www.sogo.com
時間エンコーディングを変更し、呼び出し元の詳細情報を追加#
設定に対する変更を考慮すると、以下の 2 つの問題があります:
- 時間が人間にとって読みやすくない形式で表示されています。例えば 1.572161051846623e+09
- 呼び出し元関数の詳細情報がログに表示されていません。
最初に行うべきことは、デフォルトのProductionConfig()をオーバーライドし、以下の変更を行うことです:
- 時間エンコーダーを変更する
- ログファイル内で大文字でログレベルを記録する
func getEncoder() zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
return zapcore.NewConsoleEncoder(encoderConfig)
}
次に、zap ロガーコードを修正し、ログに呼び出し関数情報を記録する機能を追加します。そのためには、zap.New(..)関数にOptionを追加します。
logger := zap.New(core, zap.AddCaller())
これらの修正されたロガー設定を使用して上記の部分のmain()関数を呼び出すと、以下の出力がtest.logファイルに印刷されます。
2019-10-27T15:33:29.855+0800 DEBUG logic/temp2.go:47 GETリクエストを試みています: www.sogo.com
2019-10-27T15:33:29.855+0800 ERROR logic/temp2.go:50 URL www.sogo.com の取得中にエラーが発生しました: エラー = Get www.sogo.com: unsupported protocol scheme ""
2019-10-27T15:33:29.856+0800 DEBUG logic/temp2.go:47 GETリクエストを試みています: http://www.sogo.com
2019-10-27T15:33:30.125+0800 INFO logic/temp2.go:52 成功! ステータスコード = 200 OK, URL = http://www.sogo.com
Lumberjack を使用したログのローテーションとアーカイブ#
このログプログラムに欠けている唯一の機能は、ログのローテーションとアーカイブ機能です。
Zap 自体はログファイルのローテーションとアーカイブをサポートしていません
ログのローテーションとアーカイブ機能を追加するために、私たちはサードパーティのライブラリLumberjackを使用します。
インストール#
以下のコマンドを実行して Lumberjack をインストールします。
go get -u github.com/natefinch/lumberjack
Zap ロガーに Lumberjack を追加#
Zap に Lumberjack サポートを追加するには、WriteSyncerコードを修正する必要があります。以下のコードのようにgetLogWriter()関数を修正します:
func getLogWriter() zapcore.WriteSyncer {
lumberJackLogger := &lumberjack.Logger{
Filename: "./test.log",
MaxSize: 10,
MaxBackups: 5,
MaxAge: 30,
Compress: false,
}
return zapcore.AddSync(lumberJackLogger)
}
Lumberjack Logger は以下のプロパティを入力として受け取ります:
- Filename: ログファイルの場所
- MaxSize:ローテーション前のログファイルの最大サイズ(MB 単位)
- MaxBackups:古いファイルの最大数
- MaxAges:古いファイルの最大日数
- Compress:古いファイルを圧縮 / アーカイブするかどうか
すべての機能をテストする#
最終的に、Zap/Lumberjack ロガーの完全なサンプルコードは以下の通りです:
package main
import (
"net/http"
"github.com/natefinch/lumberjack"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var sugarLogger *zap.SugaredLogger
func main() {
InitLogger()
defer sugarLogger.Sync()
simpleHttpGet("www.sogo.com")
simpleHttpGet("http://www.google.com")
}
func InitLogger() {
writeSyncer := getLogWriter()
encoder := getEncoder()
core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
logger := zap.New(core, zap.AddCaller())
sugarLogger = logger.Sugar()
}
func getEncoder() zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
return zapcore.NewConsoleEncoder(encoderConfig)
}
func getLogWriter() zapcore.WriteSyncer {
lumberJackLogger := &lumberjack.Logger{
Filename: "./test.log",
MaxSize: 1,
MaxBackups: 5,
MaxAge: 30,
Compress: false,
}
return zapcore.AddSync(lumberJackLogger)
}
func simpleHttpGet(url string) {
sugarLogger.Debugf("GETリクエストを試みています: %s", url)
resp, err := http.Get(url)
if err != nil {
sugarLogger.Errorf("URL %s の取得中にエラーが発生しました: エラー = %s", url, err)
} else {
sugarLogger.Infof("成功! ステータスコード = %s, URL = %s", resp.Status, url)
resp.Body.Close()
}
}
上記のコードを実行すると、以下の内容がtest.logファイルに出力されます。
2019-10-27T15:50:32.944+0800 DEBUG logic/temp2.go:48 GETリクエストを試みています: www.sogo.com
2019-10-27T15:50:32.944+0800 ERROR logic/temp2.go:51 URL www.sogo.com の取得中にエラーが発生しました: エラー = Get www.sogo.com: unsupported protocol scheme ""
2019-10-27T15:50:32.944+0800 DEBUG logic/temp2.go:48 GETリクエストを試みています: http://www.sogo.com
2019-10-27T15:50:33.165+0800 INFO logic/temp2.go:53 成功! ステータスコード = 200 OK, URL = http://www.sogo.com
また、main関数内でループしてログを記録し、ログファイルが自動的にローテーションおよびアーカイブされるかどうかをテストできます(ログファイルは 1MB ごとにローテーションされ、現在のディレクトリに最大 5 つのバックアップが保存されます)。
これで、Zap ログライブラリを Go アプリケーションプロジェクトに統合する方法をまとめました。
翻訳元https://dev-journal.in/2019/05/27/adding-uber-go-zap-logger-to-golang-project/の内容をより理解しやすくするために、若干の変更を加えています。