2011-06-07 73 views
5

一個堆棧跟蹤當您使用RMI在Java中異常的遠程堆棧跟蹤將預置當你收到它,有點像這樣:鍛造的Java

ERROR Client received error when doing stuff: 
myapp.FooBarException: bla 
at server.myMethod() 
at rmi.callHandler() // and now, on the next line comes the client 
at rmi.sendCall(); 
at client.doServerMethod() 
at Thread.run() 

這是怎麼樣的堆棧跟蹤「僞造」的做了什麼?


我想要什麼(除了只是反覆興趣)?那麼,它會幫助我,如果我能做到這一點:

outer() { 

    thread = new Thread(... 
     inner(); 
     // inner() throws 
     // RuntimeException 
     // at inner(); 
     // at Runnable.run(); 
     // at Thread.run(); 
     // at outer(); 
     // at lalalala(); 
     // ... 

).start(); 

    thread.join(); 

} 

而且讓這個在inner()拋出的異常具有outer()(和方法降低下來鏈)的堆棧跟蹤爲好,可以進行日誌記錄。

回答

9

這是種簡單:

的Throwable有方法getStackTrace()setStackTrace()

one of my projects(非開源,但也許我有一天打開遠程調用引擎):

/** 
    * Setzt den Stack-Trace zusammen. Das untere Ende (tiefer in der 
    * Aufrufhierarchie, am Anfang des Arrays/der Ausgabe) ist das, 
    * welches im Throwable schon drin ist, das obere Ende wird aus 
    * dem aktuellen Stack genommen. Dazwischen 
    * kommt ein "Remote-Aufruf-Markierer". 
    */ 

翻譯爲您提供方便:

融合了堆棧跟蹤。低端(呼叫層次結構中的較深處,在陣列的末尾 處)是堆棧中已經存在的內容,高端 將取自當前堆棧。在他們之間,我們將把一個 遠程呼叫標記

private void mergeStackTraces(Throwable error) 
    { 
     StackTraceElement[] currentStack = 
      new Throwable().getStackTrace(); 
     int currentStackLimit = 5; // TODO: raussuchen 
     StackTraceElement[] oldStack = 
      error.getStackTrace(); 
     StackTraceElement[] zusammen = 
      new StackTraceElement[currentStack.length - currentStackLimit + 
            oldStack.length + 1]; 
     System.arraycopy(oldStack, 0, zusammen, 0, oldStack.length); 
     zusammen[oldStack.length] = 
      new StackTraceElement("══════════════════════════", 
            "<remote call %" +callID+ ">", 
            "", -3); 
     System.arraycopy(currentStack, currentStackLimit, 
         zusammen, oldStack.length+1, 
         currentStack.length - currentStackLimit); 
     error.setStackTrace(zusammen); 
    } 

(在服務器端,我已經切斷的堆棧跟蹤不涉及到方法的調用零件本身,即一切相關的消息處理。)

這導致這樣的組合堆棧跟蹤:

java.lang.SecurityException: Das Passwort für Nutzer »Paul« ist falsch. 
     at de.fencing_game.db.userdb.Db4oUserDB.login(Db4oUserDB.java:304) 
     at de.fencing_game.server.impl.StandardServers$SSServer$1.run(StandardServers.java:316) 
     at de.fencing_game.server.impl.StandardServers$SSServer$1.run(StandardServers.java:314) 
     at java.security.AccessController.doPrivileged(Native Method) 
     at de.fencing_game.server.impl.StandardServers$SSServer.login(StandardServers.java:313) 
     at de.fencing_game.transport.server.ServerTransport$ConnectionInfo$4.login(ServerTransport.java:460) 
     at ══════════════════════════.<remote call %2>() 
     at $Proxy1.login(Unknown Source) 
     at de.fencing_game.gui.basics.LoginUtils.login(LoginUtils.java:80) 
     at de.fencing_game.gui.Lobby.connectTo(Lobby.java:302) 
     at de.fencing_game.gui.Lobby$20.run(Lobby.java:849) 
     at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:226) 
     at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:647) 
     at java.awt.EventQueue.access$000(EventQueue.java:96) 
     at java.awt.EventQueue$1.run(EventQueue.java:608) 
     at java.awt.EventQueue$1.run(EventQueue.java:606) 
     at java.security.AccessController.doPrivileged(Native Method) 
     at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:105) 
     at java.awt.EventQueue.dispatchEvent(EventQueue.java:617) 
     at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:275) 
     at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:200) 
     at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:190) 
     at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:185) 
     at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:177) 
     at java.awt.EventDispatchThread.run(EventDispatchThread.java:138) 

我想RMI系統確實非常相似(只是沒有══════════════════════════)的東西。


編輯: 爲了您的用例,你必須保存在內部線程啓動外線程的堆棧跟蹤信息,然後在run方法捕獲異常和外堆棧跟蹤追加到內部異常的堆棧跟蹤。不過,我真的會推薦使用某種類型的分隔符。

+0

我想內部異常有外附加的堆棧跟蹤,即使它被什麼東西深藏於內螺紋抓獲。我想這是不可能的,因爲我不能應用自定義異常。 – 2011-06-10 11:06:39

+0

您的捕獲代碼必須執行此操作(或者在拋出之前必須執行此操作,覆蓋fillInStackTrace或在構造函數中執行此操作)。這些都不適用於未知代碼拋出和捕獲的未知異常。 – 2011-06-10 12:40:25

+0

我喜歡你在一個代碼片段中有3種語言 - 英文,德文和Java :) – bacar 2012-08-17 18:29:20

2

您可以創建自定義異常,從一個異常中提取堆棧跟蹤,並通過setStackTrace()將其添加到另一個異常。

當你不想維護對由異常引起的硬引用時,對於做這樣的事情或維護堆棧跟蹤非常有用。將異常信息從服務器傳遞到客戶端時,這可能非常方便,因爲根本原因異常類可能不存在,從而導致序列化問題。

0

我想建議一種替代解決方案,這不是OP要求的解決方案,但對於一些有類似問題的人可能會更好。像我這樣的。

我建議在外部創建一個throwable,並從內部添加throwable作爲throwable來自外部的原因。這也將捕捉到線程切換的地方......這可能有助於避免混淆堆棧跟蹤。而且,線程信息可以存儲在這個在外部創建的throwable中,這可能會有更多幫助。

這是一些代碼。

public class StackCaptor { 
    public static Runnable capture(Runnable runnable) { 
     // Capture the stack 
     final Throwable prison = new Throwable(); 
     // Wrap run method to create a new throwable representing the creator of the original Runnable. 
     return new Runnable() { 
      @Override 
      public void run() { 
       try { 
        runnable.run(); 
       } catch (Throwable originalThrowable) { 
        RuntimeException callingThreadsException = new RuntimeException(originalThrowable); 
        callingThreadsException.setStackTrace(prison.getStackTrace()); 
        throw callingThreadsException; 
       } 
      } 
     }; 
    } 
} 

然後使用這樣的代碼:

// This code has not be compiled or tested... You may need to use your 
// smarts to get it working, but it should give you an idea. 
public void outer() { 
    Thread thread = new Thread(StackCaptor.capture(new Runnable() { 
     public void run() { throw new RuntimeException("my ex"); } 
    })); 
    thread.start(); 
}