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
это чрезвычайно дорого, потому что этот код выполняется, даже если асинхронный вызов не вызывает никаких исключений.
Существует ли надежный и простой в обслуживании подход для добавления контекстной информации к асинхронным вызовам?
В качестве альтернативы, существует ли эффективный подход для отслеживания трассировки стека до их источника без этой контекстной информации?