📌

Go - Echoでzapを利用してフォーマットされたログを出力

2025/02/17に公開

はじめに

echo.Context でcontextでデータを伝搬させる方法をご紹介しました。
https://zenn.dev/osachi/articles/9c736f47662e93

echo.Contextの仕組みを利用して、zapでフォーマット化されたログを出力する方法を紹介します。

Echoでのログ出力

echo.Conetxt組み込みのloggerを利用

非常にシンプルなログ出力の方法です。

func TestMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		c.Logger().Info("middleware TestMiddleware")
		c.Logger().Error("middleware TestMiddleware ")
		return next(c)
	}
}
	e := echo.New()
	e.Logger.SetLevel(log.INFO)
	e.Use(middleware.TestMiddleware)

下記のようなログが出力されます。

go_air | {"time":"2024-12-17T13:17:50.353768922Z","level":"INFO","prefix":"echo","file":"logger.go","line":"58","message":"middleware TestMiddleware"}
go_air | {"time":"2024-12-17T13:17:50.354068922Z","level":"ERROR","prefix":"echo","file":"logger.go","line":"59","message":"middleware TestMiddleware "}

echoのmiddleware.loggerを利用

echoのmiddleware.logger()を利用します。
https://echo.labstack.com/docs/middleware/logger

	e := echo.New()
	e.Use(middleware.Logger())

全リクエストの前後(自動でログを記録)でログを出力します。
下記のようなHTTP リクエストのログを記録してくれます。

{"time":"2017-01-12T08:58:07.372015644-08:00","remote_ip":"::1","host":"localhost:1323","method":"GET","uri":"/","status":200,"error":"","latency":14743,"latency_human":"14.743µs","bytes_in":0,"bytes_out":2}

middleware.loggerでformatを利用

configで設定を変更出来ます。

	e := echo.New()
	e.Use(middleware.LoggerWithConfig(echomiddleware.LoggerConfig{
		Format: "method=${method}, uri=${uri}, status=${status}\n",
	}))

formatを変更することができます。下記の形式でログ出力されます。

method=GET, uri=/, status=200

zapとは

uberが開発したパッケージ。
カスタマイズ性と処理速度に優れた、ロギングパッケージです。

zapを利用してログ出力


package middleware

import (
	"layered/logger"
	"go.uber.org/zap"
)


func TestMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {

		zapLogger, _ := zap.NewProduction()
		// シンプルなログ出力
		zapLogger.Info("This is an info message")
		zapLogger.Error("This is an error message")
		return next(c)
	}
}

	e := echo.New()
	e.Use(middleware.TestMiddleware)

フォーマット化されたloggerでログを統一 / contextにloggerを渡して任意なタイミングでログを出力

loggerの設定

contextに対して、zapで作成したloggerを渡します。
context経由でloggerを取り出せるようにします。

package logger

import (
	"context"
	"go.uber.org/zap"
)

// Context のキー
type contextKey struct{}

// グローバルロガー
var TestLogger *zap.Logger

// ロガーを初期化
func InitLogger() {
	cfg := zap.NewProductionConfig()
	cfg.Encoding = "json" // JSON フォーマット
	cfg.Level = zap.NewAtomicLevelAt(zap.InfoLevel)
	cfg.OutputPaths = []string{"stdout"}

	var err error
	TestLogger, err = cfg.Build()
	if err != nil {
		panic(err)
	}
}

// Context にロガーをセットする
func SetLoggerToContext(ctx context.Context, logger *zap.Logger) context.Context {
	var lKey ContextLoggerKey
	return context.WithValue(ctx, lKey, logger)
}

// Context からロガーを取得する
func GetLoggerFromContext(ctx context.Context) *zap.Logger {
	var lKey ContextLoggerKey
	if logger, ok := ctx.Value(lKey).(*zap.Logger); ok {
		return logger
	}
	return TestLogger // デフォルトのロガーを返す
}

middleware

func TestMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		req := c.Request()
		res := c.Response()

		id := req.Header.Get(echo.HeaderXRequestID)
		if id == "" {
			id = res.Header().Get(echo.HeaderXRequestID)
		}

		// リクエストごとに `request_id` を追加したロガーを作成
		reqLogger := logger.TestLogger.With(
			zap.String("method", req.Method),
			zap.String("uri", req.RequestURI),
			zap.String("request_id", id),
			// zap.String("request_id", req.Header.Get(echo.HeaderXRequestID)),
		)

		// `context` にロガーをセット
		ctx := logger.SetLoggerToContext(req.Context(), reqLogger)
		c.SetRequest(req.WithContext(ctx))

		reqLogger.Info("reqLogger by middleware")

		return next(c)
	}
}

controller

package controller

import (
	"layered/logger"
	"github.com/labstack/echo/v4"
)

func ZapLogger() echo.HandlerFunc {
	return func(c echo.Context) error {

		reqLogger := logger.GetLoggerFromContext(c.Request().Context())

		// ログを出力
		reqLogger.Info("reqLogger by controller")

		return c.JSON(http.StatusOK)
	}
}

main.go

	e := echo.New()
	logger.InitLogger()
	e.Use(echomiddleware.RequestID())
	e.Use(middleware.TestMiddleware)

下記のようなログが出力されます。
TestMiddlewareで設定したzapのフォーマットに従って、middleware・controllerでも出力されています。また、request_idも一連の処理で同一のものが付与されたログが出力されます。これによって一連の処理を紐づけることができます。

{"level":"info","ts":1739802523.572062,"caller":"middleware/logger.go:37","msg":"reqLogger by middleware","method":"GET","uri":"/","request_id":"XByMYCqgrHvMQNiNfoOEarPqpnxTyPmd"}
{"level":"info","ts":1739802523.5727336,"caller":"controller/zaplogger.go:1248","msg":"reqLogger by controller","method":"GET","uri":"/","request_id":"XByMYCqgrHvMQNiNfoOEarPqpnxTyPmd"}

まとめ

contextにフォーマット化されたlogger(ログ出力のメソッド)を渡すことで、一連の処理に同一の値を付与したり、同じフォーマットでログを出力できるようになります。

Discussion