Golang - logパッケージで出力を実行時に抑止する
tips Golang
Go の 標準 log
パッケージ からの出力を実行時に抑止する方法について
モチベーション
CLI ツール を作っている際に、 所謂 verbose モード を提供したいと思った。
OPTIONS:
-verbose
詳細メッセージを出力する
各種CLIツールでよくお目にかかるオプションかと思う。
各種ロギングライブラリは、大抵実行時に出力するログレベルを制御する機能が提供されているため、
main.go
だけで完結する様な 簡易なツールである場合は それらを導入してしまえば実現は簡単だ。
しかし、今回作ろうとしている ツール は基本的な処理はライブラリとして単独で提供し、
エントリーポイントとして main
パッケージを実装する構成にしていたため、既存のロギングライブラリ の採用はリスクが伴う。
既存のロギングライブラリを使うのは絶対に止めましょう。 あなたのライブラリの機能を使いたいだけなのに特定のロギングライブラリを強制されるのは、 ライブラリのユーザーにとっては苦痛でしかありません。 Golangでログを吐くコツ — KaoriYa
では、標準 log
パッケージ を使ってどの様に解決するか?
解決方法
main
パッケージにて log.SetOutput
に対して io/ioutil.Discard
を指定することで
log.Println
の出力を抑止することができる。Go Playground
package main
import (
"io/ioutil"
"log"
"os"
)
func main() {
log.SetOutput(ioutil.Discard)
log.Println("This will discard")
log.SetOutput(os.Stderr)
log.Println("Hello, World") // => 2009/11/10 23:00:00 Hello, World
}
main.init()
などで一度 log.SetOutput
を実行しておけば、別パッケージ にて log.Print
している箇所全ての出力を抑止することができて便利だ。コンソールアプリケーションなので、flag.BoolVar
との組み合わせで以下の様な実装を行えば、verbose mode が実現できる。Go Playground
package main
import (
"flag"
"fmt"
"io/ioutil"
"log"
"os"
)
var verbose bool
func init() {
flag.BoolVar(&verbose, "verbose", false, "Print a verbose message")
flag.Parse()
if verbose {
log.SetOutput(os.Stderr)
} else {
log.SetOutput(ioutil.Discard)
}
}
func main() {
// ... log.Print() を使った何か ...
}
こうしておけば、実行時に詳細ログだけ別ファイルにリダイレクトするなどもでき、利用しやすくなると思う。
$ ./hello Golang
Hello, Golang.
$ ./hello -verbose Golang
2019/11/19 15:56:05 Start main proc
2019/11/19 15:56:05 Number of Args: 1
2019/11/19 15:56:05 name: Golang
Hello, Golang.
2019/11/19 15:56:05 End main proc
$ ./hello -verbose Golang 2>./detail.log
Hello, Golang.
$ cat detail.log
2019/11/19 15:56:16 Start main proc
2019/11/19 15:56:16 Number of Args: 1
2019/11/19 15:56:16 name: Golang
2019/11/19 15:56:16 End main proc
解説
log.SetOutput
は、標準ロガーの出力先を引数で指定された io.Writer
に切り替える。
log.Println
は指定がなければ この.標準ロガー を使用する。
// https://golang.org/src/log/log.go 抜粋
var std = New(os.Stderr, "", LstdFlags)
// ...
// SetOutput sets the output destination for the standard logger.
func SetOutput(w io.Writer) {
std.mu.Lock()
defer std.mu.Unlock()
std.out = w
}
一方、 ioutil.Discard
は io.Writer
を実装した構造体で、
Write()
実行時に入力を捨てる用な実装がなされているため、 結果として log.Println()
の内部で書き出された内容は
捨てられる様になる。
// https://golang.org/src/io/ioutil/ioutil.go 抜粋
type devNull int
// devNull implements ReaderFrom as an optimization so io.Copy to
// ioutil.Discard can avoid doing unnecessary work.
var _ io.ReaderFrom = devNull(0)
func (devNull) Write(p []byte) (int, error) {
return len(p), nil
}
// Discard is an io.Writer on which all Write calls succeed
// without doing anything.
var Discard io.Writer = devNull(0)
参考
- log#SetOutput - godoc
- io/ioutil - godoc
- Golangでログを吐くコツ — KaoriYa
- Disable Log Output During Tests - Golang Code
comments powered by Disqus