はじめに
Goのログライブラリで人気なzap。
私も使っているのですが、インスタンスを引数で受け渡す必要があるのが少し面倒な気がします。
package main
import "go.uber.org/zap"
func main() {
logger, _ := zap.NewProduction()
defer logger.Sync()
sugar := logger.Sugar()
// 引数で渡す
hoge.Hoge(sugar)
}
package hoge
// ログ出力したい
func Hoge(s *zap.SugaredLogger) {
url := "http://example.com"
s.Infof("error: %s", url)
}
規模が小さい時はそこまで気になりませんでしたが、大きくなるにつれて徐々に気になるようになりました。
ネストされたところでログ出力したいがために、そこへ引数リレーしていくのがちょっと辛いです。
別の使い方がないかと思って調べてみると、global logger
を使うと解決できそうだったので、この記事で解説していきたいと思います。
解決策
2つありますが、私は前者推しです。
global loggerを使う
package main
import "go.uber.org/zap"
func main() {
logger, _ := zap.NewProduction()
defer logger.Sync()
// こいつがキモ
zap.ReplaceGlobals(logger)
hoge.Hoge()
}
package hoge
import "go.uber.org/zap"
// ログ出力したい
func Hoge() {
url := "http://example.com"
zap.S().Infof("error: %s", url)
}
引数渡しなしで、いい感じにログ出力を実装することができました!
zap.ReplaceGlobalsとは
公式より引用
https://pkg.go.dev/go.uber.org/zap#example-ReplaceGlobals
func ReplaceGlobals(logger *Logger) func()
ReplaceGlobals replaces the global Logger and SugaredLogger, and returns a function to restore the original values. It's safe for concurrent use.
zapにはL()
やS()
といったglobal logger
を返す関数が用意されており、ReplaceGlobals
でglobal logger
を登録する形になります。
以下のzapのソースコード読んだ方が話早いかもです。
https://github.com/uber-go/zap/blob/master/global.go
var (
_globalMu sync.RWMutex
_globalL = NewNop()
_globalS = _globalL.Sugar()
)
// L returns the global Logger, which can be reconfigured with ReplaceGlobals.
// It's safe for concurrent use.
func L() *Logger {
_globalMu.RLock()
l := _globalL
_globalMu.RUnlock()
return l
}
// S returns the global SugaredLogger, which can be reconfigured with
// ReplaceGlobals. It's safe for concurrent use.
func S() *SugaredLogger {
_globalMu.RLock()
s := _globalS
_globalMu.RUnlock()
return s
}
// ReplaceGlobals replaces the global Logger and SugaredLogger, and returns a
// function to restore the original values. It's safe for concurrent use.
func ReplaceGlobals(logger *Logger) func() {
_globalMu.Lock()
prev := _globalL
_globalL = logger
_globalS = logger.Sugar()
_globalMu.Unlock()
return func() { ReplaceGlobals(prev) }
}
パッケージスコープで変数宣言する
// zapをwrapしたようなパッケージ
package logger
import "go.uber.org/zap"
func initLogger() *zap.SugaredLogger {
logger, _ := zap.NewProduction()
defer logger.Sync()
sugar := logger.Sugar()
return sugar
}
var Sugar = initLogger()
package hoge
imoprt logger
// ログ出力したい
func hoge() {
url := "http://example.com"
logger.Sugar.Infof("error: %s", url)
}
上記のように、パッケージスコープにexportedな変数Sugar
を宣言して、それをよそのパッケージでも使うという手法です。
ただ個人的には、変数Sugar
がどこでも上書きできてしまうのであまり好みません。
const定義もできないですしね。
というかやっていること自体はReplaceGlobals
とほぼ一緒で、自前で用意するかパッケージで用意されているものを使うかの違いですね。
公式の推しは引数リレーっぽい
この問題にぶち当たって色々調べている時に、以下のissueがとても参考になりました。
contributerの方が以下のように発言されていました。
Zap prefers not to provide implicit default global behavior, favoring explicit
configuration in your main().
The default global logger used by zap.L() and zap.S() is a no-op logger.
To configure the global loggers, you must use ReplaceGlobals.
The approach we recommend is to not rely on global loggers at all. Instead,
build a logger in your main() and pass it down to the components that need
to perform logging operations explicitly.
we believe that it's cleaner software design to not rely on any
global state in our applications. We believe that the verbosity trade-off is
worth it. The trade-off of Zap may not be worth it for simple systems.
By "pass it down" I meant to instantiate the logger in your main(), not in
init(), and then passing it explicitly as an argument to all components that
need it, without ever using zap.ReplaceGlobals.
ソフトウェア設計の観点から、loggerを必要とする関数に明示的な引数として渡してあげた方が良い、といった趣旨のコメントです。
まあわかるけどちょっと規模大きくなるとめんどうだな。。というのが私見です。
もちろんPJの状況に合わせて選定していくのが一番なので、一概にどちらが良い悪いはないと思います。
さいごに
Twitterの方でも、モダンな技術習得やサービス開発の様子を発信したりしているので良かったらチェックしてみてください!
Goの人気ログライブラリzapに関する記事書いた!
— やぎぬ😇QOL追求エンジニア (@yagi_eng) May 9, 2021
⬇️【Go】ログライブラリzapを使う時はglobal loggerが便利!https://t.co/UGKbW9qZ7t
global loggerの日本語情報があまりなかったので書いてみた
zapの使い方って、READMEにはすごいシンプルにしか書いてないから参考になるかもしれない😇
また、BOT開発を通じてGoとLINE BOTにまとめて入門する記事をZennに掲載していますので、良かったらそちらもご覧ください!
ZennでGoとLINE BOTの記事を書いてみました
— やぎぬ😇行動力エンジニア (@yagi_eng) November 7, 2020
⬇️BOT開発を通じてGoとLINE BOTにまとめて入門するhttps://t.co/QqsEESJMKa
5ステップに分け、「Hello Worldから始めて、飲食店検索ができるLINE BOTの実装まで」を解説しています
GoやLINE BOTに興味のある人は是非読んでみてください😇
24,000字超え😇