¿Cómo crear stacktraces asíncronos?

ACTUALIZAR: La última versión de Intellij IDEAimplementos exactamente lo que estoy buscando. La pregunta es cómo implementar esto fuera del IDE (para poder volcar los rastros de la pila asíncrona en los archivos de registro), idealmente sin el uso de un agente de instrumentación.

Desde que convertí mi aplicación de un modelo síncrono a asíncrono, tengo problemas para depurar fallas.

Cuando uso API síncronas, siempre encuentro mis clases en trazados de excepción para saber dónde comenzar a buscar si algo sale mal. Con las API asincrónicas, obtengo stacktraces que no hacen referencia a mis clases ni indican qué solicitud provocó la falla.

Te daré un ejemplo concreto, pero estoy interesado en una solución general a este tipo de problema.

Ejemplo concreto

Hago una solicitud HTTP usandoJersey:

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

dónderx() indica que la solicitud debe realizarse de forma asincrónica, devolviendo unCompletionStage<JsonNode> en vez de unaJsonNode directamente. Si esta llamada falla, obtengo este stacktrace:

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)

Aviso:

El stacktrace no hace referencia al código de usuario.El mensaje de excepción no contiene información contextual sobre la solicitud HTTP que activó el error (método HTTP, URI, etc.).

Como resultado, no tengo forma de rastrear la excepción hasta su origen.

¿Por qué está pasando esto?

Si cavas debajo del capó, descubrirás queJersey está invocando:

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

pararx() invocaciones Debido a que el proveedor está construido por Jersey, no hay ninguna referencia al código de usuario.

Lo que he intentado

Traté de presentar un informe de error contra Jetty por un ejemplo asincrónico no relacionado, y posteriormente fue rechazado por motivos de seguridad.

En cambio, he estado agregando información contextual de la siguiente manera:

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

Es decir, estoy agregando manualmenteexceptionally() después de cada solicitud HTTP en mi código. Cualquier excepción lanzada por Jersey está envuelta en una excepción secundaria que hace referencia a mi código. El stacktrace resultante se ve así:

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

No me gusta este enfoque porque es propenso a errores y disminuye la legibilidad del código. Si omito esto por error para alguna solicitud HTTP, terminaré con un vago stacktrace y pasaré mucho tiempo rastreando.

Además, si quiero ocultar este truco detrás de una clase de utilidad, entonces tengo que instanciar una excepción fuera de unCompletionStage; de lo contrario, la clase de utilidad se mostrará en el stacktrace en lugar del sitio de llamada real. Crear instancias de una excepción fuera de unCompletionStage es extremadamente costoso porque este código se ejecuta incluso si la llamada asíncrona nunca lanza una excepción.

Mi pregunta

¿Existe un enfoque robusto y fácil de mantener para agregar información contextual a las llamadas asincrónicas?

Alternativamente, ¿hay un enfoque eficiente para rastrear los stacktraces hasta su fuente sin esta información contextual?

Respuestas a la pregunta(1)

Su respuesta a la pregunta