Zap
Zap は Go で構築された高速で構造化されたレベル付きロギングコンポーネントです。
公式リポジトリ:uber-go/zap: Blazing fast, structured, leveled logging in Go. (github.com)
公式ドキュメント:zap package - go.uber.org/zap - Go Packages
インストール
go get -u go.uber.org/zapクイックスタート
公式ドキュメントには 2 つのクイックスタート例が提供されています。どちらもプロダクションレベルのロガーです。1 つ目は printf スタイルをサポートしていますが、パフォーマンスは比較的低めです。
logger, _ := zap.NewProduction()
defer logger.Sync() // プログラム終了時にバッファされたログ書き込みをファイルにフラッシュ
sugar := logger.Sugar()
sugar.Infow("failed to fetch URL",
"url", url,
"attempt", 3,
"backoff", time.Second,
)
sugar.Infof("Failed to fetch URL: %s", url)2 つ目は logger で、より良いパフォーマンスを持ちますが、強く型付けされた出力のみをサポートしています。
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("failed to fetch URL",
// 強く型付けされた Field 値としての構造化コンテキスト。
zap.String("url", url),
zap.Int("attempt", 3),
zap.Duration("backoff", time.Second),
)TIP
Zap の使い方は非常にシンプルです。難しい部分は、プロジェクトに適したロガーを設定することです。公式の例は少ないので、ソースコードのコメントをより多く読んでください。
設定
一般的に、ロガーの設定は設定ファイルで記述されます。Zap の設定も設定ファイルからのデシリアライゼーションをサポートしていますが、基本的な設定のみをサポートしています。高度な設定でさえ、公式の例は非常に簡潔で、プロダクション使用には不十分なので、詳細な設定について議論する必要があります。
まず、設定構造体全体を見てみましょう。各フィールドの意味を理解する必要があります。
type Config struct {
// 最小ログレベル
Level AtomicLevel `json:"level" yaml:"level"`
// 開発モード、主にスタックトレースに影響
Development bool `json:"development" yaml:"development"`
// コーラー追跡
DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`
// スタックトレース
DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"`
// サンプリング、ログパフォーマンスへの影響を制限しながら代表的なログのみを記録
Sampling *SamplingConfig `json:"sampling" yaml:"sampling"`
// エンコーディング、json または console モード
Encoding string `json:"encoding" yaml:"encoding"`
// エンコーダー設定、主に出力フォーマット設定
EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`
// ログファイル出力パス
OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
// エラーファイル出力パス
ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`
// ログにいくつかのデフォルト出力コンテンツを追加
InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`
}以下はエンコーダー設定の詳細です
type EncoderConfig struct {
// キー値、キーが空の場合、対応するプロパティは出力されません
MessageKey string `json:"messageKey" yaml:"messageKey"`
LevelKey string `json:"levelKey" yaml:"levelKey"`
TimeKey string `json:"timeKey" yaml:"timeKey"`
NameKey string `json:"nameKey" yaml:"nameKey"`
CallerKey string `json:"callerKey" yaml:"callerKey"`
FunctionKey string `json:"functionKey" yaml:"functionKey"`
StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"`
SkipLineEnding bool `json:"skipLineEnding" yaml:"skipLineEnding"`
LineEnding string `json:"lineEnding" yaml:"lineEnding"`
// カスタムエンコーダー
EncodeLevel LevelEncoder `json:"levelEncoder" yaml:"levelEncoder"`
EncodeTime TimeEncoder `json:"timeEncoder" yaml:"timeEncoder"`
EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"`
EncodeCaller CallerEncoder `json:"callerEncoder" yaml:"callerEncoder"`
// ロガー名エンコーダー
EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"`
// リフレクションエンコーダー、主に interface{} 型用、デフォルトの jsonencoder がない場合
NewReflectedEncoder func(io.Writer) ReflectedEncoder `json:"-" yaml:"-"`
// コンソール出力区切り文字列
ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"`
}Option は設定スイッチとアプリケーションについてで、多くの実装があります。
type Option interface {
apply(*Logger)
}
// Option 実装
type optionFunc func(*Logger)
func (f optionFunc) apply(log *Logger) {
f(log)
}
// アプリケーション
func Development() Option {
return optionFunc(func(log *Logger) {
log.development = true
})
}これは最も一般的に使用されるロガーコアです。その内部フィールドは基本的に設定ステップを表しています。デシリアライズ設定の公式ステップを参照することもできます。ほぼ同じです。
type ioCore struct {
// ログレベル
LevelEnabler
// ログエンコーディング
enc Encoder
// ログ書き込み
out WriteSyncer
}zap.Encoder はログのフォーマットとエンコードを担当します
zap.WriteSyncer はログ出力を担当し、主にファイルとコンソールへ
zap.LevelEnabler は最小ログレベルです。このレベル以下のログは syncer を通じて出力されません。
ログエンコーディング
ログエンコーディングは主にログのフォーマット詳細に関係します。まず、元のロガーを直接使用した出力を見てみましょう。
func TestQuickStart(t *testing.T) {
rawJSON := []byte(`{
"level": "debug",
"encoding": "json",
"outputPaths": ["stdout"],
"errorOutputPaths": ["stderr"],
"initialFields": {"foo": "bar"},
"encoderConfig": {
"messageKey": "message",
"levelKey": "level",
"levelEncoder": "lowercase"
}
}`)
var cfg zap.Config
if err := json.Unmarshal(rawJSON, &cfg); err != nil {
panic(err)
}
logger := zap.Must(cfg.Build())
defer logger.Sync()
logger.Info("logger construction succeeded")
}{"level":"info","message":"logger construction succeeded","foo":"bar"}このログにはいくつかの問題があることがわかります:
- タイムスタンプがない
- コーラー情報がない、このログがどこから出力されたかわからず、エラーのトラブルシューティングが困難
- スタックトレースがない
次に、これらの問題を段階的に解決しましょう。主に zapcore.EncoderConfig を修正します。まず、公式の直接デシリアライゼーションを使用せず、独自の設定ファイルを作成する必要があります。設定ファイル config.yml を作成します
# Zap Logger Configuration
zap:
prefix: ZapLogTest
timeFormat: 2006/01/02 - 15:04:05.00000
level: debug
caller: true
stackTrace: false
encode: console
# 出力先:file | console | both
writer: both
logFile:
maxSize: 20
backups: 5
compress: true
output:
- "./log/output.log"対応する構造体
// ZapConfig
// @Date: 2023-01-09 16:37:05
// @Description: zap logger configuration struct
type ZapConfig struct {
Prefix string `yaml:"prefix" mapstructure:"prefix"`
TimeFormat string `yaml:"timeFormat" mapstructure:"timeFormat"`
Level string `yaml:"level" mapstructure:"level"`
Caller bool `yaml:"caller" mapstructure:"caller"`
StackTrace bool `yaml:"stackTrace" mapstructure:"stackTrace"`
Writer string `yaml:"writer" mapstructure:"writer"`
Encode string `yaml:"encode" mapstructure:"encode"`
LogFile *LogFileConfig `yaml:"logFile" mapstructure:"logFile"`
}
// LogFileConfig
// @Date: 2023-01-09 16:38:45
// @Description: log file configuration struct
type LogFileConfig struct {
MaxSize int `yaml:"maxSize" mapstructure:"maxSize"`
BackUps int `yaml:"backups" mapstructure:"backups"`
Compress bool `yaml:"compress" mapstructure:"compress"`
Output []string `yaml:"output" mapstructure:"output"`
Errput []string `yaml:"errput" mapstructure:"errput"`
}TIP
設定の読み取りには Viper を使用します。コードは省略します。
type TimeEncoder func(time.Time, PrimitiveArrayEncoder)TimeEncoder は実際には関数です。公式が提供する他のタイムエンコーダーを使用することも、独自に作成することもできます。
func CustomTimeFormatEncoder(t time.Time, encoder zapcore.PrimitiveArrayEncoder) {
encoder.AppendString(global.Config.ZapConfig.Prefix + "\t" + t.Format(global.Config.ZapConfig.TimeFormat))
}全体部分は以下の通りです
func zapEncoder(config *ZapConfig) zapcore.Encoder {
// 新しい設定を作成
encoderConfig := zapcore.EncoderConfig{
TimeKey: "Time",
LevelKey: "Level",
NameKey: "Logger",
CallerKey: "Caller",
MessageKey: "Message",
StacktraceKey: "StackTrace",
LineEnding: zapcore.DefaultLineEnding,
FunctionKey: zapcore.OmitKey,
}
// カスタム時間形式
encoderConfig.EncodeTime = CustomTimeFormatEncoder
// 大文字のログレベル
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
// 秒単位の時間間隔
encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder
// 短いコーラー出力
encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
// ロガー名の完全シリアライゼーション
encoderConfig.EncodeName = zapcore.FullNameEncoder
// 最終ログエンコーディング:json または console
switch config.Encode {
case "json":
{
return zapcore.NewJSONEncoder(encoderConfig)
}
case "console":
{
return zapcore.NewConsoleEncoder(encoderConfig)
}
}
// デフォルト console
return zapcore.NewConsoleEncoder(encoderConfig)
}ログ出力
ログ出力はコンソール出力とファイル出力に分かれます。設定ファイルに基づいて動的に設定できます。ログファイルのローテーションが必要な場合は、別のサードパーティ依存関係を使用する必要があります。
go get -u github.com/natefinch/lumberjack最終コードは以下の通りです
func zapWriteSyncer(cfg *ZapConfig) zapcore.WriteSyncer {
syncers := make([]zapcore.WriteSyncer, 0, 2)
// コンソール出力が有効な場合、コンソールライターを追加
if cfg.Writer == config.WriteBoth || cfg.Writer == config.WriteConsole {
syncers = append(syncers, zapcore.AddSync(os.Stdout))
}
// ファイルログが有効な場合、ファイルパスに基づいてライターを追加
if cfg.Writer == config.WriteBoth || cfg.Writer == config.WriteFile {
// ログ出力を追加
for _, path := range cfg.LogFile.Output {
logger := &lumberjack.Logger{
Filename: path, // ファイルパス
MaxSize: cfg.LogFile.MaxSize, // 分割ファイルのサイズ
MaxBackups: cfg.LogFile.BackUps, // バックアップ数
Compress: cfg.LogFile.Compress, // 圧縮するかどうか
LocalTime: true, // 現地時間を使用
}
syncers = append(syncers, zapcore.Lock(zapcore.AddSync(logger)))
}
}
return zap.CombineWriteSyncers(syncers...)
}ログレベル
ログレベルには公式ドキュメントに列挙項目があります。直接使用してください。
func zapLevelEnabler(cfg *ZapConfig) zapcore.LevelEnabler {
switch cfg.Level {
case config.DebugLevel:
return zap.DebugLevel
case config.InfoLevel:
return zap.InfoLevel
case config.ErrorLevel:
return zap.ErrorLevel
case config.PanicLevel:
return zap.PanicLevel
case config.FatalLevel:
return zap.FatalLevel
}
// デフォルト Debug レベル
return zap.DebugLevel
}最終ビルド
func InitZap(config *ZapConfig) *zap.Logger {
// エンコーダーをビルド
encoder := zapEncoder(config)
// ログレベルをビルド
levelEnabler := zapLevelEnabler(config)
// 最後に Core と Options を取得
subCore, options := tee(config, encoder, levelEnabler)
// ロガーを作成
return zap.New(subCore, options...)
}
// すべてをマージ
func tee(cfg *ZapConfig, encoder zapcore.Encoder, levelEnabler zapcore.LevelEnabler) (core zapcore.Core, options []zap.Option) {
sink := zapWriteSyncer(cfg)
return zapcore.NewCore(encoder, sink, levelEnabler), buildOptions(cfg, levelEnabler)
}
// オプションをビルド
func buildOptions(cfg *ZapConfig, levelEnabler zapcore.LevelEnabler) (options []zap.Option) {
if cfg.Caller {
options = append(options, zap.AddCaller())
}
if cfg.StackTrace {
options = append(options, zap.AddStacktrace(levelEnabler))
}
return
}最終結果
ZapLogTest 2023/01/09 - 19:44:00.91076 INFO demo/zap.go:49 Logger initialization complete