kratos.New() 启动 panic 是因基础服务未创建,中间件需挂载到已初始化的 HTTP/gRPC Server 实例上;transport.Context 单向封装导致 ctx.Value() 失效,应使用 transport 接口取值;recover 仅捕获 handler panic,启动期错误需前置检查配置与服务实例。

kratos.New() 启动就 panic,不是中间件写错了,是连基础服务都没跑起来——中间件注册的前提是 http.Server 或 grpc.Server 实例已创建并传入 kratos.New(),否则中间件根本没地方挂载。
中间件必须挂在 Server 实例上,不能单独注册
Kratos 的中间件(比如 recovery.Recovery()、tracing.Server())不是全局注册的,而是通过 http.Middleware() 或 grpc.Middleware() 封装进具体 Server 实例。它不作用于整个应用生命周期,只对经过该 Server 的请求生效。
-
http.Middleware()只影响http.NewServer()创建的 HTTP server;grpc.Middleware()同理,只作用于grpc.NewServer() - 如果只启了 HTTP server 却给
grpc.Middleware()传参,不会报错但完全无效 - 多个中间件按传入顺序执行:先注册的先执行(例如
recovery放最后,才能兜住前面中间件抛出的 panic) - 别在
main()里调用middleware.Use()这类伪全局方法——Kratos 没这 API,那是其他框架的写法
ctx.Value() 在 HTTP/gRPC 中取不到值?transport.Context 是单向封装
Kratos 把原始 context.Context 包了一层 transport.Context,但 HTTP 和 gRPC 的封装方式不同:transport.Context 从 HTTP 请求头或 gRPC metadata 提取元数据,再注入到 context;但它不反向同步回原始 context,所以你在中间件里用 ctx.Value("key") 取不到自己塞进去的值。
- 正确做法:用
transport.FromContext(ctx)获取 transport 实例,再调.RequestHeader()(HTTP)或.Operation()(gRPC)取值 - 想透传自定义字段?别塞
ctx.WithValue(),改用transport.SetRequestHeader(ctx, "X-Trace-ID", "xxx"),下游能用transport.FromContext(ctx).RequestHeader().Get("X-Trace-ID")拿到 - 日志中间件里打 trace_id,必须用
transport.TraceID(ctx),而不是假设ctx.Value(trace.Key)存在
自定义中间件接收参数时,别直接 closure 捕获 logger 或 config
看似方便的闭包捕获(比如 func(logger log.Logger) middleware.Middleware),在 Kratos 依赖注入(Wire)场景下容易引发循环依赖或初始化顺序错误——logger 本身可能依赖配置加载完成,而中间件构造又早于配置加载。
- 推荐写法:中间件函数只接收必要参数(如开关 flag、超时时间),把
log.Logger、*conf.Config等通过Handler的context.Context传递,或由 service 层注入 - 示例签名应为:
func(timeout time.Duration) middleware.Middleware,而非func(logger log.Logger, timeout time.Duration) middleware.Middleware - 如果真要访问 logger,用
log.NewHelper(log.With(logger, "middleware", "auth"))在 handler 内部构造,避免提前绑定
recover 中间件没生效?panic 被上层 http.ServeHTTP 吞掉了
recovery.Recovery() 确实能捕获 handler 执行时的 panic,但它无法捕获 Server 启动阶段(比如 http.NewServer() 初始化时)或中间件自身构造时的 panic——那些 panic 发生在 Kratos 应用启动流程中,早于任何请求上下文。
- 常见误判:加了
recovery.Recovery()还 panic,其实是service.Register()失败或conf.Load()没调用导致的启动失败,和 recovery 无关 - 验证 recovery 是否工作:手动在某个
service.SayHello()方法里写panic("test"),然后发请求,看日志是否打出 recover 日志并返回 500 - 真正要防的是启动期 panic,必须确保
conf.Load()在kratos.New()前、grpc.NewServer()和http.NewServer()已创建且非 nil、所有config.Provider文件真实存在
中间件本身不难写,难的是搞清它在哪一层生效、依赖哪些前置条件、以及 panic 到底发生在哪一环——很多“中间件失效”问题,根子其实在 kratos.New() 那一行就崩了。











