V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
feng32
V2EX  ›  程序员

Spring MVC: 如何自动生成 RequestID 并在需要时访问

  •  
  •   feng32 · 2021-02-10 16:41:52 +08:00 · 2302 次点击
    这是一个创建于 1142 天前的主题,其中的信息可能已经有所发展或是发生改变。

    目前的程序结构大概是这样的:

    // base controller 中定义了异常情况下的 API 返回
    public class BaseController {
        @ExceptionHandler(CodeMessageException.class)
        @ResponseBody
        protected ResultModel<Long> exception(CodeMessageException e) {
            log.error("{}", e.getMessage(), e);
            return new ResultModel<>(e.getCode(), e.getMessage());
        }
    }
    
    // 有一个 Filter 已经实现了打印 Request / Response Body (通过 Filter 实现了一些全局的处理逻辑)
    public class HttpLogFilter implements Filter {
        @Override
        public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) {
            ...
        }
    }
    
    // 业务 Controller 继承 BaseController
    @Controller
    public class DeviceController extends BaseController {
        @PostMapping("/device/activate")
        @ResponseBody
        public ResultModel<Long> activateDevice(@RequestBody ActivateDeviceRequest request) {
            return deviceService.activateDevice(request.getSn());
        }
    
    // Service 层可以进行必要的参数检查,然后直接抛一个异常出来
    // 异常被 BaseController 捕捉,然后转成 JSON,这样代码看上去就很干净
    public ResultModel<Long> activateDevice(String sn) {
        if (sn == null) {
            throw new CodeMessageException(ERROR_PARAM_MISSING, "Parameter sn is missing");
        }
        ...
    }
    

    问题来了,现在需要给每个 Request 生成一个 RequestID (UUID),这个 ID 是后端生成的,调用者不需要写对应的代码,但是调用者在 Response 中可以看到这个 Request ID

    如何以尽量小的侵入性实现这个特性呢?基本的需求是 Service 层以及 BaseController (异常处理逻辑) 都能够比较简单地取到这个值并返回

    同时希望打印 Request (包括 url / body)的时候,也能够把 Request ID 打印出来,否则请求量大的时候,日志还是会对不上

    12 条回复    2021-02-12 19:37:04 +08:00
    ltoddy
        1
    ltoddy  
       2021-02-10 16:47:32 +08:00
    你为什么要在服务端生成啊,不应该是 nginx 或者 api-gateway 那里生成嘛,然后把 request id 透传。
    huifer
        2
    huifer  
       2021-02-10 16:51:37 +08:00
    spring boot 里面写个 starter 可以吗. 或者引入统一拦截器,或者用 gateway 上面加入这个
    buliugu
        3
    buliugu  
       2021-02-10 17:04:12 +08:00
    不如老实上一套 APM
    zhiguang
        4
    zhiguang  
       2021-02-10 17:07:42 +08:00
    requestwrapper
    cs419
        5
    cs419  
       2021-02-10 17:27:55 +08:00
    生成 reqId:Filter + ThreadLocal
    resp 添加 reqId: HandlerMethodReturnValueHandler
    jaynos
        6
    jaynos  
       2021-02-10 17:36:12 +08:00
    之前好像实现过一个,通过声明一个 Request 作用域的 Bean 实现,构造函数里直接随机一个字符串
    clf
        7
    clf  
       2021-02-10 21:34:59 +08:00
    这个我以前做过,相当于链路追踪用的 ID 呗?

    大致的逻辑给你一个参考:
    1.返回的 response 结构是我自己定义的,里面一个属性是 TraceId 。
    2.然后实现一个 Context 类,见代码片段: https://gist.github.com/lychs1998/1679bc9c155744ba23ea99307244dad8
    3.调用方式就是 TraceContext.ctx.getTraceId();
    4.丢到日志里当一个 TraceId (随意丢不丢,我丢进去是用来链路追踪用的),在需要的时候用 3 里的语句调用就行。

    整体核心就是用好 InheritableThreadLocal 类。
    choice4
        8
    choice4  
       2021-02-10 23:51:30 +08:00
    filter+threadlocal 保存 id,responsebodyadvice 包装响应
    Geraltt
        9
    Geraltt  
       2021-02-10 23:57:58 +08:00 via Android
    生成 logId,放到 threadlocal 和 mdc 里面 直接打印,最后包装
    luozic
        10
    luozic  
       2021-02-11 00:01:47 +08:00 via iPhone
    除了 context 加,也可以通过 jvm 的后门加进去,instrument 机制,不过推荐对接 metrics 架构,可以平滑由日志到 apm 平台。
    samun
        11
    samun  
       2021-02-12 10:16:04 +08:00
    用的 nginx+lua 处理的
    axbx
        12
    axbx  
       2021-02-12 19:37:04 +08:00
    filte 添加一个 trace_id,然后从 Context 中获取
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1553 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 34ms · UTC 17:00 · PVG 01:00 · LAX 10:00 · JFK 13:00
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.