LoginSignup
5
0

More than 3 years have passed since last update.

【Go】ログライブラリzapを使う時はglobal loggerが便利!

Last updated at Posted at 2021-05-09

はじめに

Goのログライブラリで人気なzap。
私も使っているのですが、インスタンスを引数で受け渡す必要があるのが少し面倒な気がします。

main.go
package main

import "go.uber.org/zap"

func main() {
    logger, _ := zap.NewProduction()
    defer logger.Sync()
    sugar := logger.Sugar()

    // 引数で渡す
    hoge.Hoge(sugar)
}
hoge.go
package hoge

// ログ出力したい
func Hoge(s *zap.SugaredLogger) {
    url := "http://example.com"
    s.Infof("error: %s", url)
}

規模が小さい時はそこまで気になりませんでしたが、大きくなるにつれて徐々に気になるようになりました。
ネストされたところでログ出力したいがために、そこへ引数リレーしていくのがちょっと辛いです。

別の使い方がないかと思って調べてみると、global loggerを使うと解決できそうだったので、この記事で解説していきたいと思います。

解決策

2つありますが、私は前者推しです。

global loggerを使う

main.go
package main

import "go.uber.org/zap"

func main() {
    logger, _ := zap.NewProduction()
    defer logger.Sync()
    // こいつがキモ
    zap.ReplaceGlobals(logger)

    hoge.Hoge()
}
hoge.go
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を返す関数が用意されており、ReplaceGlobalsglobal loggerを登録する形になります。

以下のzapのソースコード読んだ方が話早いかもです。
https://github.com/uber-go/zap/blob/master/global.go

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) }
}

パッケージスコープで変数宣言する

logger.go
// 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()
hoge.go
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の方でも、モダンな技術習得やサービス開発の様子を発信したりしているので良かったらチェックしてみてください!

また、BOT開発を通じてGoとLINE BOTにまとめて入門する記事をZennに掲載していますので、良かったらそちらもご覧ください!

参考

5
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0