Die Gewinnung nützlicher Informationen aus Ihren Protokolldateien ist für den Erfolg Ihrer Java-Anwendung entscheidend. Protokolldaten geben Ihnen wertvolle Einblicke in die Leistung, Stabilität und Benutzerfreundlichkeit Ihrer Anwendung.

Die Analyse von Protokolldaten mag mühsam erscheinen, muss es aber nicht. Es gibt eine Vielzahl von Tools zum Lesen, Analysieren und Konsolidieren von Protokolldaten. Einfache Befehlszeilentools wie grep, uniq und sort können nützliche Informationen aus Protokolldateien kombinieren und extrahieren. Fortschrittlichere Protokollparser wie Logstash oder Fluentd können Schlüsseldaten aus Ihren Protokollen in leicht durchsuchbare Token extrahieren. Cloud-basierte Protokollierungsdienste wie SolarWinds® Loggly® speichern Ihre Protokolldaten für Sie und bieten ausgefeilte Analysefunktionen, so dass Sie die Protokolle nicht mehr selbst pflegen müssen.

In diesem Abschnitt werden einige Methoden und Tools zur Analyse von Protokollen mit dem Ziel der Verbesserung Ihrer Anwendungen vorgestellt.

Ermitteln der häufigsten Ausnahmen

Das Ermitteln der häufigsten Ausnahmen kann Ihnen dabei helfen, Bereiche mit schlechter Leistung in Ihrer Java-Anwendung zu identifizieren. Die meisten Logging-Frameworks zeichnen den Typ der Ausnahme, die Ausnahmemeldung und die Methode, in der die Ausnahme auftrat, auf. Mit Log4j sieht ein Ausnahmeprotokoll ähnlich aus wie das folgende.

09:54:44.565 ERROR Log4jTest.MathClass - java.lang.ArithmeticException: / by zero10:00:10.157 ERROR Log4jTest.Log4jTest - java.io.FileNotFoundException: myFile (No such file or directory)10:00:10.157 ERROR Log4jTest.Log4jTest - java.io.FileNotFoundException: newFile (No such file or directory)

Wenn Ihre Ausgabe Stack Traces enthält, lesen Sie den Abschnitt Analysieren mehrzeiliger Stack Traces in diesem Handbuch.

Beginnen wir mit einem einfachen Beispiel, bei dem das beliebte GNU-Tool grep verwendet wird. Dann zeigen wir, wie ein Protokollverwaltungstool es noch einfacher machen kann.

Ausnahmen nach Typ mit grep finden

Der folgende Unix-Befehl findet Ausnahmen, extrahiert den Ausnahmetyp und zählt die Anzahl der Vorkommen. grep ist ein beliebtes Befehlszeilen-Tool, das Mustervergleiche durchführt, während uniq und sort die Ergebnisse gruppieren bzw. sortieren:

$ grep -o "\w*Exception" myLog.log | sort -r | uniq -c

In diesem Beispiel passt der reguläre Ausdruck \w*Exception auf jedes Wort, das mit „Ausnahme“ endet. Das Flag -o weist grep an, nur die Teile der Ausgabe auszugeben, die mit dem Suchstring übereinstimmen. sort -r sortiert das Ergebnis in umgekehrter Reihenfolge, während uniq -c die Ergebnisse gruppiert und die Anzahl der Vorkommen zählt. Als Ergebnis erhalten wir eine Zählung der Ausnahmen nach Typ.

2 FileNotFoundException1 ArithmeticException

Wir können grep auch verwenden, um das Protokoll nach jeder spezifischen Instanz dieser Ausnahmen zu durchsuchen.

$ grep ArithmeticException myLog.log09:54:44.565 ERROR Log4jTest.Log4jTest - java.lang.ArithmeticException: / by zero

Dies gibt jede Zeile zurück, die eine Instanz der Suchzeichenkette ArithmeticException enthält, wobei die Suchzeichenkette selbst in der Ausgabe hervorgehoben wird.

Finden von Ausnahmen nach Klasse mit Grep

Sie können auch nach Ausnahmen nach der Klasse suchen, in der sie aufgetreten sind. Für einzeilige Protokollereignisse gruppiert der folgende Befehl die Anzahl der Ausnahmen nach Klasse und Typ. grep extrahiert den Klassennamen, indem es nach einem Wort, gefolgt von einem bestimmten Zeichen, sucht (in diesem Beispiel wird ein Bindestrich verwendet, obwohl jedes Zeichen verwendet werden kann, solange es für den Eintrag eindeutig ist). Dieses Zeichen ist zwar nicht unbedingt erforderlich, hilft aber beim Auffinden des Klassennamens im Protokolleintrag. Mit dem Operator OR (gekennzeichnet durch |) extrahiert grep auch den Namen der Ausnahme, indem es nach einem Wort sucht, das die Zeichenfolge „Exception“ enthält.

sed ist ein weiteres Befehlszeilendienstprogramm, mit dem die Ausgabe von grep formatiert werden kann. In diesem Beispiel entfernt sed das Sonderzeichen aus unserer Ausgabe sowie alle Zeilenumbrüche. Die Ausgabe wird dann über die Pipeline an die Befehle uniq und sort weitergeleitet, um die Ergebnisse zu gruppieren bzw. zu sortieren.

$ grep -o "w* -|\w*Exception" myLog.log | sed 'N; s/ -n/ /' | sort -r | uniq -c

Das Ergebnis bringt uns den Hauptproblembereichen näher:

2 Log4jTest FileNotFoundException1 MathClass ArithmeticException

Mit diesen Ergebnissen können wir grep verwenden, um weitere Details über Probleme zu finden, die in bestimmten Klassen auftreten. Der folgende Befehl ruft beispielsweise Ausnahmen ab, die in der Klasse MathClass aufgetreten sind.

$ grep -e "MathClass.*Exception" myLog.log09:54:44.565 ERROR Log4jtest.MathClass - java.lang.ArithmeticException: / by zero

Verwendung einer Protokollverwaltungslösung

Die meisten Protokollverwaltungslösungen bieten Möglichkeiten zur Gruppierung und Suche von Protokolleinträgen auf der Grundlage von Protokolltyp, Meldungsinhalt, Klasse, Methode und Thread. Wenn Ihre Protokolle bereits geparst und gespeichert werden, können viele Lösungen Ausnahmen grafisch darstellen und nach der Anzahl des Auftretens sortieren. In Loggly ist dies eine Point-and-Click-Operation, so dass Sie keine komplizierten Grep-Befehle auswendig lernen müssen. Loggly indiziert auch die Log-Einträge, was die Suche und Zählung viel schneller macht als grep. Zum Beispiel können wir den Feld-Explorer verwenden, um eine Liste von Ausnahmen und deren Häufigkeit zu sehen, und dann auf eine Ausnahme klicken, um alle relevanten Protokolle zu sehen.

Den Loggly Feld-Explorer verwenden, um schnell Protokolle nach Ausnahmetyp zu finden.

Loggly bietet auch Visualisierungswerkzeuge, die informativer sein können als die textbasierte Ausgabe von grep. Diagramme wie dieses können Ihnen helfen, Ressourcen für Entwicklungssprints oder für Patches nach einer neuen Version zu priorisieren.

Darstellung von Ausnahmen nach Häufigkeit in Loggly.

Produktionsprobleme debuggen

Wenn es um Produktionsprobleme geht, ist Zeit das A und O. Ein kritischer Fehler in Ihrer Anwendung wird nicht nur Ihre Benutzer unzufrieden machen, sondern auch den Umsatz senken und das Vertrauen in Ihre Anwendung oder Ihren Service verringern.

In den meisten Fällen kann die Lösung eines Problems in drei Hauptschritte unterteilt werden:

  1. Informationen über das Problem sammeln
  2. Die Ursache des Problems identifizieren
  3. Eine Lösung finden und verhindern, dass das Problem wieder auftritt

Informationen über das Problem sammeln

Der erste Schritt besteht darin, Informationen über das Problem zu sammeln. Sammeln Sie so viele Informationen wie möglich – Screenshots, Absturzberichte, Protokolle, Links (für Webdienste) usw. -, um mögliche Ursachen einzugrenzen. Sie möchten, dass der Benutzer, bei dem das Problem auftrat, detaillierte Informationen über das Ereignis liefert, einschließlich: wann und wo im Programm das Problem auftrat, seine oder ihre Aktionen, die zu dem Problem führten, die Betriebsumgebung und jegliches seltsame oder ungewöhnliche Verhalten des Programms vor und nach dem Auftreten des Problems.

Danach können Sie mit dem Sammeln von Protokollinformationen beginnen. Wenn es sich bei Ihrer Anwendung um einen gehosteten Dienst handelt, beginnen Sie mit dem Abrufen von Protokollen von den Web- und Anwendungsservern. Handelt es sich bei Ihrer Anwendung um ein verteiltes Softwarepaket, bitten Sie den Benutzer, die Protokolldaten in seinen Fehlerbericht aufzunehmen. Wenn Ihre Anwendung Protokollereignisse an einen zentralen Protokollserver sendet, sind Ihre Protokolle sofort verfügbar.

Wenn Sie eine angemessene Menge an Daten über das Problem haben, können Sie damit beginnen, es im Code aufzuspüren.

Identifizieren Sie die Ursache des Problems

Nach dem Sammeln von Informationen über das Problem besteht der nächste Schritt darin, seine Ursache zu identifizieren. Das Reproduzieren eines Fehlers in einer Entwicklungsumgebung ist eine der einfachsten Möglichkeiten, seine Existenz zu bestätigen, aber es kann zeitaufwendig sein und funktioniert nicht in allen Fällen. Ein gründlicher Satz von Protokollen wird Sie direkt zur Quelle des Problems führen und Ihnen Zeit und Frustration ersparen.

Ein Fehlerbericht wird Ihnen eine allgemeine Vorstellung davon geben, was das Problem ist und wo es aufgetreten ist. Mit dem Protokollverwaltungstool Ihrer Wahl können Sie Ihre Suche auf einen kleineren Bereich von Protokolleinträgen eingrenzen, indem Sie nach einem eindeutigen Daten-Token wie einem Benutzernamen, einer Sitzungs-ID oder einem Nachrichtentext suchen.

Lassen Sie uns ein Beispielszenario durchspielen, um zu zeigen, wie man ein System debuggt. Stellen Sie sich vor, wir haben eine webbasierte Schnittstelle für die Fernanmeldung bei einer Website. Die Webseite hat einen Anmeldebildschirm, der eine grundlegende Authentifizierung mit einem Benutzernamen und einem Passwort durchführt. Die Benutzer haben gemeldet, dass sie sich nicht bei der Website anmelden können. Die Webseite akzeptiert ihre Eingaben, schlägt dann aber mit einer allgemeinen Fehlermeldung fehl.

Eine Beispiel-Webseite, die nach einem Anmeldeversuch einen Fehler meldet.

Diese Meldung gibt uns nicht viel mehr Informationen als einen allgemeinen Hinweis auf den Schweregrad des Protokolls. Die Suche in einem Protokoll-Manager nach Einträgen mit „Severe“ im Level oder in der Meldung könnte zu Hunderten von Ergebnissen führen, ohne dass garantiert werden kann, dass auch nur eines davon mit dem vorliegenden Problem in Verbindung steht. Glücklicherweise hat unser Logger auch den Benutzernamen des Benutzers aufgezeichnet, bei dem der Fehler auftrat, so dass wir nach dem Benutzernamen „admin“ filtern können.

Betrachten einer Java-Ausnahme in Loggly.

Wenn wir einen Drilldown durchführen, sehen wir, dass die Ursache des Fehlers eine fehlende oder ungültige Tabelle ist. Dies ist ein ernstes Problem, da es auf ein versehentliches Löschen oder eine Beschädigung der Datenbank hindeuten könnte.

ERROR: Exception for user admincom.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'test_schema.users' doesn't exist...com.mysql.jdbc.StatementImpl.executeQuery(StatementImpl.java:1651) at TestApplication.Test.doPost(Test.java:33) at javax.servlet.http.HttpServlet.service(HttpServlet.java:646) at javax.servlet.http.HttpServlet.service(HttpServlet.java:727) at...

Wenn wir uns den Stack-Trace ansehen, sehen wir, dass der Dienst in Zeile 33 von Test.java fehlgeschlagen ist. Diese Zeile besteht aus einer SQL-Anweisung, die Daten über einen Benutzer aus der Tabelle test_schema.users abruft.

rs = stmt.executeQuery("SELECT * FROM test_schema.users WHERE username = " + username + " AND Password = " + password);

Wenn wir versuchen, diese Abfrage in einem SQL-Frontend auszuführen, stellen wir fest, dass die Tabelle „test_schema.users“ nicht existiert. Die Datenbank hat jedoch eine Tabelle „test_schema.user“. Irgendwann könnte ein Entwickler versehentlich den falschen Tabellennamen eingegeben und das überarbeitete Formular in die Produktion übertragen haben. Jetzt wissen wir, was das Problem ist und wo es in unserem Code auftaucht.

Mit SolarWinds Loggly können Sie Probleme debuggen und die Grundursache finden, indem Sie Fehler durch Ihren Anwendungsstapel und über mehrere Log-Ereignisse hinweg verfolgen und sogar die relevante Codezeile in GitHub lokalisieren.

Das Problem beheben und verhindern, dass es wieder auftritt

Nun, da wir das Problem und seine Ursache identifiziert haben, besteht der letzte Schritt darin, es zu lösen. Unser Login-Beispiel war ein übertriebener Fall mit einer relativ einfachen Lösung, aber Sie können auf kompliziertere Fehler stoßen, die in verschiedenen Bereichen Ihrer Anwendung liegen. Bevor Sie sich auf die schnelle und schmutzige Lösung stürzen, sollten Sie sorgfältig abwägen, wie sich Ihre Änderung auf die Anwendung auswirken wird. Ist dies wirklich die beste Lösung für das Problem? Ist es möglich, dass Ihre Änderung eine andere Komponente beeinträchtigt? Wird diese Lösung die Einführung neuer Korrekturen oder Funktionen in der Zukunft erschweren? Wird diese Lösung auch verhindern, dass später ähnliche Probleme auftreten?

Was wäre zum Beispiel, wenn zwei verschiedene Benutzer zwei verschiedene Konten mit demselben Benutzernamen und Kennwort anlegen würden? Sie könnten einen eindeutigen Benutzernamen für alle Benutzer erzwingen, aber wie würde sich das auf Ihre Datenbankstruktur, Ihren Authentifizierungscode und Ihre bestehende Benutzerbasis auswirken? Sie könnten ein neues eindeutiges Feld hinzufügen, z. B. eine E-Mail-Adresse, und es zur Pflicht machen, aber was ist, wenn einige Ihrer derzeitigen Benutzer keine E-Mail-Adressen angegeben haben? Was ist, wenn einige E-Mail-Adressen über mehrere Konten verteilt sind? Wie werden sich diese neuen Regeln auf Ihre Registrierungs-, Such- und Verwaltungsseiten auswirken?

Sie sollten auch die Zeitspanne zwischen der Erstellung einer Korrektur und ihrer Bereitstellung berücksichtigen. In einer Unternehmensumgebung muss Ihr Code von anderen Entwicklern geprüft, in die Codebasis integriert, erstellt, von der Qualitätssicherung getestet, bereitgestellt und möglicherweise noch einige weitere Schritte durchlaufen werden, bevor er in Produktion geht. Möglicherweise gibt es in Ihrem Unternehmen spezielle Protokolle für kritische oder zeitkritische Bugs, die Sie befolgen müssen. Sobald Sie eine Lösung gefunden haben, sollten Sie diese in Ihre Dokumentation aufnehmen, ebenso wie alle sinnvollen Umgehungsmöglichkeiten für das Problem. Auf diese Weise können Ihre Kunden Ihr Produkt weiterhin nutzen, und Ihr Support-Team kann die Anzahl der doppelten Fehlerberichte reduzieren, während der Fix seinen Weg in die Produktion findet.

Nach der Bereitstellung des Fixes überwachen Sie Ihre Produktionsanwendung weiter, um zu überprüfen, ob das Problem behoben wurde. Nach der Bereitstellung des Fixes für das Datenbankproblem im obigen Beispiel sollten zum Beispiel keine Ereignisse mit „Tabelle ‚test_schema.users‘ existiert nicht“ mehr auftreten. Wenn die Fehler weiterhin auftreten oder wenn neue Fehler auftreten, wissen wir, dass die Korrektur nicht funktioniert hat. Im Idealfall sehen wir ein Muster wie im folgenden Screenshot, bei dem die Anzahl der Fehler unmittelbar nach dem Einspielen des Patches auf Null sinkt.

Die Anzahl der Protokolle, die einen Fehler in Loggly enthalten.

Weitere Debugging-Tools für die Produktion

Während die Protokollierung die bewährte Methode zur Fehlerbehebung und zum Debuggen von Anwendungen ist, können diese Tools Ihnen helfen, mehr Einblick in die Funktionsweise Ihrer Anwendung zu erhalten.

jdb

jdb, der Java Debugger, ist ein Befehlszeilen-Dienstprogramm zum Debuggen von Java-Klassen. jdb ist einfach zu benutzen und wird mit dem Java Development Kit geliefert. Bei der Verwendung von jdb wird eine neue Java Virtual Machine (JVM) erstellt, so dass Sie eine Klasse debuggen können, ohne laufende Programme zu beeinträchtigen. Sie können jdb auch zum Debuggen laufender Anwendungen verwenden, indem Sie Ihrem java-Befehl die folgenden Parameter hinzufügen:

-agentlib:jdwp=transport=dt_socket,server=y,suspend=n

Wenn die JVM startet, weist sie eine Portnummer für eingehende jdb-Verbindungen zu. Sie können dann mit jdb -attach:

$ jdb -attach

eine Verbindung zur laufenden JVM-Instanz herstellen, um beispielsweise Ihren Debugger mit einer laufenden Produktionsinstanz zu verbinden. Seien Sie vorsichtig mit der Verwendung von Haltepunkten in diesem Szenario, da dadurch ein aktiver Thread angehalten werden könnte. Dies kann Folgen haben, wenn z. B. ein Kunde die Anwendung benutzt, während Sie sie debuggen. Weitere Informationen finden Sie in der Java-Dokumentation zu jdb.

OverOps

OverOps ist eine Reihe von Tools zur Überwachung von Anwendungen, zur Analyse von Code und zur Erkennung von Problemen. Im Gegensatz zu Protokollierungssoftware, die auf die von einer laufenden Anwendung erzeugten Ausgaben angewiesen ist, verbindet sich OverOps direkt mit der Java Virtual Machine, um die Codebasis der Anwendung abzubilden, Variablen zu lesen und Fehler aufzuzeichnen. Dadurch kann es mehr Fehler abfangen und mehr Daten aufzeichnen als das Logging-Framework der Anwendung. OverOps verwendet ein Software-as-a-Service-Modell (SaaS), bei dem die Metriken auf den Cloud-Servern von OverOps gesammelt und gespeichert werden. Sie können es aber auch vor Ort einsetzen. In beiden Fällen können Sie Ihre Daten über eine webbasierte Schnittstelle einsehen.

BTrace

BTrace ist ein Tracing-Tool, mit dem Sie alle Aspekte Ihrer Anwendung überwachen können, von Klassennamen bis zu Fehlern. BTrace verwendet einen aspektorientierten Programmieransatz, der die Verwendung von Annotationen beinhaltet, die angeben, wo und wie BTrace Ihre Anwendung überwacht. Das folgende BTrace-Skript überwacht und protokolliert beispielsweise jeden einzelnen Aufruf des javax.swing-Pakets.

import com.sun.btrace.annotations.*;import static com.sun.btrace.BTraceUtils.*; @BTrace public class AllMethods { @OnMethod( clazz="/javax\.swing\..*/", method="/.*/" ) public static void m(@ProbeClassName String probeClass, @ProbeMethodName String probeMethod) { print(Strings.strcat("entered ", probeClass)); println(Strings.strcat(".", probeMethod)); }}

Weitere Informationen zu BTrace finden Sie im BTrace-GitHub-Repository und im Wiki.

Chronon

Chronon ermöglicht es Ihnen, den gesamten Ausführungsablauf einer Anwendung zurückzuspulen und abzuspielen. Es zeichnet jede einzelne Änderung auf, die während der Lebensdauer einer Anwendung vorgenommen wird, so dass Sie den Zustand der Anwendung zu jedem beliebigen Zeitpunkt reproduzieren können. Die Aufzeichnungen werden in einer Datei gespeichert, so dass es einfach ist, die Ausführungshistorie von einer Produktionsmaschine auf eine Entwicklungsmaschine zum Testen zu übertragen.

Chronon besteht aus dem Chronon Recording Server, mit dem Sie Java-Anwendungen aus der Ferne aufzeichnen können; Embedded Chronon, das den Rekorder in eine Anwendung einbettet; und dem Time Travelling Debugger, mit dem Sie die Aufzeichnungen abspielen können.

jhsdb

jhsdb (Java HotSpot Debugger) ist eine Suite von Tools zum Debuggen, Analysieren und Profilieren der Standard-JVM, die sowohl mit OpenJDK als auch mit dem Oracle JDK geliefert wird. Mit jhsdb können Sie sich an laufende Java-Prozesse anhängen, Schnappschüsse von Stack Traces machen und sogar abgestürzte JVMs analysieren. Sie können damit auf den Heap, den Code-Cache, Garbage-Collection-Statistiken und mehr zugreifen. Weitere Informationen finden Sie auf der jhsdb-Dokumentationsseite.

Tracing Transactions

Wenn ein Problem auftritt, ist es wichtig zu wissen, wo das Problem seinen Ursprung hat und wie es sich auf den Rest Ihrer Anwendung auswirkt. Das ist in einer monolithischen Anwendung schon schwierig genug, wird aber in einer verteilten, serviceorientierten Architektur noch schwieriger, wo eine einzige Anfrage Dutzende von Diensten betreffen kann. Es ist nicht immer offensichtlich, welcher Dienst die Ursache des Fehlers ist oder wie er sich auf andere Dienste auswirkt. Tracing liefert die Daten, die benötigt werden, um den Ausführungspfad Ihrer Anwendung zu verfolgen und die genaue Ursache eines Problems zu finden.

Im Abschnitt Debugging von Produktionsproblemen des Handbuchs haben wir ein Beispiel durchgespielt, bei dem ein Benutzer aufgrund einer ungültigen Datenbanktabelle Schwierigkeiten hatte, sich bei einer Webanwendung anzumelden. In diesem Abschnitt zeigen wir, wie die Verfolgung von Transaktionen eine zentrale Rolle bei der Lösung des Problems spielte.

Verfolgung eindeutiger IDs

Um eine Sequenz von Log-Ereignissen zu verfolgen, benötigen Sie eine Möglichkeit, zusammengehörige Logs eindeutig zu identifizieren. In einer Umgebung mit mehreren Benutzern können Hunderte von identischen Protokollen erzeugt werden, was eine Suche anhand von Zeitstempeln oder Logger erschwert. Eine einfachere Lösung besteht darin, zusammenhängenden Protokolleinträgen einen eindeutigen Bezeichner beizufügen. Dieser Bezeichner kann ein Benutzername, eine Sitzungs-ID, ein API-Schlüssel oder ein Universally Unique Identifier (UUID) sein. Glücklicherweise ist ThreadContext für diese Aufgabe perfekt geeignet.

Im Beispiel Debugging Production Problems hatten wir eine webbasierte Benutzeroberfläche, die in einem Tomcat-Servlet gehostet wurde, das mit einer MySQL-Datenbank verbunden war. Die Benutzer gaben ihre Anmeldedaten auf der Webseite ein, und nachdem sie auf „Submit“ gedrückt hatten, führte das Servlet eine Abfrage durch, die ihre Anmeldedaten mit den in der Datenbank gespeicherten Daten verglich. Wurde der Benutzer erfolgreich authentifiziert, so wurde er zur Hauptseite weitergeleitet. Trat ein Fehler auf, wurden Einzelheiten über den Fehler protokolliert, und die Benutzer erhielten eine allgemeine Meldung.

Wir konnten dieses Problem beheben, indem wir die Benutzernamen der Benutzer in die Protokollmeldung aufnahmen. So konnten wir schnell nach Log-Ereignissen suchen, die sich auf den Admin-Benutzer bezogen. Ausgehend von diesem Beispiel können wir ThreadContext.put() verwenden, um einen Benutzernamen einem Logger zuzuordnen. Wenn die Benutzer ihre Anmeldedaten übermitteln, ruft das Servlet die Methode doPost() auf, die die Benutzernamen zum ThreadContext hinzufügt:

public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {ThreadContext.put("username", request.getParameter("username"));logger.info("Entering doPost().");...}

Die Verwendung von Log4j mit dem Muster %p : %m%n ergibt den folgenden Eintrag:

INFO : Entering doPost().

Wir können ähnliche Ereignisse für den Zugriff auf die MySQL-Datenbank, das Verlassen der doPost-Methode und die Durchführung anderer Aktionen hinzufügen. Auf diese Weise wissen wir, wenn der Benutzer eine Ausnahme auslöst, genau, was er getan hat, als die Ausnahme auftrat.

Methodenaufrufe verfolgen

Viele Logging-Frameworks bieten native Methoden zum Verfolgen des Ausführungspfads einer Anwendung. Diese Methoden variieren leicht zwischen den Frameworks, folgen aber dem gleichen allgemeinen Format.

  • traceEntry() markiert den Anfang einer Methode.
  • traceExit() markiert das Ende einer Methode. Bei Methoden, die ein Objekt zurückgeben, können Sie gleichzeitig das Objekt zurückgeben und das Ereignis mit return logger.exit(object) protokollieren.
  • throwing() markiert eine Ausnahme, die wahrscheinlich nicht behandelt wird, z. B. eine RuntimeException.
  • catching() markiert eine Ausnahme, die nicht erneut ausgelöst wird.

Frameworkspezifische Informationen zu diesen Methoden finden Sie in der Logger-Dokumentation für Log4j, Logback und java.util.logging.

Tracing-Methoden in Log4j

Als Beispiel für die Verwendung von Log4j werden wir die Logger.info()-Methoden in unserem Servlet durch Tracing-Methoden ersetzen.

public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { logger.entry(); ... logger.exit();}

Wir werden auch die Appender's PatternLayout ändern, um den Klassennamen, die Methode und die Zeilennummer (die %class, %M und %line Konvertierungsmuster) anzuzeigen.

<PatternLayout pattern="%p %class %M %line: %m%n" />

Tracing-Methoden protokollieren Ereignisse auf der Ebene TRACE, was bedeutet, dass wir die Ebene Logger's von DEBUG auf TRACE ändern müssen. Andernfalls werden die Protokollmeldungen unterdrückt.

<Loggers> <Root level="trace"> <AppenderRef ref="Console"/> </Root></Loggers> TRACE DatabaseApplication.Login doPost 26: entry...TRACE DatabaseApplication.Login doPost 59: exit

Tracing-Methoden bieten auch ihre eigenen Markers. Zum Beispiel zeigen die Methoden Logger.entry() und Logger.exit() ENTER bzw. EXIT an.

ENTER 14:47:41.074 TRACE DatabaseApplication.Login - EnterEXIT 14:47:41.251 TRACE DatabaseApplication.Login - Exit

Tracing-Methoden in SLF4J

SLF4J-Benutzer können die Vorteile von MDC nutzen, um Log-Ereignisse zu verfolgen. Ähnlich wie bei Log4j können MDC-Werte mit einem Appender unter Verwendung des %X-Konvertierungsmusters verwendet werden.

SLF4J bietet auch entry(), exit(), throwing() und catching() Methoden durch die XLogger (Extended Logger) Klasse. Sie können eine XLogger-Instanz mit XLoggerFactory.getXLogger() erstellen.

package DatabaseApplication;import org.slf4j.Logger;import org.slf4j.LoggerFactory; import org.slf4j.ext.XLogger;import org.slf4j.ext.XLoggerFactory; public class Login extends HTTPServlet { final static XLogger xlogger = XLoggerFactory.getXLogger(Login.class.getName()); final static Logger logger = LoggerFactory.getLogger(Login.class.getName()); @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { xlogger.entry(); .. xlogger.exit(); }}

Fügen Sie die Konvertierungsmuster %class und %line zu logback.xml hinzu.

<configuration> <appender name="Console" class="ch.qos.Logback.core.ConsoleAppender"> <encoder> <pattern>%-5level %class{36} %M %L: %msg%xEx%n</pattern> </encoder> </appender> <root level="trace"> ... </root></configuration>

Die resultierenden Protokolleinträge sind identisch mit denen, die von Log4j erstellt werden.

Speichernutzung verwalten

Die Speicherverwaltung wird in höheren Sprachen wie Java oft übersehen. Während die durchschnittliche Speichermenge in modernen Geräten zunimmt, kann ein hoher Speicherverbrauch große Auswirkungen auf die Stabilität und Leistung Ihrer Anwendung haben. Sobald die virtuelle Java-Maschine nicht mehr in der Lage ist, Speicher vom Betriebssystem zuzuweisen, könnte Ihr Programm abbrechen und abstürzen. Wenn Sie wissen, wie Sie den Speicher verwalten, können Sie Probleme vermeiden, wenn Ihre Anwendung größer wird.

Stellen Sie sich zum Beispiel vor, Sie möchten eine Reihe von Zahlen speichern, die bei eins beginnen. Ein Benutzer gibt eine Obergrenze vor, und das Programm speichert jede Ganzzahl von „1“ bis zu dieser Grenze. Wir verwenden eine while-Schleife mit einem Zähler, um jede Zahl zu einem Array hinzuzufügen.

import java.util.Scanner;...System.out.print("Please enter the maximum size of the array: ");Scanner scanner = new Scanner(System.in);int limit = scanner.nextInt(); ArrayList intArray = new ArrayList(); int count = 1;while (count <= limit) { intArray.add(count);}

Sie haben vielleicht bemerkt, dass count in der Schleife nicht inkrementiert wird. Das ist ein großes Problem; wenn der Benutzer eine Zahl größer als Null eingibt, wird das Array weiter wachsen, bis die JVM ihren gesamten verfügbaren Speicher verbraucht und das Programm abstürzt.

Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceat java.util.Arrays.copyOf(Arrays.java:2245)at java.util.Arrays.copyOf(Arrays.java:2219)at java.util.ArrayList.grow(ArrayList.java:242)at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216)at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:208)at java.util.ArrayList.add(ArrayList.java:440)at MemoryTest.main(MemoryTest.java:10)

Garbage Collection

Um den Speicherverbrauch zu reduzieren, führt die Java Virtual Machine einen regelmäßigen Aufräumprozess durch, der als Garbage Collection bekannt ist. Die Garbage Collection sucht nach Speicherblöcken, die nicht mehr verwendet werden, und macht sie für die Wiederverwendung verfügbar. In den folgenden Ressourcen wird der Garbage-Collection-Prozess von Java ausführlicher erläutert.

  • Java Garbage Collection Basics (Oracle)
  • HotSpot Virtual Machine Garbage Collection Tuning Guide (Oracle)
  • Understanding G1 GC Logs (Poonam Bajaj)
  • Garbage Collection Tuning Guide (Atlassian)

Der Garbage Collector generiert Diagnoseinformationen, die für das Performance-Profiling von Anwendungen nützlich sein können. Sie können diese Informationen protokollieren, indem Sie beim Starten Ihrer Anwendung -Xlog:gc an die JVM übergeben. Nach jedem Lauf gibt der Garbage Collector Statistiken über den Garbage Collection-Prozess im Unified JVM Logging-Format aus. Hier ist ein Beispiel.

 Using G1 Periodic GC disabled GC(0) Pause Young (Normal) (G1 Evacuation Pause) 7M->1M(64M) 8.450ms

Using G1 gibt an, welche Garbage-Collection-Methode verwendet wird. Der Garbage-First (G1)-Kollektor ist auf Multiprozessor-Computern mit viel RAM oft standardmäßig aktiviert. Periodic GC disabled gibt an, dass der Garbage-Collection-Prozess nicht wiederholt wird. Die dritte Zeile besagt, dass es sich um eine Evakuierungspause handelt, in der Objekte zwischen Speicherbereichen kopiert werden, je nachdem, ob sie noch in Gebrauch sind. Pause Young sagt uns, dass der Prozess die junge Generation bereinigt hat, in der neue Objekte zugewiesen werden. Infolgedessen sank der Gesamtspeicherverbrauch von Objekten in der jungen Generation von 7 Mio. auf 1 Mio. von 64 Mio. zugewiesenen Objekten, und der Prozess benötigte 8.450ms.

Während der Garbage Collector Diagnoseinformationen auf der Konsole ausgibt, können wir sehen, wie die Menge des zugewiesenen Speichers (die Heap-Größe) mit der Zeit weiter wächst. Der Einfachheit halber entfernen wir die CPU-Zeiten.

$ java -Xlog:gc MemoryLeakDemo Using G1 Periodic GC disabled GC(0) Pause Young (Concurrent Start) (G1 Humongous Allocation) 25M->23M(64M) 4.372ms GC(1) Concurrent Cycle GC(1) Pause Remark 50M->34M(64M) 0.250ms GC(1) Pause Cleanup 94M->94M(124M) 0.057ms GC(1) Concurrent Cycle 169.695ms GC(2) Pause Young (Concurrent Start) (G1 Humongous Allocation) 94M->94M(124M) 3.738ms GC(3) Concurrent Cycle...

Das Diagramm zeigt einen allmählichen Anstieg der JVM-Heap-Nutzung im Laufe der Zeit.

Mit -XX:+PrintGCDateStamps und -XX:+PrintGCTimeStamps können Sie jedem Eintrag Zeitstempel hinzufügen. Sie können auch die Ausgabe des Garbage Collectors in eine Datei protokollieren, indem Sie Xlog:gc:file: an die JVM übergeben, wenn Sie Ihre Anwendung starten.

Wenn Sie Ihre Protokolldateien mit rsyslog überwachen, können Sie die Protokolle an ein zentrales Protokollierungssystem weiterleiten, wo sie geparst werden und für eine Fast-Echtzeit-Analyse bereit sind. Hier sehen Sie, wie eine Zeitleiste in Loggly aussieht. Es zeigt die Gesamtgröße des Heaps sowie die Größe des Heaps vor und nach der Ausführung des Garbage Collectors.

Speicherlecks

Ein Speicherleck ist eine Situation, in der ein Programm Speicher schneller zuweist, als es ihn wieder freigeben kann. Der einfachste Weg, ein Speicherleck zu erkennen, ist, wenn Ihr Programm nicht mehr reagiert, instabil wird oder OutOfMemoryErrors verursacht. In Java finden Sie mehr Instanzen von Full GC-Sammlungen, wenn die Speichernutzung zunimmt.

Sie können mehr über die Ausgabe des Garbage Collectors und das Aufspüren von Speicherlecks im Kapitel Troubleshoot Memory Leaks der Java-Dokumentation erfahren.

Zusätzliche Ressourcen

Sparen Sie Debugging-Zeit, ohne eine einzige Zeile Code zu schreiben (Loggly)-Leitfaden zur Verwendung von Takipi

The Ultimate Guide: 5 Methods for Debugging Production Servers at Scale (High Scalability)-Tools und Techniken zum Debuggen von Produktionsproblemen

Memory Management

Garbage Collection Tuning Guide (Oracle)-Leitfaden zum Verständnis und zur Feinabstimmung der Garbage Collection von Java

Understanding Java Garbage Collection (CUBRID Blog)-Leitfaden zu garb

See it. Analyze it. Inspect it. Lösen Sie es

Sehen Sie, worauf es ankommt.

START FREE TRIAL

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.