为基于Spring的Web应用程序中的每个请求分配唯一ID

use*_*598 16 java spring

我创建了一个基于Spring的Java Web应用程序,

对于每个请求,将创建一个"控制器类"实例来处理请求.

在业务逻辑中,我希望使用自动分配给每个请求的UNIQUE ID进行一些日志记录,以便我可以跟踪程序的确切操作.

日志可能是这样的(同时有2个请求):

[INFO] request #XXX: begin.
[INFO] request #XXX: did step 1
[INFO] request #YYY: begin.
[INFO] request #XXX: did step 2
[INFO] request #YYY: did step 1
[INFO] request #XXX: end.
[INFO] request #YYY: end.
Run Code Online (Sandbox Code Playgroud)

从日志中,我可以实现:req #XXX:begin-step1-step2-end req #YYY:begin-step1-end

我希望可以在代码中的任何地方轻松调用日志记录,因此我不想在每个java函数中添加"requestId"参数,

如果可以以静态方式调用日志工具,那将是完美的:

LOG.doLog("did step 1");
Run Code Online (Sandbox Code Playgroud)

我怎么能这样做呢?谢谢 :)

Sha*_*adr 31

您也可以尝试使用Log4j的MDC类.MDC基于每个线程进行管理.如果您使用的是ServletRequestListner,则可以在requestInitialized中设置唯一ID.

import org.apache.log4j.MDC;
import java.util.UUID;

public class TestRequestListener implements ServletRequestListener {    
protected static final Logger LOGGER = LoggerFactory.getLogger(TestRequestListener.class);

 public void requestInitialized(ServletRequestEvent arg0) {
    LOGGER.debug("++++++++++++ REQUEST INITIALIZED +++++++++++++++++");

    MDC.put("RequestId", UUID.randomUUID());

 }

 public void requestDestroyed(ServletRequestEvent arg0) {
    LOGGER.debug("-------------REQUEST DESTROYED ------------");
    MDC.clear(); 
 }
}
Run Code Online (Sandbox Code Playgroud)

如果您执行日志调试,警告或错误,现在代码中的任何位置.无论你输入MDC的是什么,都会打印出来.您需要配置log4j.properties.注意%X {RequestId}.这引用了上面的requestInitialized()中插入的键名.

log4j.appender.A1.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSSS} %p %C %X{RequestId} - %m%n
Run Code Online (Sandbox Code Playgroud)

我也发现这个链接很有用 - > Log4j的NDC和MDC设施有什么区别?

  • 呃小心!例如,当使用 Spring WebTemplate 以及其他情况时,其中有另一个线程池用于客户端交互...当该池中的线程被分配任务时,它*不会*自​​动获取 MDC/线程本地的副本信息。Spring WebTemplate HTTP 线程池不是调用者的子线程,因此 MDC *不会*被复制。它将具有来自先前调用的任意值。需要拦截任何线程本地数据并将其从池中复制到线程中。 (4认同)
  • 对于那里的 Spring Boot 用户,您可以在 application.yml 中配置日志记录模式。`logging.pattern.console: %d{yyyy-MM-dd HH:mm:ss.SSSS} %p %C %X{RequestId} - %m%n` (2认同)

MPa*_*esi 19

您有三个不同的问题需要解决:

  1. 为每个请求生成唯一ID
  2. 存储id并在代码中的任何位置访问它
  3. 自动记录id

我会建议这种方法

  1. 使用Servlet过滤器或ServletRequestListener(由M. Deinum建议)或Spring Handler Interceptor 以一般方式拦截请求,在那里你可以创建一个唯一的id,可能有一个UUID

  2. 您可以将id保存为请求的属性,在这种情况下,id将仅在控制器层中传播,而不是在服务中传播.所以,你可以解决使用问题的ThreadLocal变量或询问春天做魔术与RequestContextHolder:在RequestContextHolder将允许您访问特定线程的请求,并请求属性以及在服务层.RequestContextHolder使用ThreadLocal变量来存储请求.您可以通过以下方式访问请求:

    ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
    // Extract the request
    HttpServletRequest request = attr.getRequest();
    
    Run Code Online (Sandbox Code Playgroud)
  3. 如果您使用log4j,则有一篇有趣的文章(2018替代),用于定制记录器的模式布局.但是,您只需创建日志系统接口的代理,并将id手动附加到每个记录的字符串.

  • 我可能会创建一个“ ServletRequestListener”来代替“过滤器”,因为它会在所有过滤器之前执行。尤其是在Servlet 3.0环境(您并不总是知道所获得的过滤器)中,这很方便。紧接着,我将其远离框架,因此将其存储在`ThreadLocal`中(也许是像Springs LocaleContextHolder一样,将RequestIdHolder包裹起来)。 (2认同)

K.E*_*.E. 5

您还可以在 Log4j 2 中使用“Fish Tagging”。它与https://logging.apache.org/log4j/2.x/manual/thread- 中描述的 MDC 和 NDC(线程基础)的想法相同-上下文.html

在这里您可以使用线程上下文堆栈或线程上下文映射。Map 的示例如下所示:

//put a unique id to the map
ThreadContext.put("id", UUID.randomUUID().toString()

//clear map
ThreadContext.clearMap();
Run Code Online (Sandbox Code Playgroud)

对于 log4j2.xml 中的模式,您还可以使用 %X{KEY} 标记。

要将新的 id 放入映射中(例如,对于每个传入的请求),您可以在 ServletRequestListener 实现中这样做,Sharadr 如何描述它。