Goの任意のLoggerをログローテート対応できるreplaceablewriter
https://github.com/Songmu/replaceablewriter
表題の通りですが、io.Writer
をラップして io.WriteCloser
として振る舞い、その内部に保持した io.Writer
を差し替え可能にするライブラリを書いた。
例えば、Goの標準logをログローテートしたい場合には以下のようにします。
f, _ := os.OpenFile("20191001.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
w := replaceablewriter.New(f)
log.SetOutput(w)
// 翌日になったら差し替える
f2, _ := os.OpenFile("20191002.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
w.Replace(f2) // 以降、20191002.logにログが書き込まれる
io.Writer
の差し替えはシグナル受けたときにやっても良いし、タイマーを回しても良いでしょう。ファイル名を同じにしたい場合は、事前にファイルを os.Rename
してからファイルを開き直して、Replace
を呼べばOKです。例えば以下のような形。
logname := "log.log"
f, _ := os.OpenFile(logname, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
w := replaceablewriter.New(f)
log.SetOutput(w)
// 翌日になったら差し替える
os.Rename(logname, "log-20191001.log")
f2, _ := os.OpenFile(logname, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
w.Replace(f2)
単なる io.WriteCloser
なので、io.Writer
を指定できるloggerであればなんでも対応可能です。ざっと書いたので、まだ適当クオリティですが、フィードバックあると嬉しいです。
作った動機とか
個人的に多機能なロガーが好きじゃない。ロガーは呼び出しも多く、IOを伴う処理でもあるため、地味にパフォーマンスのボトルネックになりやすい。なので、ロガーはシンプルにストリームに書くことに集中して欲しい。プラガブルな機能とかログローテート機能は別に必要ない。実際、多機能で知られるlogrusとかもパフォーマンスが悪いことで有名です。
ログローテートも、アプリケーション自体はシンプルに標準エラー出力や標準出力に吐いておいて、後続のlograotate, clonolog, multilog等に任せるのが好みです。kazuhoさんもこう言っています。
logrotate ファイルを rename してシグナル送ってサーバに仕事させるという負荷がスパイクする設計なのが嫌い。負荷をプロビジョンしてサービス設計する観点からいうと rotatelogs みたいなアプローチがいい
— Kazuho Oku (@kazuho) June 7, 2019
そもそもファイルに書くという発想が時代遅れになりつつあるわけです。それでもログローテートしたい場合、ファットなロガーがオールインワンでそれを提供するのではなくて、Goにはio.Writer
という素晴らしい抽象があるので、このレイヤーで書き出し先を差し替えれば良いんじゃないかと思ったのが、このreplaceablewriterを作った動機です。
ログローテートについて熱く語ってしまいましたが、別にログローテートに限らず、書き出し先を切り替えられるのは便利だと思うので、よろしければご活用ください。