Writers パッケージを作ってみた
Twitter で
というのを見かけたので,試しに作ってみた。
いや tee
および grep
コマンドを組み合わせれば出力の分割はできるんだけどね1。
まぁ,言語的に面白いトピックはないし,手遊びということで。
たとえば,拙作の logf
パッケージを使ってこんなログ出力を考えてみる。
package main
import (
"os"
"github.com/spiegel-im-spiegel/logf"
)
func main() {
logf.SetOutput(os.Stdout)
for i := 0; i < 6; i++ {
logf.SetMinLevel(logf.TRACE + logf.Level(i))
logf.Tracef("Traceing: No. %d\n", i+1)
logf.Debugf("Debugging: No. %d\n", i+1)
logf.Printf("Information: No. %d\n", i+1)
logf.Warnf("Warning: No. %d\n", i+1)
logf.Errorf("Erroring: No. %d\n", i+1)
logf.Fatalf("Fatal Erroring: No. %d\n", i+1)
}
}
これを実行すると,こんな感じになる。
$ go run sample.go
2020/03/28 14:44:44 [TRACE] Traceing: No. 1
2020/03/28 14:44:44 [DEBUG] Debugging: No. 1
2020/03/28 14:44:44 [INFO] Information: No. 1
2020/03/28 14:44:44 [WARN] Warning: No. 1
2020/03/28 14:44:44 [ERROR] Erroring: No. 1
2020/03/28 14:44:44 [FATAL] Fatal Erroring: No. 1
2020/03/28 14:44:44 [DEBUG] Debugging: No. 2
2020/03/28 14:44:44 [INFO] Information: No. 2
2020/03/28 14:44:44 [WARN] Warning: No. 2
2020/03/28 14:44:44 [ERROR] Erroring: No. 2
2020/03/28 14:44:44 [FATAL] Fatal Erroring: No. 2
2020/03/28 14:44:44 [INFO] Information: No. 3
2020/03/28 14:44:44 [WARN] Warning: No. 3
2020/03/28 14:44:44 [ERROR] Erroring: No. 3
2020/03/28 14:44:44 [FATAL] Fatal Erroring: No. 3
2020/03/28 14:44:44 [WARN] Warning: No. 4
2020/03/28 14:44:44 [ERROR] Erroring: No. 4
2020/03/28 14:44:44 [FATAL] Fatal Erroring: No. 4
2020/03/28 14:44:44 [ERROR] Erroring: No. 5
2020/03/28 14:44:44 [FATAL] Fatal Erroring: No. 5
2020/03/28 14:44:44 [FATAL] Fatal Erroring: No. 6
これを出発点とする。
出力を多重化するには io
.MultiWriter()
関数を使うとよい。
こんな感じ。
package main
import (
"fmt"
"io"
"os"
"github.com/spiegel-im-spiegel/logf"
)
func main() {
file, err := os.Create("log.txt")
if err != nil {
fmt.Printf("%#v\n", err)
return
}
defer file.Close()
ws := io.MultiWriter(
file,
os.Stdout,
)
logf.SetOutput(ws)
for i := 0; i < 6; i++ {
logf.SetMinLevel(logf.TRACE + logf.Level(i))
logf.Tracef("Traceing: No. %d\n", i+1)
logf.Debugf("Debugging: No. %d\n", i+1)
logf.Printf("Information: No. %d\n", i+1)
logf.Warnf("Warning: No. %d\n", i+1)
logf.Errorf("Erroring: No. %d\n", i+1)
logf.Fatalf("Fatal Erroring: No. %d\n", i+1)
}
}
これで標準出力と log.txt
ファイルに全く同じ内容が出力される。
次に,標準出力には [ERROR]
と [FATAL]
のログのみ出力したい。
そこでこんな型を考える。
package writers
//FilterWriter type is Writer with filter
type FilterWriter struct {
word []byte
writer io.Writer
}
この型に対して以下の Write()
メソッド
//Write function writes bytes data.
func (w *FilterWriter) Write(b []byte) (int, error) {
if w.match(b) {
return w.writer.Write(b)
}
return len(b), nil
}
func (w *FilterWriter) match(b []byte) bool {
if len(b) == 0 {
return false
}
if w.word == nil {
return true
}
return bytes.Contains(b, w.word)
}
を組み込めば,設定したキーワードを含んでいる場合のみ書き込みを行うようになる。
writers
.FilterWriter
を使って先程のコードを書き換えてみよう。
こんな感じ。
package main
import (
"fmt"
"io"
"os"
"github.com/spiegel-im-spiegel/logf"
"github.com/spiegel-im-spiegel/writers"
)
func main() {
file, err := os.Create("log.txt")
if err != nil {
fmt.Printf("%#v\n", err)
return
}
defer file.Close()
ws := io.MultiWriter(
file,
writers.Filter(os.Stdout, []byte("[ERROR]")),
writers.Filter(os.Stdout, []byte("[FATAL]")),
)
logf.SetOutput(ws)
for i := 0; i < 6; i++ {
logf.SetMinLevel(logf.TRACE + logf.Level(i))
logf.Tracef("Traceing: No. %d\n", i+1)
logf.Debugf("Debugging: No. %d\n", i+1)
logf.Printf("Information: No. %d\n", i+1)
logf.Warnf("Warning: No. %d\n", i+1)
logf.Errorf("Erroring: No. %d\n", i+1)
logf.Fatalf("Fatal Erroring: No. %d\n", i+1)
}
}
これで標準出力が
$ go run sample.go
2020/03/28 14:44:44 [ERROR] Erroring: No. 1
2020/03/28 14:44:44 [FATAL] Fatal Erroring: No. 1
2020/03/28 14:44:44 [ERROR] Erroring: No. 2
2020/03/28 14:44:44 [FATAL] Fatal Erroring: No. 2
2020/03/28 14:44:44 [ERROR] Erroring: No. 3
2020/03/28 14:44:44 [FATAL] Fatal Erroring: No. 3
2020/03/28 14:44:44 [ERROR] Erroring: No. 4
2020/03/28 14:44:44 [FATAL] Fatal Erroring: No. 4
2020/03/28 14:44:44 [ERROR] Erroring: No. 5
2020/03/28 14:44:44 [FATAL] Fatal Erroring: No. 5
2020/03/28 14:44:44 [FATAL] Fatal Erroring: No. 6
となった。
単純な比較のみだと複雑なパターンを構成し辛いので,正規表現バージョンも作ってみた。
//RegexpWriter type is Writer with regular expression filter
type RegexpWriter struct {
re *regexp.Regexp
writer io.Writer
}
//WriteString function writes string.
func (w *RegexpWriter) Write(b []byte) (int, error) {
if w.match(b) {
return w.writer.Write(b)
}
return len(b), nil
}
func (w *RegexpWriter) match(b []byte) bool {
if len(b) == 0 {
return false
}
if w.re == nil {
return true
}
return w.re.Match(b)
}
これを使えば,先程のコードはこんな感じにできる。
package main
import (
"fmt"
"io"
"os"
"regexp"
"github.com/spiegel-im-spiegel/logf"
"github.com/spiegel-im-spiegel/writers"
)
func main() {
file, err := os.Create("log.txt")
if err != nil {
fmt.Printf("%#v\n", err)
return
}
defer file.Close()
ws := io.MultiWriter(
file,
writers.FilterRegexp(os.Stdout, regexp.MustCompile(`\[(ERROR|FATAL)\]`)),
)
logf.SetOutput(ws)
for i := 0; i < 6; i++ {
logf.SetMinLevel(logf.TRACE + logf.Level(i))
logf.Tracef("Traceing: No. %d\n", i+1)
logf.Debugf("Debugging: No. %d\n", i+1)
logf.Printf("Information: No. %d\n", i+1)
logf.Warnf("Warning: No. %d\n", i+1)
logf.Errorf("Erroring: No. %d\n", i+1)
logf.Fatalf("Fatal Erroring: No. %d\n", i+1)
}
}
これで同じ結果が得られる。
今回はベースの Writer
に出力多重化やらフィルタやらの機能を被せているだけなので,色々と応用が効くだろう。
効くといいな(笑)
ブックマーク
参考図書
- プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
- Alan A.A. Donovan (著), Brian W. Kernighan (著), 柴田 芳樹 (翻訳)
- 丸善出版 2016-06-20
- 単行本(ソフトカバー)
- 4621300253 (ASIN), 9784621300251 (EAN), 4621300253 (ISBN), 9784621300251 (ISBN)
- 評価
著者のひとりは(あの「バイブル」とも呼ばれる)通称 “K&R” の K のほうである。この本は Go 言語の教科書と言ってもいいだろう。