API ...

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

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

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

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

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

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

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

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

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()&nbsp;вызовы. Поскольку поставщик создан Джерси, нет ссылки на код пользователя.

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

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

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

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

Смысл, я добавляю вручнуюexceptionally()&nbsp;после каждого 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&nbsp;это чрезвычайно дорого, потому что этот код выполняется, даже если асинхронный вызов не вызывает никаких исключений.

Мой вопрос

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

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