API ...

ИТЬ: Последняя версия Intellij IDEAинвентарь именно то, что я ищу. Вопрос заключается в том, как реализовать это за пределами среды IDE (чтобы можно было выгружать трассировки асинхронного стека в файлы журналов), в идеале без использования инструментального агента.

С тех пор, как я преобразовал свое приложение из синхронной в асинхронную модель, у меня возникают проблемы при отладке сбоев.

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

Я приведу вам конкретный пример, но меня интересует общее решение проблемы такого рода.

Конкретный пример

Я делаю HTTP-запрос, используяДжерси:

new Client().target("http://test.com/").request().rx().get(JsonNode.class);

гдеrx() указывает, что запрос должен выполняться асинхронно, возвращаяCompletionStage<JsonNode> вместоJsonNode непосредственно. Если этот вызов не удался, я получаю следующую трассировку стека:

javax.ws.rs.ForbiddenException: HTTP 403 Authentication Failed
    at org.glassfish.jersey.client.JerseyInvocation.convertToException(JerseyInvocation.java:1083)
    at org.glassfish.jersey.client.JerseyInvocation.translate(JerseyInvocation.java:883)
    at org.glassfish.jersey.client.JerseyInvocation.lambda$invoke$1(JerseyInvocation.java:767)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:316)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:298)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:229)
    at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:414)
    at org.glassfish.jersey.client.JerseyInvocation.invoke(JerseyInvocation.java:765)
    at org.glassfish.jersey.client.JerseyInvocation$Builder.method(JerseyInvocation.java:456)
    at org.glassfish.jersey.client.JerseyCompletionStageRxInvoker.lambda$method$1(JerseyCompletionStageRxInvoker.java:70)
    at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1590)

Обратите внимание:

Трассировка стека не ссылается на код пользователя.Сообщение об исключении не содержит контекстную информацию о HTTP-запросе, который вызвал ошибку (метод HTTP, URI и т. Д.).

В результате я не могу отследить исключение до его источника.

Почему это происходит

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

CompletableFuture.supplyAsync(() -> getSyncInvoker().method(name, entity, responseType))

заrx() вызовы. Поскольку поставщик создан Джерси, нет ссылки на код пользователя.

Что я пробовал

Я пытался подать отчет об ошибке против Jetty для несвязанного асинхронного примера, и впоследствии был отклонен по соображениям безопасности.

Вместо этого я добавил контекстную информацию следующим образом:

makeHttpRequest().exceptionally(e ->
{
    throw new RuntimeException(e);
});

Смысл, я добавляю вручнуюexceptionally() после каждого HTTP-запроса в моем коде. Любые исключения, выдаваемые Джерси, заключены во вторичное исключение, которое ссылается на мой код. Полученная трассировка стека выглядит следующим образом:

java.lang.RuntimeException: javax.ws.rs.ForbiddenException: HTTP 403 Authentication Failed
    at my.user.code.Testcase.lambda$null$1(Testcase.java:25)
    at java.util.concurrent.CompletableFuture.uniExceptionally(CompletableFuture.java:870)
    ... 6 common frames omitted
Caused by: javax.ws.rs.ForbiddenException: HTTP 403 Authentication Failed
    at org.glassfish.jersey.client.JerseyInvocation.convertToException(JerseyInvocation.java:1083)
    at org.glassfish.jersey.client.JerseyInvocation.translate(JerseyInvocation.java:883)
    at org.glassfish.jersey.client.JerseyInvocation.lambda$invoke$1(JerseyInvocation.java:767)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:316)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:298)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:229)
    at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:414)
    at org.glassfish.jersey.client.JerseyInvocation.invoke(JerseyInvocation.java:765)
    at org.glassfish.jersey.client.JerseyInvocation$Builder.method(JerseyInvocation.java:456)
    at org.glassfish.jersey.client.JerseyCompletionStageRxInvoker.lambda$method$1(JerseyCompletionStageRxInvoker.java:70)
    at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1590)
    ... 3 common frames omitted

Мне не нравится этот подход, потому что он подвержен ошибкам и снижает читабельность кода. Если я ошибочно опущу это для какого-то HTTP-запроса, я получу неопределенную трассировку стека и потрачу много времени на ее отслеживание.

Кроме того, если я хочу спрятать этот трюк за служебным классом, я должен создать исключение внеCompletionStage; в противном случае служебный класс будет отображаться в трассировке стека вместо действительного сайта вызова. Создание исключения за пределамиCompletionStage это чрезвычайно дорого, потому что этот код выполняется, даже если асинхронный вызов не вызывает никаких исключений.

Мой вопрос

Существует ли надежный и простой в обслуживании подход для добавления контекстной информации к асинхронным вызовам?

В качестве альтернативы, существует ли эффективный подход для отслеживания трассировки стека до их источника без этой контекстной информации?

Ответы на вопрос(1)

Ваш ответ на вопрос