22

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

对于每个请求,将创建一个“控制器类”的实例来处理请求。

在业务逻辑中,我想使用自动分配给每个请求的唯一 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.

从日志中,我可以意识到: req #XXX: begin-step1-step2-end req #YYY: begin-step1-end

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

如果日志工具可以以静态方式调用就完美了:

LOG.doLog("did step 1");

知道我该怎么做吗?谢谢你 :)

4

6 回答 6

36

您也可以尝试使用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(); 
 }
}

现在,如果您在调试、警告或错误中记录日志,则可以在代码中的任何位置。您在 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

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

于 2014-08-18T20:51:20.220 回答
26

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

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

我会建议这种方法

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

  2. 您可以将 id 保存为请求的属性,在这种情况下,id 将仅在控制器层中传播,而不是在服务中传播。因此,您可以使用ThreadLocal变量或让 Spring使用RequestContextHolder来解决问题:RequestContextHolder 将允许您访问该特定线程的请求,以及服务层中的请求属性。RequestContextHolder 使用 ThreadLocal 变量来存储请求。您可以通过这种方式访问​​请求:

    ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
    // Extract the request
    HttpServletRequest request = attr.getRequest();
    
  3. 如果您使用 log4j,有一篇有趣的文章2018 替代品)关于记录器模式布局的自定义。但是,您可以简单地创建日志系统接口的代理并手动将 id 附加到每个记录的字符串。

于 2013-09-16T08:45:51.217 回答
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();

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

要将新的 id 放入地图(例如,对于每个传入的请求),您可以在 ServletRequestListener 实现中按照 Sharadr 的描述进行操作。

于 2015-12-03T08:59:52.850 回答
3

如果您不介意使用 spring 4.1.3 或更高版本,您可以将您的请求包装到ContentCachingRequestWrapper类的自定义子类中。

public class MyHTTPServletRequestWrapper extends ContentCachingRequestWrapper {

    private UUID uuid;

    public MyHTTPServletRequestWrapper (HttpServletRequest request) {
        super(request);
        uuid = UUID.randomUUID();
    }

    public UUID getUUID() {
        return uuid;
    }
}

在您的 spring 控制器中,将请求添加到方法的参数中,并将其转换为您的自定义包装器:

@RequestMapping(value="/get", method = RequestMethod.GET, produces="application/json")
    public @ResponseBody String find(@RequestParam(value = "id") String id, HttpServletRequest request) {
        MyHTTPServletRequestWrapper wrappedRequest = (WGHTTPServletRequestWrapper)request;
        System.out.println(wrappedRequest.getUUID());
...
}

您将需要用户过滤,以连接点:

public class RequestLoggingFilter extends GenericFilterBean {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest requestToCache = new MyHTTPServletRequestWrapper((HttpServletRequest) request);
System.out.println(((WGHTTPServletRequestWrapper)requestToCache).getUUID());
        chain.doFilter(requestToCache, response);

    }
}

您会发现 RequestLoggingFilter.doFilter() 和控制器 getId() 中的 printlin 将产生相同的 UUID。

您只需要在适当的地方尝试使用 UUID,但至少您在控制器中具有值,这是您的业务逻辑的开始。

于 2016-09-23T05:47:42.077 回答
0

您必须使用数据库序列(如果您的数据库是 ORACLE),如下所示

create sequence "sequence_Name";

String uniqueString=sessionFactory.getCurrentSession(). createSQLQuery("SELECT \"sequence_Name\".nextval FROM dual").list().get(0);
于 2021-06-20T03:39:44.660 回答
0

您可以使用@Scope注释为每个请求加载服务的新实例。

在此处查看官方文档

如果你想将它注入到非请求感知服务中,你应该使用 proxyMode 选项。更多信息在这里(来自这个堆栈问题)

于 2018-02-08T09:06:17.003 回答