it-swarm.com.de

Was ist ein Stack-Trace und wie kann ich ihn zum Debuggen meiner Anwendungsfehler verwenden?

Wenn ich meine Anwendung ausführe, wird manchmal ein Fehler angezeigt, der wie folgt aussieht:

Exception in thread "main" Java.lang.NullPointerException
        at com.example.myproject.Book.getTitle(Book.Java:16)
        at com.example.myproject.Author.getBookTitles(Author.Java:25)
        at com.example.myproject.Bootstrap.main(Bootstrap.Java:14)

Die Leute haben dies als "Stack-Trace" bezeichnet. Was ist ein Stack-Trace? Was kann er mir über den Fehler in meinem Programm sagen?


Über diese Frage - ziemlich oft sehe ich eine Frage, bei der ein unerfahrener Programmierer "einen Fehler" bekommt, und sie fügen einfach ihre Stapelverfolgung und einen zufälligen Codeblock ein, ohne zu verstehen, was der Stapel ist trace ist oder wie sie es benutzen können. Diese Frage ist als Referenz für unerfahrene Programmierer gedacht, die Hilfe benötigen, um den Wert eines Stack-Trace zu verstehen.

608
Rob Hruska

In einfachen Worten ist ein Stack-Trace eine Liste der Methodenaufrufe, in deren Mitte die Anwendung eine Ausnahme ausgelöst hat.

Einfaches Beispiel

Anhand des in der Frage angegebenen Beispiels können wir genau bestimmen, wo die Ausnahme in der Anwendung ausgelöst wurde. Werfen wir einen Blick auf den Stack-Trace:

Exception in thread "main" Java.lang.NullPointerException
        at com.example.myproject.Book.getTitle(Book.Java:16)
        at com.example.myproject.Author.getBookTitles(Author.Java:25)
        at com.example.myproject.Bootstrap.main(Bootstrap.Java:14)

Dies ist eine sehr einfache Stapelverfolgung. Wenn wir am Anfang der Liste von "at ..." beginnen, können wir feststellen, wo unser Fehler aufgetreten ist. Was wir suchen, ist der oberste Methodenaufruf, der Teil unserer Anwendung ist. In diesem Fall ist es:

at com.example.myproject.Book.getTitle(Book.Java:16)

Um dies zu debuggen, können wir Book.Java öffnen und in der Zeile 16 nachsehen.

15   public String getTitle() {
16      System.out.println(title.toString());
17      return title;
18   }

Dies würde anzeigen, dass im obigen Code etwas (wahrscheinlich title) null ist.

Beispiel mit einer Ausnahmekette

Manchmal fangen Anwendungen eine Ausnahmebedingung ab und lösen sie erneut als Ursache für eine andere Ausnahmebedingung aus. Das sieht normalerweise so aus:

34   public void getBookIds(int id) {
35      try {
36         book.getId(id);    // this method it throws a NullPointerException on line 22
37      } catch (NullPointerException e) {
38         throw new IllegalStateException("A book has a null property", e)
39      }
40   }

Dadurch erhalten Sie möglicherweise eine Stapelablaufverfolgung, die wie folgt aussieht:

Exception in thread "main" Java.lang.IllegalStateException: A book has a null property
        at com.example.myproject.Author.getBookIds(Author.Java:38)
        at com.example.myproject.Bootstrap.main(Bootstrap.Java:14)
Caused by: Java.lang.NullPointerException
        at com.example.myproject.Book.getId(Book.Java:22)
        at com.example.myproject.Author.getBookIds(Author.Java:36)
        ... 1 more

Was ist anders an diesem ist die "Caused by". In Ausnahmefällen sind mehrere Abschnitte "Verursacht durch" vorhanden. In diesen Fällen möchten Sie in der Regel die "Grundursache" ermitteln, bei der es sich um einen der niedrigsten "Verursacht durch" -Abschnitte in der Stapelablaufverfolgung handelt. In unserem Fall ist es:

Caused by: Java.lang.NullPointerException <-- root cause
        at com.example.myproject.Book.getId(Book.Java:22) <-- important line

Mit dieser Ausnahme möchten wir noch einmal in der Zeile 22 von Book.Java nachsehen, was die NullPointerException hier verursachen könnte.

Erschreckenderes Beispiel mit Bibliothekscode

Normalerweise sind Stapelspuren viel komplexer als die beiden obigen Beispiele. Hier ist ein Beispiel (es ist lang, zeigt jedoch mehrere Ebenen von verketteten Ausnahmen):

javax.servlet.ServletException: Something bad happened
    at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.Java:60)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.Java:1157)
    at com.example.myproject.ExceptionHandlerFilter.doFilter(ExceptionHandlerFilter.Java:28)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.Java:1157)
    at com.example.myproject.OutputBufferFilter.doFilter(OutputBufferFilter.Java:33)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.Java:1157)
    at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.Java:388)
    at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.Java:216)
    at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.Java:182)
    at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.Java:765)
    at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.Java:418)
    at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.Java:152)
    at org.mortbay.jetty.Server.handle(Server.Java:326)
    at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.Java:542)
    at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.Java:943)
    at org.mortbay.jetty.HttpParser.parseNext(HttpParser.Java:756)
    at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.Java:218)
    at org.mortbay.jetty.HttpConnection.handle(HttpConnection.Java:404)
    at org.mortbay.jetty.bio.SocketConnector$Connection.run(SocketConnector.Java:228)
    at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.Java:582)
Caused by: com.example.myproject.MyProjectServletException
    at com.example.myproject.MyServlet.doPost(MyServlet.Java:169)
    at javax.servlet.http.HttpServlet.service(HttpServlet.Java:727)
    at javax.servlet.http.HttpServlet.service(HttpServlet.Java:820)
    at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.Java:511)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.Java:1166)
    at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.Java:30)
    ... 27 more
Caused by: org.hibernate.exception.ConstraintViolationException: could not insert: [com.example.myproject.MyEntity]
    at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.Java:96)
    at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.Java:66)
    at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.Java:64)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.Java:2329)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.Java:2822)
    at org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.Java:71)
    at org.hibernate.engine.ActionQueue.execute(ActionQueue.Java:268)
    at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.Java:321)
    at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.Java:204)
    at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.Java:130)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.Java:210)
    at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.Java:56)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.Java:195)
    at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.Java:50)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.Java:93)
    at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.Java:705)
    at org.hibernate.impl.SessionImpl.save(SessionImpl.Java:693)
    at org.hibernate.impl.SessionImpl.save(SessionImpl.Java:689)
    at Sun.reflect.GeneratedMethodAccessor5.invoke(Unknown Source)
    at Sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.Java:25)
    at Java.lang.reflect.Method.invoke(Method.Java:597)
    at org.hibernate.context.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.Java:344)
    at $Proxy19.save(Unknown Source)
    at com.example.myproject.MyEntityService.save(MyEntityService.Java:59) <-- relevant call (see notes below)
    at com.example.myproject.MyServlet.doPost(MyServlet.Java:164)
    ... 32 more
Caused by: Java.sql.SQLException: Violation of unique constraint MY_ENTITY_UK_1: duplicate value(s) for column(s) MY_COLUMN in statement [...]
    at org.hsqldb.jdbc.Util.throwError(Unknown Source)
    at org.hsqldb.jdbc.jdbcPreparedStatement.executeUpdate(Unknown Source)
    at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.Java:105)
    at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.Java:57)
    ... 54 more

In diesem Beispiel gibt es noch viel mehr. Was uns am meisten beschäftigt, ist die Suche nach Methoden, die aus unserem Code stammen und die im com.example.myproject -Paket enthalten sind. Im zweiten Beispiel (oben) möchten wir zuerst nach der Grundursache suchen, nämlich:

Caused by: Java.sql.SQLException

Alle Methodenaufrufe darunter sind jedoch Bibliothekscode. Also gehen wir zum "Verursacht durch" darüber und suchen nach dem ersten Methodenaufruf, der aus unserem Code stammt:

at com.example.myproject.MyEntityService.save(MyEntityService.Java:59)

Wie in den vorherigen Beispielen sollten wir uns MyEntityService.Java in der Zeile 59 ansehen, da hier dieser Fehler entstanden ist (dies ist ein bisschen offensichtlich, was schief gelaufen ist, da die SQLException den Fehler angibt, aber das Debugging-Verfahren dies ist was wir suchen).

556
Rob Hruska

Ich poste diese Antwort, damit die oberste Antwort (nach Aktivität sortiert) nicht einfach falsch ist.

Was ist ein Stacktrace?

Ein Stacktrace ist ein sehr hilfreiches Debugging-Tool. Es zeigt den Aufrufstapel (dh den Stapel von Funktionen, die bis zu diesem Zeitpunkt aufgerufen wurden), als eine nicht erfasste Ausnahme ausgelöst wurde (oder den Zeitpunkt, zu dem der Stacktrace manuell generiert wurde). Dies ist sehr nützlich, da es Ihnen nicht nur zeigt, wo der Fehler aufgetreten ist, sondern auch, wie das Programm an dieser Stelle des Codes gelandet ist. Dies führt zur nächsten Frage:

Was ist eine Ausnahme?

Eine Ausnahme ist das, was die Laufzeitumgebung verwendet, um Ihnen mitzuteilen, dass ein Fehler aufgetreten ist. Beliebte Beispiele sind NullPointerException, IndexOutOfBoundsException oder ArithmeticException. Jede dieser Ursachen tritt auf, wenn Sie versuchen, etwas zu tun, was nicht möglich ist. Beispielsweise wird eine NullPointerException ausgelöst, wenn Sie versuchen, ein Null-Objekt dereferenzieren:

Object a = null;
a.toString();                 //this line throws a NullPointerException

Object[] b = new Object[5];
System.out.println(b[10]);    //this line throws an IndexOutOfBoundsException,
                              //because b is only 5 elements long
int ia = 5;
int ib = 0;
ia = ia/ib;                   //this line throws an  ArithmeticException with the 
                              //message "/ by 0", because you are trying to
                              //divide by 0, which is not possible.

Wie soll ich mit Stacktraces/Exceptions umgehen?

Finden Sie zunächst heraus, was die Ausnahme verursacht. Versuchen Sie, den Namen der Ausnahme zu googeln, um herauszufinden, was die Ursache für diese Ausnahme ist. Meistens wird es durch falschen Code verursacht. In den obigen Beispielen werden alle Ausnahmen durch falschen Code verursacht. Für das NullPointerException-Beispiel können Sie also sicherstellen, dass a zu diesem Zeitpunkt niemals null ist. Sie können zum Beispiel a initialisieren oder einen Check wie diesen einfügen:

if (a!=null) {
    a.toString();
}

Auf diese Weise wird die fehlerhafte Zeile nicht ausgeführt, wenn a==null. Gleiches gilt für die anderen Beispiele.

Manchmal können Sie nicht sicherstellen, dass Sie keine Ausnahme erhalten. Wenn Sie beispielsweise eine Netzwerkverbindung in Ihrem Programm verwenden, können Sie den Computer nicht daran hindern, seine Internetverbindung zu verlieren (z. B. können Sie den Benutzer nicht daran hindern, die Netzwerkverbindung des Computers zu trennen). In diesem Fall wird die Netzwerkbibliothek wahrscheinlich eine Ausnahme auslösen. Jetzt sollten Sie die Ausnahme abfangen und handle it. Dies bedeutet, dass Sie im Beispiel mit der Netzwerkverbindung versuchen sollten, die Verbindung erneut zu öffnen oder den Benutzer oder ähnliches zu benachrichtigen. Wenn Sie catch verwenden, fangen Sie immer nur die Ausnahme ab, die Sie abfangen möchten. Verwenden Sie keine allgemeinen catch-Anweisungen wie catch (Exception e), die alle Ausnahmen abfangen würden. Dies ist sehr wichtig, da Sie sonst möglicherweise versehentlich die falsche Ausnahme abfangen und falsch reagieren.

try {
    Socket x = new Socket("1.1.1.1", 6789);
    x.getInputStream().read()
} catch (IOException e) {
    System.err.println("Connection could not be established, please try again later!")
}

Warum sollte ich catch (Exception e) nicht verwenden?

Lassen Sie uns an einem kleinen Beispiel zeigen, warum Sie nicht alle Ausnahmen abfangen sollten:

int mult(Integer a,Integer b) {
    try {
        int result = a/b
        return result;
    } catch (Exception e) {
        System.err.println("Error: Division by zero!");
        return 0;
    }
}

Mit diesem Code wird versucht, das ArithmeticException abzufangen, das durch eine mögliche Division durch 0 verursacht wird. Es wird jedoch auch ein mögliches NullPointerException abgefangen, das ausgelöst wird, wenn a oder b sind null. Dies bedeutet, dass Sie möglicherweise ein NullPointerException erhalten, es jedoch als ArithmeticException behandeln und wahrscheinlich das Falsche tun. Im besten Fall vermissen Sie immer noch, dass es eine NullPointerException gab. Solche Dinge erschweren das Debuggen erheblich. Tun Sie das also nicht.

TLDR

  1. Finden Sie heraus, was die Ursache der Ausnahme ist, und beheben Sie sie, damit die Ausnahme überhaupt nicht ausgelöst wird.
  2. Wenn 1. nicht möglich ist, fangen Sie die spezifische Ausnahme ab und behandeln Sie sie.

    • Fügen Sie niemals einfach einen Versuch/Fang hinzu und ignorieren Sie dann einfach die Ausnahme! Mach das nicht!
    • Verwenden Sie niemals catch (Exception e), fangen Sie immer bestimmte Ausnahmen ab. Das erspart Ihnen viele Kopfschmerzen.
73
Dakkaron

Hinzufügen zu dem, was Rob erwähnt hat. Das Festlegen von Haltepunkten in Ihrer Anwendung ermöglicht die schrittweise Verarbeitung des Stapels. Auf diese Weise kann der Entwickler mithilfe des Debuggers feststellen, an welchem ​​Punkt die Methode etwas Unerwartetes ausführt.

Da Rob das NullPointerException (NPE) zur Veranschaulichung von häufig auftretenden Problemen verwendet hat, können wir dieses Problem auf folgende Weise beheben:

wenn wir eine Methode haben, die folgende Parameter akzeptiert: void (String firstName)

In unserem Code möchten wir auswerten, dass firstName einen Wert enthält. Dies geschieht folgendermaßen: if(firstName == null || firstName.equals("")) return;

Das oben Gesagte hindert uns daran, firstName als unsicheren Parameter zu verwenden. Daher können wir durch Nullprüfungen vor der Verarbeitung sicherstellen, dass unser Code ordnungsgemäß ausgeführt wird. Um ein Beispiel zu erweitern, das ein Objekt mit Methoden verwendet, sehen wir uns Folgendes an:

if(dog == null || dog.firstName == null) return;

Das Obige ist die richtige Reihenfolge, um nach Nullen zu suchen. Wir beginnen mit dem Basisobjekt, in diesem Fall dog, und gehen dann den Baum der Möglichkeiten entlang, um sicherzustellen, dass vor der Verarbeitung alles gültig ist. Wenn die Reihenfolge umgekehrt würde, könnte möglicherweise eine NPE ausgelöst werden, und unser Programm würde abstürzen.

21
Woot4Moo

Es gibt eine weitere Stacktrace-Funktion der Throwable-Familie - die Möglichkeit, Stacktrace-Informationen zu manipulieren .

Standardverhalten:

package test.stack.trace;

public class SomeClass {

    public void methodA() {
        methodB();
    }

    public void methodB() {
        methodC();
    }

    public void methodC() {
        throw new RuntimeException();
    }

    public static void main(String[] args) {
        new SomeClass().methodA();
    }
}

Stack-Trace:

Exception in thread "main" Java.lang.RuntimeException
    at test.stack.trace.SomeClass.methodC(SomeClass.Java:18)
    at test.stack.trace.SomeClass.methodB(SomeClass.Java:13)
    at test.stack.trace.SomeClass.methodA(SomeClass.Java:9)
    at test.stack.trace.SomeClass.main(SomeClass.Java:27)

Manipulierter Stack-Trace:

package test.stack.trace;

public class SomeClass {

    ...

    public void methodC() {
        RuntimeException e = new RuntimeException();
        e.setStackTrace(new StackTraceElement[]{
                new StackTraceElement("OtherClass", "methodX", "String.Java", 99),
                new StackTraceElement("OtherClass", "methodY", "String.Java", 55)
        });
        throw e;
    }

    public static void main(String[] args) {
        new SomeClass().methodA();
    }
}

Stack-Trace:

Exception in thread "main" Java.lang.RuntimeException
    at OtherClass.methodX(String.Java:99)
    at OtherClass.methodY(String.Java:55)
15
przemek hertel

Um den Namen zu verstehen : Ein Stack-Trace ist eine Liste von Ausnahmen (oder Sie können eine Liste von "Ursache von" sagen), von der obersten Ausnahme (zB Service Layer Exception) bis zur tiefsten (zB Database Exception). Genau wie der Grund, warum wir es 'Stapel' nennen, ist, dass der Stapel First in Last out (FILO) ist, die tiefste Ausnahme war am Anfang aufgetreten, dann wurde eine Ausnahmekette mit einer Reihe von Konsequenzen erzeugt, die Oberflächenausnahme war die letzte eines passierte rechtzeitig, aber wir sehen es an erster Stelle.

Taste 1 : Eine knifflige und wichtige Sache, die hier verstanden werden muss, ist: Die tiefste Ursache ist möglicherweise nicht die "Grundursache", denn wenn Sie welche schreiben "Schlechter Code", es kann eine Ausnahme geben, unter der die Ebene tiefer liegt. Beispielsweise kann eine fehlerhafte SQL-Abfrage dazu führen, dass die SQLServerException-Verbindung im unteren Bereich zurückgesetzt wird, anstatt dass ein Syndax-Fehler auftritt, der sich möglicherweise direkt in der Mitte des Stapels befindet.

-> Finde die Ursache in der Mitte ist dein Job. enter image description here

Taste 2 : In jedem "Cause by" -Block befindet sich eine weitere schwierige, aber wichtige Sache. Die erste Zeile war die tiefste Ebene und der erste Platz für diesen Block. Zum Beispiel,

Exception in thread "main" Java.lang.NullPointerException
        at com.example.myproject.Book.getTitle(Book.Java:16)
           at com.example.myproject.Author.getBookTitles(Author.Java:25)
               at com.example.myproject.Bootstrap.main(Bootstrap.Java:14)

Book.Java:16 wurde von Auther.Java:25 aufgerufen, was von Bootstrap.Java:14 aufgerufen wurde. Book.Java:16 war die Hauptursache. Hier ein Diagramm anhängen und den Trace-Stack in chronologischer Reihenfolge sortieren. enter image description here

15
Kevin Li

Nur um die anderen Beispiele zu ergänzen, gibt es innere (verschachtelte) Klassen, die mit dem Zeichen $ angezeigt werden. Zum Beispiel:

public class Test {

    private static void privateMethod() {
        throw new RuntimeException();
    }

    public static void main(String[] args) throws Exception {
        Runnable runnable = new Runnable() {
            @Override public void run() {
                privateMethod();
            }
        };
        runnable.run();
    }
}

Ergibt diesen Stack-Trace:

Exception in thread "main" Java.lang.RuntimeException
        at Test.privateMethod(Test.Java:4)
        at Test.access$000(Test.Java:1)
        at Test$1.run(Test.Java:10)
        at Test.main(Test.Java:13)
8
Eugene S

Die anderen Beiträge beschreiben, was ein Stack-Trace ist, aber es kann immer noch schwierig sein, mit ihm zu arbeiten.

Wenn Sie einen Stack-Trace erhalten und die Ursache der Ausnahme nachverfolgen möchten, ist die Verwendung der Java-Stack-Trace-Konsole in ein guter Ausgangspunkt für das Verständnis Eclipse. Wenn Sie ein anderes IDE verwenden, gibt es möglicherweise eine ähnliche Funktion, aber in dieser Antwort geht es um Eclipse.

Stellen Sie zunächst sicher, dass auf alle Ihre Java Quellen in einem Eclipse-Projekt zugegriffen werden kann.

Klicken Sie dann in der Perspektive Java auf die Registerkarte Konsole (normalerweise unter den Boden). Wenn die Konsolenansicht nicht sichtbar ist, gehen Sie zur Menüoption Fenster -> Ansicht anzeigen und wählen Sie Konsole .

Klicken Sie dann im Konsolenfenster auf die folgende Schaltfläche (rechts)

Consoles button

wählen Sie dann Java Stack Trace Console aus der Dropdown-Liste.

Fügen Sie Ihren Stack-Trace in die Konsole ein. Daraufhin wird eine Liste mit Links zu Ihrem Quellcode und allen anderen verfügbaren Quellcodes angezeigt.

Folgendes sehen Sie möglicherweise (Bild aus der Eclipse-Dokumentation):

Diagram from Eclipse documentation

Der zuletzt ausgeführte Methodenaufruf ist der Anfang des Stapels, dh die oberste Zeile (ohne den Nachrichtentext). Den Stapel runter zu gehen, geht in der Zeit zurück. Die zweite Zeile ist die Methode, die die erste Zeile usw. aufruft.

Wenn Sie Open-Source-Software verwenden, müssen Sie möglicherweise die Quellen herunterladen und an Ihr Projekt anhängen, wenn Sie sie untersuchen möchten. Laden Sie die Quelldateien herunter, und öffnen Sie in Ihrem Projekt den Ordner Referenzierte Bibliotheken , um die Datei für Ihr Open-Source-Modul (die mit den Klassendateien) zu finden. Klicken Sie dann mit der rechten Maustaste, wählen Sie Eigenschaften und hängen Sie das Quellglas an.

5
rghome