こんにちは。Tomoyuki(@tomoyuki65)です。
Go言語(Golang)でログ出力をする際にはfmt.Printlnなどを使うことがありますが、Goのv1.21からは標準ライブラリとして“log/slog“が使えます。
この記事では、Go言語のログ出力について解説します。
Go言語(Golang)のログ出力ではslogが使える!コンテキストの引き回しも忘れずに。
例えばGinを使ってAPIを作る際などには、ミドルウェアを使って一意のIDをリクエストヘッダー「X-Request-Id」にセットすることがあると思います。
package main
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
func httpRequestMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
newUUID, err := uuid.NewRandom()
if err != nil {
panic(err)
}
c.Request.Header.Set("X-Request-Id", newUUID.String())
}
}
func main() {
router := gin.Default()
router.Use(httpRequestMiddleware())
router.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "Hello World !!",
})
})
router.Run(":8080")
}
加えて、APIの実行直後のコード部分ではパラメータにある*gin.Contextのcが使えるため、コンテキストから一意のIDを取り出してログ出力に加えることも可能です。
package main
import (
"net/http"
"log/slog"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
func httpRequestMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
newUUID, err := uuid.NewRandom()
if err != nil {
panic(err)
}
c.Request.Header.Set("X-Request-Id", newUUID.String())
}
}
func main() {
router := gin.Default()
router.Use(httpRequestMiddleware())
router.GET("/", func(c *gin.Context) {
requestId := c.Request.Header.Get("X-Request-Id")
slog.Info("ログ出力メッセージ1", "requestId", requestId)
c.JSON(http.StatusOK, gin.H{
"message": "Hello World !!",
})
})
router.Run(":8080")
}
ログ出力結果
go-log-api | 2024/12/11 13:40:34 INFO ログ出力メッセージ1 requestId=496f5424-ff26-4041-a469-d08ed184002e
ただし、以下のように途中で関数を使った場合は、そのままだとログに一意のIDを付与することができません。
package main
import (
"net/http"
"log/slog"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
func httpRequestMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
newUUID, err := uuid.NewRandom()
if err != nil {
panic(err)
}
c.Request.Header.Set("X-Request-Id", newUUID.String())
}
}
func action() {
slog.Info("ログ出力メッセージ2")
}
func main() {
router := gin.Default()
router.Use(httpRequestMiddleware())
router.GET("/", func(c *gin.Context) {
requestId := c.Request.Header.Get("X-Request-Id")
slog.Info("ログ出力メッセージ1", "requestId", requestId)
// 関数を実行
action()
c.JSON(http.StatusOK, gin.H{
"message": "Hello World !!",
})
})
router.Run(":8080")
}
ログ出力結果
go-log-api | 2024/12/11 13:46:10 INFO ログ出力メッセージ1 requestId=c0cde5f3-2925-467b-853d-949ab0722226
go-log-api | 2024/12/11 13:46:10 INFO ログ出力メッセージ2
このような場合、Goでは基本的には以下のようにコンテキストを関数に引き回すことになるため、面倒ですが忘れないように注意しましょう。
package main
import (
"net/http"
"log/slog"
"context"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
func httpRequestMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
newUUID, err := uuid.NewRandom()
if err != nil {
panic(err)
}
c.Request.Header.Set("X-Request-Id", newUUID.String())
}
}
func action(ctx context.Context) {
requestId := ctx.Value("requestId")
slog.Info("ログ出力メッセージ2", "requestId", requestId)
}
func main() {
router := gin.Default()
router.Use(httpRequestMiddleware())
router.GET("/", func(c *gin.Context) {
requestId := c.Request.Header.Get("X-Request-Id")
slog.Info("ログ出力メッセージ1", "requestId", requestId)
// コンテキストを引き回して関数を実行
ctx := context.WithValue(c.Request.Context(), "requestId", requestId)
action(ctx)
c.JSON(http.StatusOK, gin.H{
"message": "Hello World !!",
})
})
router.Run(":8080")
}
ログ出力結果
go-log-api | 2024/12/11 13:52:46 INFO ログ出力メッセージ1 requestId=204caad8-1aba-44e9-bc41-0798aec64edb
go-log-api | 2024/12/11 13:52:46 INFO ログ出力メッセージ2 requestId=204caad8-1aba-44e9-bc41-0798aec64edb
また、slogはカスタムロガーを作ることができるので、以下のようにコンテキストを渡して特定の項目をログに追加するような関数を作ることも可能です。
package main
import (
"net/http"
"log/slog"
"context"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
func httpRequestMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
newUUID, err := uuid.NewRandom()
if err != nil {
panic(err)
}
c.Request.Header.Set("X-Request-Id", newUUID.String())
}
}
func action(ctx context.Context) {
requestId := ctx.Value("requestId")
slog.Info("ログ出力メッセージ2", "requestId", requestId)
}
type SlogHandler struct {
slog.Handler
}
func (h *SlogHandler) Handle(ctx context.Context, r slog.Record) error {
requestId, ok := ctx.Value("requestId").(string)
if ok {
r.AddAttrs(slog.Attr{Key: "requestId", Value: slog.String("requestId", requestId).Value})
}
return h.Handler.Handle(ctx, r)
}
var slogHandler = &SlogHandler{
slog.NewTextHandler(os.Stdout, nil),
}
var slogLogger = slog.New(slogHandler)
func InfoWithContext(ctx context.Context, message string) {
slogLogger.InfoContext(ctx, message)
}
func main() {
router := gin.Default()
router.Use(httpRequestMiddleware())
router.GET("/", func(c *gin.Context) {
requestId := c.Request.Header.Get("X-Request-Id")
slog.Info("ログ出力メッセージ1", "requestId", requestId)
// コンテキストを引き回して関数を実行
ctx := context.WithValue(c.Request.Context(), "requestId", requestId)
action(ctx)
// slogのカスタムロガーでログ出力
InfoWithContext(ctx, "ログ出力メッセージ3")
c.JSON(http.StatusOK, gin.H{
"message": "Hello World !!",
})
})
router.Run(":8080")
}
ログ出力結果
go-log-api | 2024/12/11 14:23:04 INFO ログ出力メッセージ1 requestId=c281372d-ef7d-4061-a770-3daa56285d9a
go-log-api | 2024/12/11 14:23:04 INFO ログ出力メッセージ2 requestId=c281372d-ef7d-4061-a770-3daa56285d9a
go-log-api | time=2024-12-11T14:23:04.771Z level=INFO msg=ログ出力メッセージ3 requestId=c281372d-ef7d-4061-a770-3daa56285d9a
※カスタムロガーにすると出力形式が変わります。上記はText形式ですが、JSON形式にすることも可能です。
尚、上記では「slog.Info」や「slog.InfoContext」を使いましたが、ログレベルを変えたい場合は「slog.Warn」、「slog.WarnContext」、「slog.Error」、「slog.ErrorContext」なども使えます。
最後に
今回はログ出力用のslogや注意点についてご紹介しました。
Go言語ではログ出力に一意のIDをつけたい場合はコンテキストの引き回しが必要になったりするため、各種関数を作る際は必ず「context.Context」型の引数を付けておいた方がいいでしょう。
コメント