はじめに
今まで、文字列などを出力するときはlogかfmtを使って標準出力か標準エラー出力をしていたが、APIサーバを作るにあたり利用者の行動をファイルに記録する必要があると考えた。そこで、Goの標準パッケージであるlog/slogを用いて、json形式での構造化ロギングを行った。
実装
コード
このコードは、GoのWebフレームワークに一つであるginを用いた簡単なAPIサーバです。エンドポイントへのアクセスが行われたとき、その情報をlog/slogを用いてserver.logに出力します。
main.go
package main
import (
"github.com/gin-gonic/gin"
"io"
"log"
"log/slog"
"os"
"time"
)
func main() {
// ファイルを開く
file, err := os.OpenFile("server.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Println("Error")
return
}
defer file.Close()
fileWriter := io.Writer(file)
// loggerの生成
logger := slog.New(slog.NewJSONHandler(fileWriter, nil))
// 生成したloggerをデフォルトにする
slog.SetDefault(logger)
r := gin.Default()
// Ginのミドルウェアを設定
r.Use(ginLogFormat(logger))
r.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
r.Run()
}
// Ginのミドルウェア
func ginLogFormat(logger *slog.Logger) gin.HandlerFunc {
return gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
logger.Info("gin-request",
slog.String("time", param.TimeStamp.Format(time.RFC3339)),
slog.Int("status", param.StatusCode),
slog.String("latency", param.Latency.String()),
slog.String("client_ip", param.ClientIP),
slog.String("method", param.Method),
slog.String("path", param.Path),
slog.String("error", param.ErrorMessage),
)
return ""
})
}
実行結果
-
http://localhost:8080/ping
にアクセス -> pongが返される(200)
server.log
{"time":"2024-07-04T12:52:46.369314015+09:00","level":"INFO","msg":"gin-request","time":"2024-07-04T12:52:46+09:00","status":200,"latency":"23.841µs","client_ip":"::1","method":"GET","path":"/ping","error":""}
-
http://localhost:8080/hoge
にアクセス -> ページが存在しない(404)
server.log
{"time":"2024-07-04T12:58:19.784257142+09:00","level":"INFO","msg":"gin-request","time":"2024-07-04T12:58:19+09:00","status":404,"latency":"370ns","client_ip":"::1","method":"GET","path":"/hoge","error":""}
おわりに
サードパーティのパッケージに依存せず、標準パッケージ内の機能で構造化ロギングを行えるのはすごいと思った。このパッケージのことをもう少し深く掘り下げて、実運用で活かせるようなログ収集をしていきたい。
参考