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
相关产品推荐
相关产品推荐

