Формирование трассировки стека в Java - PullRequest
7 голосов
/ 07 июня 2011

Когда вы используете RMI в 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() (и методы ниже по цепочке) в трассировке стека, для целей ведения журнала.

Ответы [ 3 ]

12 голосов
/ 07 июня 2011

Это довольно просто:

Throwable имеет методы getStackTrace() и setStackTrace().

Из одного из моих проектов (не с открытым исходным кодом, но, возможно, когда-нибудь я открою механизм удаленного вызова):

    /**
     * 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 перехватывает исключение и добавляет трассировку внешнего стека к трассировке стека внутреннего исключения.Я бы действительно рекомендовал установить какой-либо тип разделителя.

2 голосов
/ 07 июня 2011

Вы создаете настраиваемое исключение, которое извлекает трассировку стека из одного исключения и добавляет его к другому с помощью setStackTrace () .

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

0 голосов
/ 16 февраля 2018

Я хотел бы предложить альтернативное решение, которое не то, о чем просил ОП, но может быть лучше для некоторых людей, у которых очень похожий вопрос.Как и я.

Я предлагаю создать бросаемый объект во внешнем пространстве и добавить его в качестве причины броска из внешнего источника.Это также уловит момент, когда был сделан переключатель потока ... что может быть полезно, чтобы избежать путаницы в трассировке стека.Кроме того, информация о потоках может храниться в этом бросаемом объекте, созданном в external, который может помочь еще больше.

Вот некоторый код.

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();
}
...