108 lines
3.2 KiB
Go
108 lines
3.2 KiB
Go
package middleware
|
|
|
|
import (
|
|
"fmt"
|
|
"runtime/debug"
|
|
"time"
|
|
|
|
apierrors "git.yuntuo.hk/k2safe/go-omni/errors"
|
|
"git.yuntuo.hk/k2safe/go-omni/httpx"
|
|
"git.yuntuo.hk/k2safe/go-omni/logx"
|
|
)
|
|
|
|
// RequestIDKey is the value key used by the request ID middleware.
|
|
const RequestIDKey = "go-omni.requestId"
|
|
|
|
// RequestID reads or creates a request ID and returns it in the response
|
|
// header. It is a transport-neutral GoOmni middleware.
|
|
func RequestID(header string) httpx.Middleware {
|
|
return func(next httpx.Handler) httpx.Handler {
|
|
return func(ctx *httpx.Context) error {
|
|
requestID := ctx.Header(header)
|
|
if requestID == "" {
|
|
requestID = newRequestID()
|
|
}
|
|
ctx.Set(RequestIDKey, requestID)
|
|
ctx.SetResponseHeader(header, requestID)
|
|
ctx.WithContext(logx.WithRequestID(ctx.Context(), requestID))
|
|
return next(ctx)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Recovery converts panics from handlers and middleware into the standard
|
|
// internal error. Recovery belongs to GoOmni's transport-neutral middleware
|
|
// rather than a router-specific hook.
|
|
func Recovery() httpx.Middleware {
|
|
return func(next httpx.Handler) httpx.Handler {
|
|
return func(ctx *httpx.Context) (err error) {
|
|
defer func() {
|
|
if recovered := recover(); recovered != nil {
|
|
logx.ErrorContext(ctx.Context(), "http.panic", "panic", recovered, "stack", string(debug.Stack()), "method", ctx.Request().Method, "route", httpx.RoutePattern(ctx.Context()))
|
|
err = apierrors.Internal(fmt.Errorf("panic recovered: %v", recovered))
|
|
}
|
|
}()
|
|
return next(ctx)
|
|
}
|
|
}
|
|
}
|
|
|
|
// AccessLog records one structured event after each request. It deliberately
|
|
// lives in GoOmni instead of using a router-specific logger so business
|
|
// applications get the same log format with every transport adapter.
|
|
type AccessLogOption func(*accessLogConfig)
|
|
type accessLogConfig struct{ slowThreshold time.Duration }
|
|
|
|
// WithSlowRequestThreshold logs slow requests at WARN. Zero disables detection.
|
|
func WithSlowRequestThreshold(threshold time.Duration) AccessLogOption {
|
|
return func(c *accessLogConfig) {
|
|
if threshold >= 0 {
|
|
c.slowThreshold = threshold
|
|
}
|
|
}
|
|
}
|
|
|
|
func AccessLog(options ...AccessLogOption) httpx.Middleware {
|
|
settings := accessLogConfig{}
|
|
for _, option := range options {
|
|
if option != nil {
|
|
option(&settings)
|
|
}
|
|
}
|
|
return func(next httpx.Handler) httpx.Handler {
|
|
return func(ctx *httpx.Context) error {
|
|
started := time.Now()
|
|
err := next(ctx)
|
|
status := ctx.Status()
|
|
if err != nil && !ctx.Responded() {
|
|
status = apierrors.From(err).Status
|
|
}
|
|
duration := time.Since(started)
|
|
slow := settings.slowThreshold > 0 && duration >= settings.slowThreshold
|
|
attrs := []any{
|
|
"method", ctx.Request().Method,
|
|
"path", ctx.Request().URL.Path,
|
|
"route", httpx.RoutePattern(ctx.Context()),
|
|
"status", status,
|
|
"durationMs", duration.Seconds() * 1000,
|
|
"clientIp", ctx.ClientIP(),
|
|
}
|
|
if slow {
|
|
attrs = append(attrs, "slow", true)
|
|
}
|
|
if err != nil {
|
|
attrs = append(attrs, "error", err)
|
|
}
|
|
switch {
|
|
case status >= 500:
|
|
logx.ErrorContext(ctx.Context(), "http.request", attrs...)
|
|
case slow:
|
|
logx.WarnContext(ctx.Context(), "http.request", attrs...)
|
|
default:
|
|
logx.InfoContext(ctx.Context(), "http.request", attrs...)
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
}
|