· 3分で読了

Golang アプリケーションでログを収集・集約する方法

この記事は中国語から自動翻訳されたものです。翻訳によりニュアンスが失われている場合があります。

この記事は、How to collect, standardize, and centralize Golang logs | Datadog を読んだまとめだ。

ログを扱う際、通常いくつかの注意点(アプローチ)がある:

  1. ログを引数として渡し、ログが必要になったときに引数経由で渡す
  2. context に一元的にカプセル化し、必要なときに context から取り出す
  3. ロガーをパッケージとしてラップし、他のパッケージが使えるように変数を公開する

明らかに、最初のアプローチはすぐに問題に直面する。それは引数がかなり不格好になることだ。例えば:

func (p *Post) CreatePost(name string, content string, logger *log.Logger) {
  ///
  if err != nil {
    logger.Fatal("error!")
  }
}

CreatePost のロジックにわざわざロガーを渡さなければならず、引数が汚くなりやすいし、階層を越えてロガーをバケツリレーする状況も生じやすい。このアプローチの最大のメリットは依存関係が透明なことで、関数シグネチャを見れば内部でロガーが使われることが明確にわかり、テスト時にもロガーを簡単にモックできる点だ。

僕たちは2番目のアプローチを採用することもできる。main の中で明示的に context を渡し、その中にロガーを詰めておくのだ。必要になったら log.Logger.FromContext(ctx) のように取り出す。今のところ特に目立ったデメリットは思い浮かばない。

const (
  ContextKeyLogger = "logger"
)
func main() {

  ctx := context.Background()
  loggerCtx := context.WithValue(ctx, ContextKeyLogger, log.Logger)
  
}

3番目のアプローチは、僕が現在採用している方法だ。ロガーをパッケージにラップし、他のパッケージ向けに変数を公開するというものだ。このアプローチの最大のメリットは手軽さで、プラグアンドプレイで使える点にある。デメリットは、テストを書く際があまり容易ではないことだ。引数としてパラメータ化されていないため、ロガーをモック化するのが難しい。(もしかしたら他にも良い解決策があるかもしれないが。)

package logging

var Logger *log.Logger

func init() {
  Logger = &log.Logger{
    // your setting
  }
}

続いて、元記事で挙げられている推奨事項を見ていこう:

  1. logrus の使用を推奨する。logrus は非常に使い勝手の良いロギングパッケージであり、標準の log も完全にサポートしているため、痛みなく移行できる。hook の仕組みを利用すれば、Datadog や Sentry などのサードパーティ製レポーティングプラットフォームとの連携も容易だ。
  2. ログファイルのフォーマットに JSON を使用する:JSON は非常にパースしやすく、主要なプログラミング言語がすべてサポートしており、サードパーティのプラットフォームにとっても分析しやすい。非常におすすめだ。logrus では直接 SetFormatter(&logrus.JSONFormatter{}) で出力を設定できる。
  3. 統一されたインターフェースを使用する。
  4. goroutine の中ではできるだけロガーを呼び出さないようにする。並行処理(concurrency)の問題を考慮しなければならないだけでなく、ロガーの内部実装でも goroutine が動いている可能性があるため、全体の挙動を把握しづらくなるからだ。
  5. 外部プラットフォームを使用している場合でも、ログをローカルファイルとして保存しておく。こうすることでネットワーク障害によるログの消失を防ぎ、いつでもログファイルを確認できるように担保できる。
  6. クラスターのようなアーキテクチャを採用している場合、syslog などの方式を用いて専用のログサーバーに一元集約する必要があるかもしれない。
  7. Docker などのコンテナサービスを利用している場合は、Docker の STDOUT を監視・収集する必要があるかもしれない。

その他

log.Fatal() と log.Panic() についてだが、Fatal は裏でこっそり os.Exit(1) を呼び出す。ドキュメントに書いてあるのは知っているけれど、見ていない人も多いかもしれない。一方 Panic は裏で panic を呼び出すので、使用する際は十分に注意が必要だ。

関連記事

他のトピックを探索