前段
ミドルウェアのエラーログハンドリング周りの処理が複雑で、いくつか問題が発生していました。
その対処を行う過程で自分が得た知見を簡単にご紹介します。
ミドルウェアの処理順序
まず Echo の Middleware における注意点は、その処理順序です。
仮に、A・B という Middleware があったとします。
それを、下記のように順に呼び出します。
echo := echo.New()
echo.Use(echo.MiddlewareFunc(middlewares.NewA()))
echo.Use(echo.MiddlewareFunc(middlewares.NewB()))
そして、上記 A・B のそれぞれの Middleware の中では下記のように処理しているとします。
func NewA() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
fmt.Println(`A:before`)
err := next(c)
fmt.Println(`A:after`)
return err
}
}
}
func NewB() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
fmt.Println(`B:before`)
err := next(c)
fmt.Println(`B:after`)
return err
}
}
}
そうしますと、出力結果は下記となります。
A:before
B:before
B:after
A:after
つまり、next(c)
記述箇所の前後で処理が分けられ、入れ子構造のようになって順々に次の Middleware が呼ばれていくことになります。
なお単純化のために A・B の 2 つで書きましたが、更に複数の Middleware があれば、この階層はどんどん深くなっていきます。
(コチラの記事も参照ください:Try Golang! EchoでオリジナルのMiddlewareを作ろう!)
*echo.HTTPError.Internal を活用して内部的なエラーを設定 & 取り出し
前段
DataDog の Trace 用パッケージである dd-trace-go / func Middleware の処理中に下記のようなコメントがあります。
// Any error that is not an *echo.HTTPError will be treated as an error with 500 status code.
つまり上記メソッドの処理が実行されるタイミングでエラーを*echo.HTTPError
型として定義しておかないと、全て 500 エラーとして返されてしまいます。
本題
上記対処のため、特定の Middleware 内の処理で func echo.NewHTTPError を用いてエラーを*echo.HTTPError
型に変換してから return しているとします。
echo.NewHTTPError(400, error.Error())
引数には、ステータスコードと、エラー条件の値を渡しています。
しかし、これをすることでerror
情報を部分的に取り出したり別の情報を付加して返すことになるので、元々のerror
情報から"変質"してしまいます。
その対処として、func (*HTTPError) SetInternal を用いることができます。
echo.NewHTTPError(400, error.Error()).SetInternal(error)
上記により、元々あったerror
情報を内部的に保持することができますので、その後の Middleware で扱いやすくなります。
下記のように取り出すことができます。
error.Internal.Error()
echo.Context / Error メソッドについての注意点
echo における type Context の中にError(err error)
メソッドが存在し、下記のように説明があります。
// A side-effect of calling global error handler is that now Response has been committed (sent to the client) and
// middlewares up in chain can not change Response status code or Response body anymore.
つまりこのメソッドが呼ばれると、クライアントにエラーレスポンスを返してしまうので、以降は Midleware 内でエラー内容を変更することができなくなってしまいます。
c
がecho.Context
型だとして、実行は下記のようになります。
c.Error(error)
なおどのタイミングでこの実行を行うかは、上述の内容も踏まえた上での検討が必要です。
ここに至るまでに適切なエラーハンドリング処理を行なっていないと、例えばエラーステータスが全て 500 として返される、などといったことが起き得るので注意が必要です。
下記のようにレスポンスを取り出せます。
c.Response()