NestJS 如何在自定义装饰器中获取Request/ExecutionContext用于日志处理
NestJS 日志上下文获取相关问题解答
1. 你当前的@Loggable装饰器方案不可行,核心原因如下
- 普通单例作用域的Injectable方法被调用时,Nest不会主动将请求执行上下文注入到方法参数中,你在重写的方法里只能拿到业务代码传入的入参,没有任何途径可以直接获取到当前请求的
ExecutionContext或请求对象 - 单例实例是所有请求共享的,你无法将请求上下文存在实例属性中,并发请求下会出现上下文覆盖的冲突问题
- 上下文透传成本极高:如果要跨服务传递上下文,你需要修改所有关联服务的方法入参来接收上下文参数,对业务代码侵入性极强,可维护性极低
- 现有代码存在逻辑缺陷:你重写方法后重新定义
ROUTE_ARGS_METADATA的操作没有实际作用,这个元数据只有控制器的路由方法才会被Nest解析,普通Injectable的方法不会触发参数解析逻辑
2. @Guard获取执行上下文的原理
@UseGuards装饰器的作用仅仅是将你声明的守卫类/实例,作为元数据挂载到对应的控制器或者路由方法上。
Nest内部的请求执行引擎在处理请求时,会先读取路由上注册的所有守卫,主动构造好当前请求的ExecutionContext实例(封装了当前请求的所有上下文信息,包括HTTP请求/响应、RPC参数、GraphQL上下文等),作为入参调用守卫的canActivate方法,所以守卫可以直接拿到上下文,不需要自己主动获取。
3. @Req装饰器获取请求对象的原理
@Req是参数装饰器,当你用它修饰控制器方法的参数时,它会向对应控制器方法的ROUTE_ARGS_METADATA元数据中插入一条标记,声明该位置的参数需要注入HTTP请求对象。
Nest的路由参数解析器在调用控制器方法前,会先读取该元数据,根据标记类型从当前的ExecutionContext中取出对应的对象(@Req对应context.switchToHttp().getRequest()),组装成参数数组后再调用控制器方法,因此你可以直接拿到当前请求的对象。
推荐的可行实现方案
如果你希望保留Injectable默认单例作用域,同时实现按请求维度筛选日志,最合理的方案是使用异步本地存储(AsyncLocalStorage):
- 实现一个全局拦截器,在请求进入时将当前的
ExecutionContext或者请求ID、用户信息等你需要的日志维度字段存入AsyncLocalStorage - 自定义Logger类,打印日志时从AsyncLocalStorage中取出当前请求的上下文信息拼接进日志即可
- 该方案完全无业务侵入性,也不需要修改服务的作用域,是Nest官方推荐的请求上下文透传方案
内容的提问来源于stack exchange,提问作者Ugo Evola
相关产品推荐
相关产品推荐

