💎
Golang on CloudRunでのCustom Logger実装
背景
Golang on CloudRun
で、builtinのlog
ライブラリをwrapしてcustom Logger
を実装してみた。
Loggingライブラリいくつか見てみたが、GCP面倒見いいしそれほどリッチな機能はいらないかなと思いつつ、最低限必要な機能をcustom logger
で実装してみた。
Loggingの目的
- アプリケーションのコードのバグを見つける
- サーバーのエラー前後の動作を確認する
- パフォーマンスの問題を見つける
Loggingで表示してはいけないもの
- 名前
- IPアドレス
- クレジットカード番号
実装
GolangのLogライブラリ
Golangでは、biltinでlog
ライブラリがある。
ログレベル(DEBUG, WARN, ERROR)は無いが、基本的なLogging機能は備えてる。デフォルトでは、logパッケージは標準出力(stderr)に出力される。
CloudRun
の場合、標準出力がそのままStackdriver
に送られる。なので、Stackdriverのloglevelに合わせたJsonフォーマットでLog.Print()
すれば、Stackdriver側でLogレベルを判別してくれる。
main.go
package main
import "log"
func main() {
log.Println("Hello world!")
}
Terminal
$ go run main.go
2021/19/09 17:21:53 Hello world!
Logger
interfaceで、使用したいログレベルのメソッドを定義する。
初期化時にLoggerを差し替えたりと、融通は効くかなと。
sdlogger/print.go
package sdlogger
import (
"encoding/json"
"fmt"
"log"
"os"
"time"
)
const (
DEBUG = "DEBUG"
INFO = "INFO"
WARN = "WARN"
ERROR = "ERROR"
)
type Logger interface {
Debugf(format string, v ...interface{})
Infof(format string, v ...interface{})
Warnf(format string, v ...interface{})
Errorf(format string, v ...interface{})
}
var logger Logger = &BasicLogger{
Logger: log.New(os.Stderr, "", log.LstdFlags),
}
func Initialize(l Logger) {
logger = l
}
type BasicLogger struct {
Logger *log.Logger
}
func stackdriverFmtJSON(level string, message string) string {
entry := map[string]string{
"severity": level,
"message": message,
"time": time.Now().Format(time.RFC3339Nano),
}
bytes, _ := json.Marshal(entry)
return string(bytes)
}
func localFmt(level string, message string) string {
return fmt.Sprintf("%s %s", level, message)
}
func (bl *BasicLogger) Printf(level string, format string, v ...interface{}) {
m := fmt.Sprintf(format, v...)
if IS_GCP {
bl.Logger.Println(stackdriverFmtJSON(level, m))
} else {
bl.Logger.Println(localFmt(level, m))
}
}
func (bl *BasicLogger) Debugf(format string, v ...interface{}) {
bl.Printf(DEBUG, format, v...)
}
func (bl *BasicLogger) Infof(format string, v ...interface{}) {
bl.Printf(INFO, format, v...)
}
func (bl *BasicLogger) Warnf(format string, v ...interface{}) {
bl.Printf(WARN, format, v...)
}
func (bl *BasicLogger) Errorf(format string, v ...interface{}) {
bl.Printf(ERROR, format, v...)
}
使うときは、mainなどの早いタイミングで初期化させる。
main.go
package main
func init() {
sdlogger.Initialize(...)
}
main() {
err := fmt.Errorf("error something.")
sdlogger.Warnf("waring something... %v", err)
}
まとめ
Golangのfmtではログレベルが存在しないのは、システムによって扱うべきログレベルは違うという考え方から来ているらしい。確かに、cli toolであれば、細かなロギングなんていらないけど、APIだと環境によって細かにロギングしたかったり。
今回は、自前でCustomLogger を実装してみたが、APIの要件によっては、別のlogライブラリ入れるのも手かなと。自前で実装すると、テスト書く手間もかかるし。
zap、zerolog辺りが気になるので触ってみたい。
参考
Discussion