banner
biuaxia

biuaxia

"万物皆有裂痕,那是光进来的地方。"
github
bilibili
tg_channel

【転載】Go言語プロジェクトでZapログライブラリを使用する

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などの複数のレベルはサポートされていません。
  • エラーログにはFatalPanicがあります
    • Fatal ログはos.Exit(1)を呼び出してプログラムを終了します
    • Panic ログはログメッセージを書き込んだ後に panic を発生させます
    • しかし、panic を発生させたりプログラムを終了させたりせずにエラーを記録できる ERROR ログレベルが欠けています
  • 呼び出し元の関数名や行番号、日付や時間のフォーマットなど、ログのフォーマット機能が不足しています。
  • ログのローテーション機能を提供しません。

Uber-go Zap#

Zapは非常に高速で、構造化された、レベル別の Go ログライブラリです。

なぜ Uber-go zap を選ぶのか#

  • 構造化ログ記録と printf スタイルのログ記録の両方を提供します
  • 非常に高速です

Uber-go Zap のドキュメントによると、その性能は類似の構造化ログパッケージよりも優れており、標準ライブラリよりも速いです。以下は Zap が公開したベンチマーク情報です。

メッセージと 10 個のフィールドを記録する:

パッケージ時間zap に対する時間 %割り当てられたオブジェクト
⚡️ zap862 ns/op+0%5 allocs/op
⚡️ zap (sugared)1250 ns/op+45%11 allocs/op
zerolog4021 ns/op+366%76 allocs/op
go-kit4542 ns/op+427%105 allocs/op
apex/log26785 ns/op+3007%115 allocs/op
logrus29501 ns/op+3322%125 allocs/op
log1529906 ns/op+3369%122 allocs/op

静的文字列を記録し、コンテキストや printf スタイルのテンプレートを使用しない:

パッケージ時間zap に対する時間 %割り当てられたオブジェクト
⚡️ zap118 ns/op+0%0 allocs/op
⚡️ zap (sugared)191 ns/op+62%2 allocs/op
zerolog93 ns/op-21%0 allocs/op
go-kit280 ns/op+137%11 allocs/op
標準ライブラリ499 ns/op+323%2 allocs/op
apex/log1990 ns/op+1586%10 allocs/op
logrus3129 ns/op+2552%24 allocs/op
log153887 ns/op+3194%23 allocs/op

インストール#

以下のコマンドを実行して zap をインストールします。

go get -u go.uber.org/zap

Zap Logger の設定#

Zap は 2 種類のロガーを提供します —Sugared LoggerLogger

パフォーマンスが非常に良いがそれほど重要でないコンテキストでは、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 つの設定が必要です ——EncoderWriteSyncerLogLevel

  1. Encoder : エンコーダー(ログの書き込み方法)。私たちは、すぐに使えるNewJSONEncoder()を使用し、事前設定されたProductionEncoderConfig()を使用します。
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
  1. WriterSyncer :ログがどこに書き込まれるかを指定します。zapcore.AddSync()関数を使用し、開いたファイルハンドルを渡します。
file, _ := os.Create("./test.log")
writeSyncer := zapcore.AddSync(file)
  1. 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/、原文の内容をより理解しやすくするために若干の変更を加えています。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。