Wie lässt sich ein einfaches Logging realisieren?
Unter Logging versteht man das Protokollieren eines Softwareablaufes. Ein solches Protokoll entsteht nicht automatisch, sondern muss vom Entwickler eingerichtet werden. Üblicherweise werden dabei Zwischenzustände eines Programmablaufes notiert. Für die Ausgabe des Logprotokolls können dabei unterschiedliche Ziele (Konsole, Datei, etc.) verwendet werden.
Die Java-Logging-API stellt für viele Fälle
ausreichend flexible Möglichkeiten bereit, um Log-Ausgaben
zu formatieren und zu konfigurieren. An dieser Stelle soll
zunächst die Möglichkeit einer bewusst einfachen
Ausgabe auf die Konsole nach System.err
vorgestellt
werden.
import java.util.logging.ConsoleHandler; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.Logger; public class LoggerBsp { public static void main(String[] args) { Logger logger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); logger.setLevel(Level.ALL); logger.severe("Schwerwiegender Fehler"); logger.warning("Warnung"); logger.info("Information"); logger.config("Konfigurationshinweis"); logger.fine("Fein"); logger.finer("Feiner"); logger.finest("Am feinsten"); } }
Die obige Beispielklasse enthält lediglich eine main()
-Methode,
die beim Ausführen des Programms einige Loggingausgaben auf
die Konsole generiert.
Es wird zunächst ein Logger
-Objekt
durch die statische Methode getLogger()
gebildet.
Sie benötigt einen String
-Parameter, der einen
Bezeichner definiert, mit dem das Logger
-Objekt
angesprochen werden kann. Der Einfachheit halber wird hier
zunächst der globale Loggername eingetragen, der durch die
Konstante GLOBAL_LOGGER_NAME
gesetzt wird. Im
Produktionsumfeld wird hier üblicherweise ein voll
qualifizierter Programm- oder Klassenname verwendet, um
Eindeutigkeit innerhalb einer Logging-Hierarchie zu wahren. Im
einfachsten Fall kann jedoch auch ein anonymer Logger
verwendet werden. Er wird durch Logger.getAnonymousLogger()
erzeugt.
Logging-Ebenen
Um Unterschiede in der Bedeutungsschwere einer Lognachricht zu
erfassen, kann ein Logging auf verschiedenen Ebenen gleichzeitig
stattfinden. Bei der Ausgabe des Loggings kann dann die
gewünschte Ebene gewählt werden.
Java definiert
hierzu die folgenden Grade als statische Felder (Konstanten) der
Klasse java.util.logging.Level
in absteigender
Gewichtung:
- Level.SEVERE
- Level.WARNING
- Level.INFO
- Level.CONFIG
- Level.FINE
- Level.FINER
- Level.FINEST
Ergänzend existieren noch Level.ALL
und Level.OFF
,
um alle Ebenen anzusprechen, bzw. das Logging vollständig
abzuschalten.
Durch setLevel(Level.ALL)
wird dem Logger
-Objekt
somit mitgeteilt, dass alle Logebenen angesprochen werden
können. Das Setzen der Logebene an dieser Stelle bezieht
sich jedoch nur auf das Erzeugen der Lognachrichten selbst,
nicht auf deren Ausgabe. Diese ist Sache eines Handler
-Objektes.
Wird ein Handler
wie hier nicht explizit gesetzt,
so wird der übergeordnete ParentHandler zur
Ausgabesteuerung verwendet. Seine Standardeinstellung bewirkt
eine Ausgabe aller Warnungen bis zu Level.INFO
.
Alle feineren Warnungen werden unterdrückt.
Erzeugung der Log-Nachricht
Die Erzeugung der Log-Nachricht selbst wird durch Methoden erzeugt, deren Bezeichner denjenigen der Loglevel entsprechen. Wie erwähnt werden auf diese Weise unterschiedliche Gewichtungen von Logausgaben generiert. Unter einer "unterschiedlichen Gewichtung" ist in diesem Fall die Bedeutungsschwere eines Ereignisses zu verstehen, das zu einem Logeintrag führen soll. Es ist Aufgabe des Entwicklers zu entscheiden, ob und an welchen Stellen des Programmablaufs ein Logging erfolgen und mit welcher Gewichtung dies stattfinden soll.
Jeder Loggingebene, mit Ausnahme von ALL
und OFF
,
entsprechen zwei überladene, gleichnamige Methoden der
Klasse Logger
, die zur Erzeugung einer dem
jeweiligen Grad entsprechenden Protokollnachricht dienen und
diese an einen Handler
weiterreichen. Die Nachricht
selbst ist ein frei formulierbarer Text, der der Methode als String
-Parameter
übergeben wird. Die Methoden zur Ausgabe des Logtextes
werden im Beispielprogramm auf dem erzeugten Logger
-Objekt
für jede Ebene ein Mal aufgerufen.
Die Ausführung des Programms erzeugt die nachstehende Ausgabe:
Okt 17, 2017 8:00:50 PM allgemeines.logging.LoggerBsp main SCHWERWIEGEND: Schwerwiegender Fehler Okt 17, 2017 8:00:50 PM allgemeines.logging.LoggerBsp main WARNUNG: Warnung Okt 17, 2017 8:00:50 PM allgemeines.logging.LoggerBsp main INFORMATION: Information
Obwohl im Programm Ausgaben durch die erwähnten Methoden
auf allen Leveln erzeugt werden, erfolgt offensichtlich nur eine
Ausgabe der drei obersten Ebenen. Dies so zu erklären, dass
der Logger
zwar zur Ausgabe aller Meldungen
konfiguriert wurde und diese auch erzeugt, der Handler jedoch
alle Ausgaben ab Level.CONFIG
einschließlich
unterdrückt.
Erweitert man das Programm durch folgende Zeile
logger.getParent().getHandlers()[0].setLevel(Level.ALL);
so werden alle Warnungsebenen ausgegeben, da auch der ParentHandler
nun alle an ihn übergebenen Nachrichten publiziert.
Umgekehrt
kann durch
logger.setUseParentHandlers(false);
die Ausgabe des ParentHandlers vollständig
unterdrückt werden. Ist kein Handler
explizit
gesetzt worden, so findet eine Ausgabe dann gar nicht mehr
statt.
Der Handler
Ein Handler
ist ein Objekt, das Art und Bedingungen
der Ausgabe einer Lognachricht steuert. Die Klasse besitzt zwei
Unterklassen, MemoryHandler
und StreamHandler
,
von denen die zweite wiederum durch ConsoleHandler,
FileHandler
und SocketHandler
erweitert
wird. Wie die Bezeichner nahelegen, werden hierdurch die
Besonderheiten einer Ausgabe auf das jeweilige Medium gesteuert.
Um die Funktionsweise zu verdeutlichen, die Ausgaberoutinen
jedoch nicht unnötig zu verkomplizieren, wird im folgenden
Beispiel nun ein ConsoleHandler
erzeugt, der dem Logger
-Objekt
'manuell' hinzugefügt wird. Die Ausgabe-Logebene wird hier
ebenfalls zunächst auf Level.ALL
gesetzt, um
alle Ausgaben zu erhalten.
Hier der vollständige
Quelltext nebst Ausgabe
public class LoggerBsp { public static void main(String[] args) { Logger logger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); logger.setLevel(Level.ALL); Handler handler = new ConsoleHandler(); handler.setLevel(Level.ALL); logger.addHandler(handler); logger.severe("Schwerwiegender Fehler"); logger.warning("Warnung"); logger.info("Information"); logger.config("Konfigurationshinweis"); logger.fine("Fein"); logger.finer("Feiner"); logger.finest("Am feinsten"); } } // Ausgabe: Okt 17, 2017 7:57:36 PM allgemeines.logging.LoggerBsp main SCHWERWIEGEND: Schwerwiegender Fehler Okt 17, 2017 7:57:36 PM allgemeines.logging.LoggerBsp main SCHWERWIEGEND: Schwerwiegender Fehler Okt 17, 2017 7:57:36 PM allgemeines.logging.LoggerBsp main WARNUNG: Warnung Okt 17, 2017 7:57:36 PM allgemeines.logging.LoggerBsp main WARNUNG: Warnung Okt 17, 2017 7:57:36 PM allgemeines.logging.LoggerBsp main INFORMATION: Information Okt 17, 2017 7:57:36 PM allgemeines.logging.LoggerBsp main INFORMATION: Information Okt 17, 2017 7:57:36 PM allgemeines.logging.LoggerBsp main KONFIGURATION: Konfigurationshinweis Okt 17, 2017 7:57:36 PM allgemeines.logging.LoggerBsp main FEIN: Fein Okt 17, 2017 7:57:36 PM allgemeines.logging.LoggerBsp main FEINER: Feiner Okt 17, 2017 7:57:36 PM allgemeines.logging.LoggerBsp main AM FEINSTEN: Am feinsten
Es ist zu erkennen, dass die vier der Ebene Level.INFO
nachgeordneten Gewichtungen CONFIG, FINE, FINER
und
FINEST
ein Mal, die Ebenen SEVERE, WARNING
und INFO
jedoch doppelt ausgegeben werden. Vor dem
obigen Hintergrund ist dies leicht zu erklären: Die
Ausgaben erfolgen ein Mal durch den explizit gesetzten ConsoleHandler
,
die zusätzlichen Ausgaben entstammen dem ParentHandler.
Unterdrückt
man diesen wieder durch
logger.setUseParentHandlers(false);
so findet jede Ausgabe nur ein Mal durch den gesetzten Handler
statt.
Um das Verfahren zusätzlich zu verdeutlichen,
kann die Zuweisung der Logebene an den Handler
, an
den Logger
oder an beide gleichzeitig durch setLevel()
z.B. auf Level.FINE
gesetzt werden. Die Ausgaben
sind dann in allen drei Fällen identisch:
Okt 17, 2017 7:57:36 PM allgemeines.logging.LoggerBsp main SCHWERWIEGEND: Schwerwiegender Fehler Okt 17, 2017 7:57:36 PM allgemeines.logging.LoggerBsp main WARNUNG: Warnung Okt 17, 2017 7:57:36 PM allgemeines.logging.LoggerBsp main INFORMATION: Information Okt 17, 2017 7:57:36 PM allgemeines.logging.LoggerBsp main KONFIGURATION: Konfigurationshinweis Okt 17, 2017 7:57:36 PM allgemeines.logging.LoggerBsp main FEIN: Fein
Die Begrenzung des Ausgabelevels wird dann entweder durch den Handler
,
den Logger
oder durch beide vorgenommen.
Wenn Ihnen javabeginners.de gefällt, freue ich mich über eine Spende an diese gemeinnützigen Organisationen.