Присваивать уникальный идентификатор каждому запросу в веб-приложении на основе spring

Я создал веб-приложение Java на основе spring,

для каждого запроса, экземпляр класса контроллера будет создан для обработки запроса.

в бизнес-логике, я хочу сделать некоторые записи с 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

Я надеюсь, что ведение журнала можно легко вызвать везде в коде, поэтому я не хочу добавлять параметр "requestId" к каждой java-функции,

Идеально, если инструмент журнала можно вызвать статическим способом:

LOG.doLog("did step 1");

любая идея, как я могу это сделать? спасибо:)

Ответ 1

У вас есть три проблемы:

  • Создайте уникальный идентификатор для каждого запроса
  • Храните идентификатор и обращайтесь к нему везде в коде
  • Авторизовать идентификатор автоматически

Я бы предложил, чтобы это подходило

Ответ 2

Вы также можете попробовать использовать MDC класс Log4j. MDC управляется на основе потоков. Если вы используете ServletRequestListner, вы можете установить уникальный идентификатор в requestInitialized.

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

Я также нашел эту ссылку полезной → В чем разница между возможностями NDD и MDC Log4j?

Ответ 3

Вы также можете использовать "Fish Tagging" в Log4j 2. Это та же идея, что и MDC и NDC (нить-основа), как описано в https://logging.apache.org/log4j/2.x/manual/thread-context.html

Здесь вы можете использовать либо Контекст контекста потока, либо Контекстную карту потока. Пример для Карты выглядит следующим образом:

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

//clear map
ThreadContext.clearMap();

И для шаблона в log4j2.xml вы также можете использовать тег% X {KEY}.

Чтобы поместить новый идентификатор на карту (например, для каждого входящего запроса), вы можете сделать это в реализации ServletRequestListener, как описано Sharadr.

Ответ 4

Если вы не против использования 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);

    }
}

Вы обнаружите, что printlin из RequestLoggingFilter.doFilter() и из контроллеров getId() выдаст тот же UUID.

Вам просто нужно поиграть, чтобы использовать UUID в соответствующих местах, но по крайней мере у вас есть значение в вашем контроллере, где начинается ваша бизнес-логика.

Ответ 5

Вы можете использовать аннотацию @Scope для загрузки нового экземпляра службы для каждого запроса.

См. официальную документацию здесь

Если вы хотите ввести это в службу поддержки без запроса, вы должны использовать опцию proxyMode. подробнее здесь (из этого вопроса в стеке)