2016-09-26 6 views
6

Pour une meilleure mise au point, je voudrais souvent avoir:Comment générer dynamiquement un cadre de pile avec des informations de journal de débogage

Exception 
    at com.example.blah.Something.method() 
    at com.example.blah.Xyz.otherMethod() 
    at com.example.hello.World.foo() 
    at com.example.debug.version_3_8_0.debug_info_something.Hah.method() // synthetic method 
    at com.example.x.A.wrappingMethod() 

Le cadre de la pile de débogage comme indiqué ci-dessus serait généré dynamiquement, tout comme un java.lang.reflect.Proxy, à l'exception que je voudrais être en contrôle total du nom complet de la méthode entièrement qualifié qui finit sur le proxy.

Sur le site d'appel, je fais quelque chose de stupide et simple que cela:

public void wrappingMethod() { 
    run("com.example.debug.version_3_8_0.debug_info_something.Hah.method()",() -> { 
     World.foo(); 
    }); 
} 

Comme vous pouvez le voir, le wrappingMethod() est une vraie méthode qui se termine sur la trace de la pile, Hah.method() est une dynamique générée méthode, alors que World.foo() est encore une vraie méthode. Oui, je sais que cela pollue les traces de pile profondes déjà profondes. Ne t'en fais pas pour ça. J'ai mes raisons.

Existe-t-il une façon (simple) de faire ceci ou quelque chose de similaire à ce qui précède?

+1

Je ne suis pas très familier avec la zone, mais je sais que quelque chose comme ça peut être fait au niveau du bytecode, mais je ne suis pas sûr que ce soit possible dans la langue. –

+0

@ Meguy26: Je suis d'accord avec les solutions de niveau bytecode si elles peuvent être implémentées sans dépendance particulière en dehors du JDK. Bien, j'accepterais aussi des solutions de travail qui utilisent une dépendance comme Bytebuddy –

Répondre

8

Pas besoin de génération de code pour résoudre ce problème:

static void run(String name, Runnable runnable) { 
    try { 
    runnable.run(); 
    } catch (Throwable throwable) { 
    StackTraceElement[] stackTraceElements = throwable.getStackTrace(); 
    StackTraceElement[] currentStackTrace = new Throwable().getStackTrace(); 
    if (stackTraceElements != null && currentStackTrace != null) { // if disabled 
     int currentStackSize = currentStackStrace.length; 
     int currentFrame = stackTraceElements.length - currentStackSize - 1; 
     int methodIndex = name.lastIndexOf('.'); 
     int argumentIndex = name.indexOf('('); 
     stackTraceElements[currentFrame] = new StackTraceElement(
      name.substring(0, methodIndex), 
      name.substring(methodIndex + 1, argumentIndex), 
      null, // file name is optional 
      -1); // line number is optional 
     throwable.setStackTrace(stackTraceElements); 
    } 
    throw throwable; 
    } 
} 

Avec la génération de code, vous pouvez ajouter une méthode avec le nom, redéfinir le site d'appel dans la méthode, dérouler le cadre et appeler la méthode générée mais ce serait beaucoup plus de travail et ne serait jamais aussi stable. Cette stratégie est une approche plutôt courante dans les frameworks de test, we do it a lot in Mockito et d'autres utilitaires comme JRebel le font pour cacher leur magie en réécrivant des trames de pile d'exceptions. Lorsque Java 9 est utilisé, il serait plus efficace de faire de telles manipulations en utilisant Stack Walker API.

+0

* "Stack Walker" * ... vous voulez dire, plutôt que 'Stream.of (stackTraceElements)'? :) –

+1

Un avantage de l'API stack walker est sa performance car elle n'a pas besoin de collecter des informations sur la pile entière ce qui est plutôt coûteux. –

+0

Est-ce vrai? [J'ai déjà vu "marcher" dans l'API 'Files.walk()' et cela ne fonctionne pas comme prévu] (https://blog.jooq.org/2014/01/24/java-8 -friday-goodies-le-nouveau-nouveau-io-apis /) –