🦔

Goでのロガーの取り回しについて

2023/09/06に公開

ちょうど技術記事を読んでいたときにこんな記事を見つけました。

この記事の中でいくつかロガーの取り回し方法が紹介されていました。それ以外にもこういう方法があるよっていうのが僕の中にあったのでそういった知見(?)を共有できたらなって思います。

ロガーに対して DI(?)を行う方法

Go は関数(他言語で言うところの静的なメソッド)を使うことが許された言語です。そのため、様々な場所から呼ばれる可能性のある機能かつ、その機能が保持するべき情報が無いとき。
言い換えると構造体である必要がない場合は、関数として定義したほうが取り回しが楽です。(たかがロガーのために DI ライブラリ使ったり自前で書くのはキツですし)

例えば以下のようなのプログラムがったとします。(ちなみに Go では src フォルダを作ることはあんまりないです)

root/
    ├ app/
    │   └ usecase.go … ユースケースなど
    ├ logger/
    │   └ logger.go … ログ関係の機能を提供
    └ main.go … エントリポイント
main.go
package main

import "logger_demo/app"

func main() {
	// any functions
	app.Something1()
	app.Something2()
}
app/usecase.go
package app

import "logger_demo/logger"

func Something1() {
	// Do something
	logger.Info("message")
}

func Something2() {
	// Do something
	logger.Error("message")
}
logger/logger.go
package logger

import "log/slog"

var (
	logger *slog.Logger = nil
)

func Info(msg string) {
	logger.Info(msg)
}

func Error(msg string) {
	logger.Error(msg)
}

// etc..

func init() {
	// Any process to initialize logger to use safe
	logger = new(slog.Logger)
}

このとき、main.go 内では app 内にある構造体などを生成し DI などを行い、実行します。このとき、logger の機能をメソッドして提供すると app を起動するときに logger の分の依存性を注入しないといけなくなります。
ですが、今回の実装のように logger としての機能を関数として提供すると、ここで logger を生成して渡す必要がなくなります。

ロガーの初期化方法

ロガーを関数として提供すれば、DI でロガーの依存性を注入する必要はありません。しかし、使用するロガーやカスタマイズしたロガーを使用したい場合などは、ロガーの初期化が必要になったり、ロガーのインスタンスを生成する必要が発生するかもしれません。
そういったときに役に立つのがinit関数です。init 関数は、引数・返り値なしの関数として定義することで、パッケージインポート時に自動でランタイムが実行してくれます。(実行順などは公式ドキュメントを参照してください)

ロガーの初期化やインスタンス化の必要がある場合ロガーの機能を提供するパッケージ内にinit関数を定義しその中でロガーのカスタマイズ(フォーマットなどの指定)を行い、パッケージ内の非公開変数にロガーインスタンスを保存することで main 関数の中からロガーを使用することができます。

まとめ

  • Logger などで、機能としてなにかデータを保持する必要がないなら関数として公開したほうが楽。
  • init 関数を活用することでロガーの設定などを行える。
    • ロガーをインスタンス化して使用する際には、パッケージ内の非公開変数として保存!

今回の説明に使ったコードはGitHubで公開してあります。

Discussion