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

时间:2013-09-16 08:13:20

标签: java spring

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

对于每个请求,将创建一个'controller class'实例来处理请求。

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

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

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

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

LOG.doLog("did step 1");

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

6 个答案:

答案 0 :(得分:27)

您也可以尝试使用MDC类Log4j。 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

我还发现此链接很有用 - > What is the difference between Log4j's NDC and MDC facilities?

答案 1 :(得分:17)

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

  1. 为每个请求生成唯一ID
  2. 存储ID并在代码中随处访问
  3. 自动记录身份
  4. 我会建议这种方法

    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,则有一个有趣的article2018 alternative),可以自定义记录器的模式布局。但是,您只需创建日志系统接口的代理,并将id手动附加到每个记录的字符串。

答案 2 :(得分:2)

您还可以使用" Fish Tagging"在Log4j中2.与https://logging.apache.org/log4j/2.x/manual/thread-context.html

中描述的MDC和NDC(线程基础)相同的想法

在这里,您可以使用线程上下文堆栈或线程上下文映射。 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如何对其进行描述。

答案 3 :(得分:2)

如果您不介意使用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,但至少您拥有控制器中的值,即业务逻辑的起点。

答案 4 :(得分:0)

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

请参阅官方文档here

如果要在非请求感知服务中注入此项,则应使用proxyMode选项。更多信息here(来自this堆叠问题)

答案 5 :(得分:0)

你必须使用如下的数据库序列(如果你的数据库是ORACLE)

create sequence "sequence_Name";

String uniqueString=sessionFactory.getCurrentSession(). createSQLQuery("SELECT \"sequence_Name\".nextval FROM dual").list().get(0);