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 logger に 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.sogo.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/、原文の内容をより理解しやすくするために若干の変更を加えています。