You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go Chi中ServeHTTP错误处理及自定义日志器注入方案咨询

基于Go Chi框架的带错误返回Handler与日志注入方案

我正在使用Go的Chi框架,自定义了一个返回error的HandlerFunc。原本计划编写中间件来使用这个自定义Handler并返回需要处理的错误,既可以选择在ServeHTTP中处理错误,也可以通过全局错误处理中间件注入自定义日志器;现在确定要在ServeHTTP中处理返回的错误,但需要支持注入任意自定义日志器。

初步实现思路

// 返回error的HandlerFunc
type HandlerFunc func(w http.ResponseWriter, r *http.Request) error

type customHandler HandlerFunc

func (h customHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    err := h(w, r)
    if err != nil {
        // TODO: 在这里处理handler返回的错误
    }
}

func main() {
    r := chi.NewRouter()

    r.Method(http.MethodGet, "/", customHandler(func(w http.ResponseWriter, req *http.Request) error {
        r.Use(useSomeMiddleware)

        // Handler逻辑...

        // Handler返回错误,需要被处理
        return errors.New("handler返回错误")
    }))

    server := &http.Server{
        Addr:    fmt.Sprintf("%v:%v", "", "1337"),
        Handler: r,
    }

    err := server.ListenAndServe()
    if err != nil {
        log.Fatalf("main: 启动服务器失败: %v", err)
    }
}

func useSomeMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 可能返回错误的中间件逻辑

        err := errors.New("中间件错误")
        if err != nil {
            // TODO: 将错误返回给ServeHTTP处理,或者交给新的错误处理中间件
        }
    })
}

更新1

理想情况下,我希望编写一个使用自定义HandlerFunc的中间件,返回需要处理的错误。

我可以轻松地从使用自定义HandlerFunc的中间件中返回错误,这样就能在ServeHTTP中处理错误,但还在研究如何构建可以传入日志器的新中间件,以下是我的中间件实现雏形:

func useSomeMiddleware(next http.Handler) http.Handler {
    return customHandler(func(w http.ResponseWriter, r *http.Request) error {
        err := errors.New("use some middleware error")
        if err != nil {
            return err
        }

        return nil
    })
}

现寻求可行的实现方案,满足以下需求:

  • 在ServeHTTP中处理错误
  • 支持注入自定义日志器
  • 完善可返回错误的中间件

可行实现方案

1. 重构自定义Handler,支持日志器注入

将customHandler改为结构体类型,持有处理函数和日志器实例,确保错误处理时能使用注入的自定义日志:

import (
    "net/http"
    "log/slog"
    "os"
    "context"
    "errors"
)

// 自定义带错误返回的HandlerFunc
type HandlerFunc func(w http.ResponseWriter, r *http.Request) error

// 自定义Handler结构体,包含处理逻辑和日志器
type CustomHandler struct {
    handler HandlerFunc
    logger  *slog.Logger // 可替换为任意日志接口,如zap、logrus等
}

// 实现http.Handler接口的ServeHTTP方法,统一处理错误
func (h *CustomHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    err := h.handler(w, r)
    if err != nil {
        // 使用注入的日志器记录错误上下文
        h.logger.Error("handler执行异常", 
            "error", err,
            "path", r.URL.Path,
            "method", r.Method,
            "remote_addr", r.RemoteAddr,
        )
        // 可根据错误类型返回对应HTTP状态码(示例返回500)
        http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
    }
}

// 辅助函数:快速包装自定义Handler并注入日志器
func WrapHandler(handler HandlerFunc, logger *slog.Logger) http.Handler {
    return &CustomHandler{
        handler: handler,
        logger:  logger,
    }
}

2. 支持返回错误的中间件实现

中间件需要适配自定义HandlerFunc,提供两种日志传递方式:

方式一:中间件直接持有日志器(适合全局统一日志)

// 中间件构造函数:传入日志器,返回适配自定义HandlerFunc的中间件
func NewAuthMiddleware(logger *slog.Logger) func(next HandlerFunc) HandlerFunc {
    return func(next HandlerFunc) HandlerFunc {
        return func(w http.ResponseWriter, r *http.Request) error {
            // 中间件逻辑:示例校验请求头
            apiKey := r.Header.Get("X-API-Key")
            if apiKey == "" {
                err := errors.New("缺少API密钥")
                logger.Warn("请求校验失败", "error", err, "path", r.URL.Path)
                return err
            }

            // 调用下一个handler
            return next(w, r)
        }
    }
}

方式二:从请求上下文获取日志器(适合链路级日志传递)

如果需要日志器随请求链路传递,可将日志存入请求上下文:

// 定义上下文Key,避免冲突
type loggerKey struct{}

// 将日志器存入请求上下文
func WithLogger(r *http.Request, logger *slog.Logger) *http.Request {
    return r.WithContext(context.WithValue(r.Context(), loggerKey{}, logger))
}

// 从请求上下文获取日志器,默认返回标准日志器
func GetLogger(r *http.Request) *slog.Logger {
    logger, ok := r.Context().Value(loggerKey{}).(*slog.Logger)
    if !ok {
        return slog.Default()
    }
    return logger
}

// 适配自定义HandlerFunc的中间件
func LoggingMiddleware(next HandlerFunc) HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) error {
        logger := GetLogger(r)
        logger.Info("开始处理请求", "method", r.Method, "path", r.URL.Path)

        // 调用下一个handler
        err := next(w, r)

        logger.Info("请求处理完成", "method", r.Method, "path", r.URL.Path, "error", err)
        return err
    }
}

3. 主函数整合使用

func main() {
    // 初始化自定义日志器(示例用slog JSON格式)
    logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))

    r := chi.NewRouter()

    // 全局中间件:注入日志器到请求上下文(如果使用方式二)
    r.Use(func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            r = WithLogger(r, logger)
            next.ServeHTTP(w, r)
        })
    })

    // 初始化带日志的中间件(方式一)
    authMiddleware := NewAuthMiddleware(logger)

    // 注册路由:使用WrapHandler包装自定义Handler,整合中间件
    r.Method(http.MethodGet, "/", WrapHandler(
        // 链式调用中间件和业务Handler
        authMiddleware(LoggingMiddleware(func(w http.ResponseWriter, req *http.Request) error {
            // 业务逻辑示例
            return errors.New("业务处理出错")
        })),
        logger,
    ))

    server := &http.Server{
        Addr:    ":1337",
        Handler: r,
    }

    if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
        logger.Error("服务器启动失败", "error", err)
        os.Exit(1)
    }
}

关键说明

  • 自定义Handler通过结构体持有日志器,确保错误处理时能使用注入的实例
  • 中间件适配自定义HandlerFunc,支持直接传入日志器或从上下文获取,满足不同场景需求
  • 错误统一在ServeHTTP中处理,可扩展自定义错误类型(如包含HTTP状态码的错误)来返回更精准的响应

内容的提问来源于stack exchange,提问作者Corbuk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 04:57:24