Plan 9とGo言語のブログ

主にPlan 9やGo言語の日々気づいたことを書きます。

labstack/echoのv4.6でRequestLoggerWithConfigを使う

labstack/echoには、以前から middleware.LoggerWithConfig が存在していて、リクエストログのカスタマイズがある程度は可能でした。公式のLogger Middlewareドキュメントより引用します。

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

この Formatfasttemplate で処理されます。テンプレートの変数はレイテンシやリクエストサイズ、IPアドレスなどミドルウェア側で用意されている値を選択することはできますが、アプリケーション側で用意した任意の値を出力させることができませんでした。また、 io.Writer で出力先の変更はできますが、io.Writer インターフェイスを満たさない任意のロガーを使うことはできませんでした。なので任意の値を追加するためには、独自のミドルウェアを実装して、LoggerWithConfigが行っている処理と同じようにリクエストログで必要な値を echo.Context から計算する必要がありました。

RequestLoggerWithConfig

echoのv4.6で middleware.RequestLoggerWithConfig が追加されて、リクエストログのカスタマイズが少しだけ簡単になりました。以下に例を示します。

package main

import (
    "log"

    "github.com/go-logr/stdr"
    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
)

var logger = stdr.New(log.Default())

func writeRequestLog(c echo.Context, v middleware.RequestLoggerValues) error {
    logger.Info("finished",
        "method", v.Method,
        "path", v.URIPath,
        "remote_ip", v.RemoteIP,
        "user_agent", v.UserAgent,
        "protocol", v.Protocol,
        "status", v.Status,
        "latency", v.Latency,
        "content_length", v.ContentLength, // ContentLengthの型はstringで、GETの場合は空文字列
        "response_size", v.ResponseSize,
    )
    return nil
}

func requestLogger() echo.MiddlewareFunc {
    return middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
        LogValuesFunc:    writeRequestLog,
        LogMethod:        true,
        LogURIPath:       true,
        LogRemoteIP:      true,
        LogUserAgent:     true,
        LogProtocol:      true,
        LogStatus:        true,
        LogLatency:       true,
        LogContentLength: true,
        LogResponseSize:  true,
    })
}

func main() {
    e := echo.New()
    e.Use(requestLogger())
    e.GET("/", func(c echo.Context) error {
        return c.String(http.StatusOK, "hello")
    })
    e.Start(":8080")
}

これで、任意のロガーを通してアクセスログを出力できるようになりました。middleware.RequestLoggerValues で必要な値に対応する middleware.RequestLoggerConfig のフラグを true にセットしておかないとゼロ値になってしまうので、値がおかしい場合は見直してみましょう。

echo.Context.Logger

ところで echo.Context にはリクエストログとは別に

type Context interface {
    Logger() echo.Logger
}

が用意されていて、アプリケーションで自由に使うことができます。この echo.Logger の実態はデフォルトだと gommon.Logger になっていて echo.Context.SetLogger で変更が可能です。ただし、echo.Logger インターフェイス

type Logger interface {
    Output() io.Writer
    SetOutput(w io.Writer)
    Prefix() string
    SetPrefix(p string)
    Level() log.Lvl
    SetLevel(v log.Lvl)
    SetHeader(h string)
    Print(i ...interface{})
    Printf(format string, args ...interface{})
    Printj(j log.JSON)
    Debug(i ...interface{})
    Debugf(format string, args ...interface{})
    Debugj(j log.JSON)
    Info(i ...interface{})
    ... この後もログレベルごとに3つメソッドが定義されている...

のようにとても大きくて、JSONログが必要なだけでも全部満たす必要があるのは不毛なので、もう少しこれもどうにかならないかなと思っています。