こんにちは。KOUKIです。
とある企業でGo言語を使ったアプリ開発をしています。
最近は、goroutineを使った並行処理プログラミングの勉強をしていて、本記事では、それを組み合わせてロギングを作りました。
<目次>
ワークスペースの作成
最初にワークスペースを作成してください。
1 2 3 4 |
mkdir logger touch logger/main.go mkdir logger/logger touch logger/logger/logger.go |
ログ出力をgoroutine化する意義
ログ出力と書いてますが、標準出力ととらえていただいても問題ないです。
go言語では、fmt/logパッケージにて、情報を出力する関数を利用することができます。
1 2 3 4 5 6 7 8 9 10 11 |
package main import ( "fmt" "log" ) func main() { log.Print("Hello") fmt.Println("World") } |
結果は、以下の通りです。
1 2 3 |
$ go run main.go 2021/01/12 7:56:02 Hello World |
これらの関数は、ログやデバック、エラーメッセージなどに利用されますよね。
普段何気なく使っていたのですが、もし仮に「大容量のデータを出力する」ようなことがあった場合、どうなるのでしょうか。
おそらく、一旦処理が停止すると思うんですよね。例えば、こんな感じに。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package main import ( "fmt" "log" "strings" "time" ) func makeString() { s := strings.Repeat("test string", 100000) log.Println(s) } func main() { start := time.Now() makeString() end := time.Now() fmt.Println(end.Sub(start)) } |
上記のコードは、”test string”を100000回結合したものをlog.Printlnでログ出力しました。このコードを実行すると次の結果になります。
1 2 3 4 |
go run main.go .... .... 1.653652417s |
処理の完了までに「1.653652417s」もかかっています。
仮に後続処理があった場合、その時間分だけ遅れが生じます。
Go言語は高速処理が自慢の言語であるため、goroutineを使って改善したいなぁと思った次第です。
実装
ロガーの作成
goroutineを活用するロガーを独自に作りましょう。作成手順は、こんな感じです。
2. goroutine + for + selectを組み合わせたサブプロセス処理を作成
3. チャネルからメッセージを送信する関数を作成
実装してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
// logger.go package logger import ( "fmt" "io" "log" ) var ( l *log.Logger ) // メッセージチャネル // 名前はなんでも良い(fatalとかnormalとかメッセージの要件に合わせておくと良いかも) var debugChan chan string var infoChan chan string var warnChan chan string var errChan chan string var criticalChan chan string // ロガーをセットアップ func Setup(out io.Writer) { // capacity(100)を決めた方が処理速度が上がるので設定 debugChan = make(chan string, 100) infoChan = make(chan string, 100) warnChan = make(chan string, 100) errChan = make(chan string, 100) criticalChan = make(chan string, 100) // プレフィックスを決められる // https://golang.org/pkg/log/#pkg-constants l = log.New(out, "", log.Ldate|log.Ltime) // golang + for + selectのコンボ(パターンで覚えよう) go func() { for { select { case debug := <-debugChan: l.Print(debug) case info := <-infoChan: l.Print(info) case warn := <-warnChan: l.Print(warn) case err := <-errChan: l.Print(err) case critical := <-criticalChan: l.Fatal(critical) } } }() } // 出力用のログ関数のサンプル func Debug(msg ...interface{}) { debugChan <- fmt.Sprintf("Debug: %s", msg...) } func Info(msg ...interface{}) { infoChan <- fmt.Sprintf("Info: %s", msg...) } func Warning(msg ...interface{}) { warnChan <- fmt.Sprintf("Warning: %s", msg...) } func Err(msg ...interface{}) { errChan <- fmt.Sprintf("Err: %s", msg...) } func Critical(msg ...interface{}) { criticalChan <- fmt.Sprintf("Critical: %s", msg...) } |
上記のような「goroutine + for + select」はパターンとしてよく利用されますね。覚えておくと便利です。
main関数の実装
先ほど定義したloggerを利用してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
package main import ( "fmt" "interface/logger/logger" "os" "strings" "time" ) func makeString() { // テストのためリピート10にする s := strings.Repeat("test string", 10) logger.Info(s) } func main() { // ロガーをセットアップ logger.Setup(os.Stdout) start := time.Now() makeString() end := time.Now() fmt.Println(end.Sub(start)) // サブプロセスの完了を待つ time.Sleep(2 * time.Second) } |
ロガーを使うには、「logger.Setup(os.Stdout)」で初期化します。引数にはlogger.goに設定した
を生成するため、os.Stdoutを渡しています。l *log.Logger
そして、今回はログ出力が「別プロセスで動くこと」を確認できればいいので、time.Sleepを入れて別プロセスが完了するのを待っています。
プログラムを実行してみましょう。
1 2 3 |
$ go run main.go 29.22µs 2021/01/12 8:37:45 Info: test stringtest stringtest stringtest stringtest stringtest stringtest stringtest stringtest stringtest string |
にて、プレフィックス(Ldate/Ltime)を設定しているので、「2021/01/12 8:37:45」のように日時が出力されています。その後、出力レベルに従った文字列(Info:など)とメッセージ内容も出力されているので、これで概ねOKでしょう。log.New(out, "", log.Ldate|log.Ltime)
ロギングとしてはパワー不足かもしれませんが、必要な機能は随時追加していただければ幸いです^^
おわりに
昔記事にした「Pythonでロギングを学ぼう」を久しぶりに読み返して、Go言語でもやってみようと思い、この記事を書きました。
昔はPythonの方が好きでしたが、今ではGo言語の方が好きになりつつあります。
日本でもっとGo言語が流行ればいいのにと思います。
それでは、また!
コメントを残す
コメントを投稿するにはログインしてください。