/note/tech

logrusをラップした自作ロガー

現在のプロジェクトではロガーとしてlogrusを使用しているが、プロダクトコード内で頻繁にlogrusが呼び出されている。

現状logrusに不満があるわけではないが、将来ロガーを変更したくなった場合、logrusを使用している箇所を全て見つけ出して新しい実装に置き換えるのは面倒に感じる。

そこでlogrusをラップした自作ロガーを作り、プロダクトコードから直接logrusを触らないようにし、将来のロガー変更時は自作ロガーの内部だけを変更すればよいようにした。

以下コード。

package log

import (
    "encoding/json"
    "os"
    "runtime"

    "github.com/sirupsen/logrus"
)

type Logger interface {
    Debug(message string, fields *Fields)
    Info(message string, fields *Fields)
    Warn(message string, fields *Fields)
    Error(message string, fields *Fields)
    Fatal(message string, fields *Fields)
    Panic(message string, fields *Fields)
}

type Fields map[string]interface{}

type logger struct {
    logger *logrus.Logger
}

func NewLogger() Logger {
    log := logrus.New()
    log.Out = os.Stdout
    log.SetReportCaller(false)
    log.SetFormatter(&logrus.JSONFormatter{})
    return &logger{
        logger: log,
    }
}

func (l *logger) Info(message string, fields *Fields) {
    var f logrus.Fields
    data, _ := json.Marshal(fields)
    json.Unmarshal(data, &f)

    pt, file, line, _ := runtime.Caller(1)
    f["file"] = file
    f["line"] = line
    f["function"] = runtime.FuncForPC(pt).Name()

    l.logger.WithFields(f).Info(message)
}

func (l *logger) Debug(message string, fields *Fields) {
    // INFOと同じなので以下略
}

func (l *logger) Warn(message string, fields *Fields) {
    // ...
}

func (l *logger) Error(message string, fields *Fields) {
    // ...
}

func (l *logger) Fatal(message string, fields *Fields) {
    // ...
}

func (l *logger) Panic(message string, fields *Fields) {
    // ...
}

利用側はこんな感じ

logger = log.NewLogger()
logger.Info("message", &log.Fields{
    "key1": "aaaaaaaaaaa",
    "key2": "bbbbbbbbbbb",
})

こんな感じのログが出力される。

{"file":"/app/main.go","function":"main.main","key1":"aaaaaaaaaaa","key2":"bbbbbbbbbbb","level":"info","line":20,"msg":"message","time":"2022-04-23T05:06:46Z"}

所感

logrusのJSONログ出力機能が使いたかったので、自作ロガーのinterfaceにFieldsを含めてある。

Fieldsもlogrusのものを使わず、自作ロガーの一部として実装した。

Fieldsの存在が裏側にあるlogrusの存在を強く示唆しているが、ロガーを変更する際もJSON出力できるプロダクトを選択するのでどの道このようなinterfaceになるだろう。

懸念事項としては、Fieldsの型変換の為にjson.Marshal/Unmarshalを行っていることや、ファイルや行番号を取得する為にruntimeから情報を取り出している処理がログ出力の度に実行されるのでパフォーマンスにどの程度影響するのかというところである。