【Go】ロギングライブラリ zap の概要を掴む
この記事について
この記事はCyberAgent AI tech studio | Go Advent Calendar 2022 5日目の記事です。
対象読者
- ✅ zapとは何か概要をざっくりと把握したい方
- ✅ 普段goを使用して開発されている方
はじめに
業務で Go のロギングライブラリであるzapを使用する機会があったため、理解の促進の意味も込めて reference 等調べたことを記載します。
zapとは
⚡️ Blazing fast, structured, leveled logging in Go.
zap
は Go での高速な構造化されたレベル別のロギングです。
github の README や referenceは以下です。
概要を掴む
以下では reference を読んで知ったことを記載します。
Logger の種類
Choosing a Logger
Choosing a Logger
とあるように zapには2種類の Logger があります。
SugaredLogger
In contexts where performance is nice, but not critical, use the SugaredLogger.
パフォーマンスが重要でない場合は SugaredLogger
を使用するのが良いようです。他のロギングパッケージよりも4~10倍速く構造化ロギングとprintfスタイルのロギングの両方をサポートしてくれます。
Logger
In the rare contexts where every microsecond and every allocation matter, use the Logger.
全てのマイクロ秒、アロケーションが重要な場合は Logger
を使用するのが良いようです。 SugardedLogger よりもさらに速く、アロケーションも少ないですが、強く型付けされた構造化ロギングのみをサポートしてくれます。
Choosing between the Logger and SugaredLogger doesn't need to be an application-wide decision: converting between the two is simple and inexpensive.
上記のように Logger
と SugaredLogger
の変換は容易であるため、アプリケーション全体でどちらを使用するかを決める必要はないようです。
zapcoreとは
業務での実装を追うにあたり、zapcore
packageを使用している部分も見られたため、調べてみます。zap
の reference には以下のようにありました。
More unusual configurations (splitting output between files, sending logs to a message queue, etc.) are possible, but require direct use of go.uber.org/zap/zapcore.
より特殊な設定を可能にするべく zapcore
を直接使用する必要があるようです。
The zap package itself is a relatively thin wrapper around the interfaces in go.uber.org/zap/zapcore.
とあるように、zap
自体は zapcore
の薄いラッパーのようです。 zapcore.Encoder
, zapcore.WriteSyncer
, zapcore.Core
インターフェースの実装を行えば独自のロガーの構築もできるようです。
Levelについて
A Level is a logging priority. Higher levels are more important.
zapcore package には7種類のロギングレベルがあり iota で管理されています。
const (
// DebugLevel logs are typically voluminous, and are usually disabled in
// production.
DebugLevel Level = iota - 1
// InfoLevel is the default logging priority.
InfoLevel
// WarnLevel logs are more important than Info, but don't need individual
// human review.
WarnLevel
// ErrorLevel logs are high-priority. If an application is running smoothly,
// it shouldn't generate any error-level logs.
ErrorLevel
// DPanicLevel logs are particularly important errors. In development the
// logger panics after writing the message.
DPanicLevel
// PanicLevel logs a message, then panics.
PanicLevel
// FatalLevel logs a message, then calls os.Exit(1).
FatalLevel
_minLevel = DebugLevel
_maxLevel = FatalLevel
)
レベル別のメソッド
logger にはレベル別のメソッドが用意されており、それぞれメッセージを記録します。メッセージには渡されたフィールドがログに蓄積されたフィールドと同じように渡されます。
例えば Info レベルの実装は以下です。
// Info logs a message at InfoLevel. The message includes any fields passed
// at the log site, as well as any fields accumulated on the logger.
func (log *Logger) Info(msg string, fields ...Field) {
if ce := log.check(InfoLevel, msg); ce != nil {
ce.Write(fields...)
}
}
zap.Fieldについて
レベル別に用意されたメソッドの引数には zap.Field を渡していました。
Field is an alias for Field.
zap.Field は zapcore.Field の alias です。
type Field = zapcore.Field
A Field is a marshaling operation used to add a key-value pair to a logger's context. Most fields are lazily marshaled, so it's inexpensive to add fields to disabled debug-level log statements.
Field は logger のコンテキストに key-value ペアを追加するために使用されるマーシャリングの操作に使用されるようです。
type Field struct {
Key string
Type FieldType
Integer int64
String string
Interface interface{}
}
reference には以下のような例があり、zap.String, zap.Int, zap.Duration はそれぞれ key と value を受け取って Field を返します。
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("failed to fetch URL",
// Structured context as strongly typed Field values.
zap.String("url", url),
zap.Int("attempt", 3),
zap.Duration("backoff", time.Second),
)
例えば String の実装を見てみると Field に Key, Type, String を充てて構造体を初期化していることが分かります。
func String(key string, val string) Field {
return Field{Key: key, Type: zapcore.StringType, String: val}
}
zap.Errorについて
error を受け取って Field を返す Error 関数もあります。
Error is shorthand for the common idiom NamedError("error", err).
Error は NamedError の省略記法のようです。
func Error(err error) Field {
return NamedError("error", err)
}
NamedError constructs a field that lazily stores err.Error() under the provided key. Errors which also implement fmt.Formatter (like those produced by github.com/pkg/errors) will also have their verbose representation stored under key+"Verbose". If passed a nil error, the field is a no-op.
For the common case in which the key is simply "error", the Error function is shorter and less repetitive.
NamedError は 与えられた key に基づいて err.Error() を格納するフィールドを構築します。
先の Field の Interface 部分に error を充てることで柔軟に value を格納することができます。
func NamedError(key string, err error) Field {
if err == nil {
return Skip()
}
return Field{Key: key, Type: zapcore.ErrorType, Interface: err}
}
zap.Syncについて
Sync calls the underlying Core's Sync method, flushing any buffered log entries. Applications should take care to call Sync before exiting.
Sync は基礎となる Core の Sync メソッドを呼び、バッファリングされたログのエントリを全てフラッシュします(蓄積されたログを全て吐き出します)。
アプリケーションが終了する前に Sync を呼び出す必要があるようです。
func (log *Logger) Sync() error {
return log.core.Sync()
}
簡単に実装してみる
最後に簡単な実装を行ってみました。
コードは以下です。
Input を想定した User 構造体に対して、バリデーションを施し、エラーを起こします。
Info レベルのログとして起こった err を表示してくれます。
package main
import (
"github.com/go-playground/validator/v10"
"go.uber.org/zap"
)
type User struct {
Name string `validate:"required"`
Password string `validate:"required,min=8,max=20"`
}
func main() {
logger := zap.NewExample()
defer logger.Sync()
body := &User{
Name: "Taro",
Password: "Pass",
}
validate := validator.New() //インスタンス生成
err := validate.Struct(body)
logger.Info("validation failed",
zap.String("name", body.Name),
zap.String("password", body.Password),
zap.Error(err),
)
}
結果として得られるログは以下です。
{
"level":"info",
"msg":"validation failed",
"name":"Taro",
"password":"Pass",
"error":"Key: 'User.Password' Error:Field validation for 'Password' failed on the 'min' tag"
}
終わりに
今回は zap について簡単に reference を読んで概要を掴み、簡易的な実装を行いました。
今後は sugaredLogger, Loggerの違いや変換について
, 独自のロガーの構築
等、更に学んでいければと思います。
誤り等ございましたら、自分の成長のために御指南いただけると幸いです。
ご精読ありがとうございました。
Discussion