Log4j bietet Kontrolle über die Protokollierung

Fast jede große Anwendung verfügt über eine eigene Protokollierungs- oder Ablaufverfolgungs-API. Die Erfahrung zeigt, dass die Protokollierung ein wichtiger Bestandteil des Entwicklungszyklus ist. Die Protokollierung bietet daher mehrere Vorteile. Erstens kann es einen genauen Kontext für einen Lauf der Anwendung bereitstellen. Nach dem Einfügen in den Code erfordert die Generierung der Protokollausgabe kein menschliches Eingreifen. Zweitens kann die Protokollausgabe auf einem dauerhaften Medium gespeichert werden, um zu einem späteren Zeitpunkt untersucht zu werden. Zusätzlich zu seiner Verwendung im Entwicklungszyklus kann ein ausreichend umfangreiches Protokollierungspaket auch als Audit-Tool verwendet werden.

In Übereinstimmung mit dieser Regel beschloss das EU-SEMPER-Projekt (Secure Electronic Marketplace for Europe) Anfang 1996, eine eigene Tracing-API zu schreiben. Nach unzähligen Verbesserungen, mehreren Inkarnationen und viel Arbeit hat sich diese API zu log4j entwickelt, einem beliebten Protokollierungspaket für Java. Das Paket wird unter der IBM Public License vertrieben, die von der Open Source-Initiative zertifiziert wurde.

Die Protokollierung hat ihre Nachteile. Es kann eine Anwendung verlangsamen. Wenn es zu ausführlich ist, kann es zu Bildlaufblindheit führen. Um diese Bedenken auszuräumen, ist log4j schnell und flexibel konzipiert. Da die Protokollierung selten im Mittelpunkt einer Anwendung steht, ist die log4j-API bestrebt, einfach zu verstehen und zu verwenden.

Dieser Artikel beschreibt zunächst die Hauptkomponenten der log4j-Architektur. Es wird mit einem einfachen Beispiel fortgefahren, das die grundlegende Verwendung und Konfiguration darstellt. Zum Abschluss werden Leistungsprobleme und die bevorstehende Protokollierungs-API von Sun angesprochen.

Kategorien, Appender und Layouts

Log4j besteht aus drei Hauptkomponenten:

  • Kategorien
  • Appenders
  • Layouts

Die drei Komponenten arbeiten zusammen, damit Entwickler Nachrichten nach Nachrichtentyp und Priorität protokollieren und zur Laufzeit steuern können, wie diese Nachrichten formatiert werden und wo sie gemeldet werden. Schauen wir uns die einzelnen nacheinander an.

Kategoriehierarchie

Der wichtigste Vorteil einer Protokollierungs-API gegenüber der einfachen besteht System.out.printlndarin, dass bestimmte Protokollanweisungen deaktiviert werden können, während andere ungehindert drucken können. Diese Funktion setzt voraus, dass der Protokollierungsbereich, dh der Bereich aller möglichen Protokollierungsanweisungen, nach bestimmten vom Entwickler ausgewählten Kriterien kategorisiert wird.

In Übereinstimmung mit dieser Beobachtung bilden die org.log4j.CategoryKlassenzahlen den Kern des Pakets. Kategorien werden als Entitäten bezeichnet. In einem Benennungsschema, das Java-Entwicklern bekannt ist, wird eine Kategorie als übergeordnetes Element einer anderen Kategorie bezeichnet, wenn ihr Name, gefolgt von einem Punkt, ein Präfix des untergeordneten Kategorienamens ist. Beispielsweise ist die genannte Kategorie com.fooein übergeordnetes Element der genannten Kategorie com.foo.Bar. Ebenso javaist ein Elternteil von java.utilund ein Vorfahr von java.util.Vector.

Die Stammkategorie, die sich oben in der Kategoriehierarchie befindet, ist in zweierlei Hinsicht außergewöhnlich:

  1. Es existiert immer
  2. Es kann nicht namentlich abgerufen werden

CategoryWenn Sie in der Klasse die statische getRoot()Methode aufrufen, wird die Stammkategorie abgerufen. Die statische getInstance()Methode instanziiert alle anderen Kategorien. getInstance()Nimmt den Namen der gewünschten Kategorie als Parameter. Einige der grundlegenden Methoden in der CategoryKlasse sind unten aufgeführt:

Paket org.log4j; public Category class {// Methoden zum Erstellen und Abrufen: public static Category getRoot (); öffentliche statische Kategorie getInstance (String name); // Druckmethoden: public void debug (String message); public void info (String message); public void warn (String message); public void error (String message); // generische Druckmethode: public void log (Priorität p, String message); }}

Kategorien können Prioritäten aus dem von der org.log4j.PriorityKlasse definierten Satz zugewiesen werden . Obwohl der Prioritätssatz mit dem des Unix-Syslog-Systems übereinstimmt, empfiehlt log4j die Verwendung von nur vier Prioritäten: ERROR, WARN, INFO und DEBUG, die in absteigender Reihenfolge der Priorität aufgeführt sind. Das Grundprinzip dieser scheinbar eingeschränkten Menge besteht darin, eine flexiblere Kategoriehierarchie anstelle einer statischen (auch wenn großen) Reihe von Prioritäten zu fördern. Sie können jedoch Ihre eigenen Prioritäten definieren, indem Sie die PriorityKlasse in Unterklassen unterteilen . Wenn einer bestimmten Kategorie keine Priorität zugewiesen wurde, erbt sie eine von ihrem nächsten Vorfahren mit einer zugewiesenen Priorität. Um sicherzustellen, dass alle Kategorien eventuell eine Priorität erben können, hat die Stammkategorie immer eine zugewiesene Priorität.

Rufen Sie zum Erstellen von Protokollierungsanforderungen eine der Druckmethoden einer Kategorieinstanz auf. Diese Druckmethoden sind:

  • error()
  • warn()
  • info()
  • debug()
  • log()

Per Definition bestimmt die Druckmethode die Priorität einer Protokollierungsanforderung. Wenn es sich beispielsweise cum eine Kategorieinstanz handelt, handelt es sich bei der Anweisung c.info("..")um eine Protokollierungsanforderung mit der Priorität INFO.

Eine Protokollierungsanforderung wird als aktiviert bezeichnet, wenn ihre Priorität höher oder gleich der Priorität ihrer Kategorie ist. Andernfalls wird die Anforderung als deaktiviert bezeichnet. . Eine Kategorie ohne zugewiesene Priorität erbt eine von der Hierarchie.

Unten finden Sie ein Beispiel für diese Regel:

// eine Kategorieinstanz mit dem Namen "com.foo" abrufen Category cat = Category.getInstance ( "com.foo" ); // Jetzt setze seine Priorität. cat .setPriority ( Priority.INFO ); Kategorie barcat = Category.getInstance ( "com.foo.Bar" ); // Diese Anfrage ist aktiviert, weil WARN > = INFO . Katze. warn ("Niedriger Kraftstoffstand"); // Diese Anfrage ist deaktiviert, da DEBUG < INFO . Katze. Debug ("Suche nach nächster Tankstelle starten."); // Die Kategorieinstanz barcat mit dem Namen "com.foo.Bar" // erbt ihre Priorität von der Kategorie mit dem Namen // "com.foo".Die folgende Anfrage ist // wegen INFO aktiviert> = INFO . Barcat. info ("Nächste Tankstelle gelegen."); // Diese Anfrage ist deaktiviert, da DEBUG < INFO . Barcat. Debug ("Beenden der Tankstellensuche");

Wenn Sie die getInstance()Methode mit demselben Namen aufrufen, wird immer ein Verweis auf genau dasselbe Kategorieobjekt zurückgegeben. Somit ist es möglich, eine Kategorie zu konfigurieren und dann dieselbe Instanz an einer anderen Stelle im Code abzurufen, ohne Referenzen weiterzugeben. Kategorien können in beliebiger Reihenfolge erstellt und konfiguriert werden. Insbesondere wird eine übergeordnete Kategorie ihre untergeordneten Kategorien finden und mit ihnen verknüpfen, selbst wenn sie nach ihnen instanziiert wird. Die log4j-Umgebung wird normalerweise bei der Anwendungsinitialisierung konfiguriert, vorzugsweise durch Lesen einer Konfigurationsdatei. Dieser Ansatz wird in Kürze erläutert.

Mit Log4j können Kategorien einfach nach Softwarekomponenten benannt werden. Dies kann erreicht werden, indem eine Kategorie in jeder Klasse statisch instanziiert wird, wobei der Kategoriename dem vollständig qualifizierten Namen der Klasse entspricht - eine nützliche und einfache Methode zum Definieren von Kategorien. Da die Protokollausgabe den Namen der generierenden Kategorie trägt, erleichtert eine solche Benennungsstrategie die Identifizierung des Ursprungs einer Protokollnachricht. Dies ist jedoch nur eine mögliche, wenn auch übliche Strategie zur Benennung von Kategorien. Log4j schränkt die möglichen Kategorien nicht ein. In der Tat kann der Entwickler die Kategorien wie gewünscht benennen.

Appender und Layouts

Die Möglichkeit, Protokollierungsanforderungen basierend auf ihrer Kategorie selektiv zu aktivieren oder zu deaktivieren, ist nur ein Teil des Bildes. Mit Log4j können Protokollierungsanforderungen auch an mehrere Ausgabeziele gedruckt werden, die in log4j speak als Appender bezeichnet werden . Derzeit sind Appender für die Konsole, Dateien, GUI-Komponenten, Remote-Socket-Server, NT-Ereignisprotokollierer und Remote-UNIX-Syslog-Daemons vorhanden.

Eine Kategorie kann sich auf mehrere Appender beziehen. Jede aktivierte Protokollierungsanforderung für eine bestimmte Kategorie wird an alle Appender in dieser Kategorie sowie an die Appender in der höheren Hierarchie weitergeleitet. Mit anderen Worten, Appender werden additiv von der Kategoriehierarchie geerbt. Wenn Sie beispielsweise der Stammkategorie einen Konsolen-Appender hinzufügen, werden alle aktivierten Protokollierungsanforderungen mindestens auf der Konsole gedruckt. Wenn zusätzlich ein Datei-Appender zu einer Kategorie hinzugefügt wird, z. B. C , werden aktivierte Protokollierungsanforderungen für die untergeordneten C- und C- Dateien in einer Datei und auf der Konsole gedruckt. Beachten Sie, dass Sie dieses Standardverhalten überschreiben können, damit die Appender-Akkumulation nicht mehr additiv ist.

More often than not, users want to customize not only the output destination but also the output format, a feat accomplished by associating a layout with an appender. The layout formats the logging request according to the user's wishes, whereas an appender takes care of sending the formatted output to its destination. The PatternLayout, part of the standard log4j distribution, lets the user specify the output format according to conversion patterns similar to the C language printf function.

For example, the PatternLayout with the conversion pattern %r [%t]%-5p %c - %m%n will output something akin to:

176 [main] INFO org.foo.Bar - Located nearest gas station. 

In the output above:

  • The first field equals the number of milliseconds elapsed since the start of the program
  • The second field indicates the thread making the log request
  • The third field represents the priority of the log statement
  • The fourth field equals the name of the category associated with the log request

The text after the - indicates the statement's message.

Configuration

Inserting log requests into the application code requires a fair amount of planning and effort. Observation shows that code dedicated to logging represents approximately four percent of the application's total. Consequently, even moderately sized applications will have thousands of logging statements embedded within their code. Given their number, it becomes imperative to manage those log statements without the need to modify them manually.

The log4j environment can be fully configured programmatically. However, it is far more flexible to configure log4j by using configuration files. Currently, configuration files can be written in XML or in Java properties (key=value) format.

Let us give a taste of how that is done with the help of an imaginary application -- MyApp -- that uses log4j:

 import com.foo.Bar; // Import log4j classes. import org.log4j.Category; import org.log4j.BasicConfigurator; public class MyApp { // Define a static category variable so that it references the // Category instance named "MyApp". static Category cat = Category.getInstance(MyApp.class.getName()); public static void main(String[] args) { // Set up a simple configuration that logs on the console. BasicConfigurator.configure(); cat.info("Entering application."); Bar bar = new Bar(); bar.doIt(); cat.info("Exiting application."); } } 

As seen in the code above, MyApp begins by importing log4j related classes. It then defines a static category variable with the name MyApp, which happens to be the class' fully qualified name.

MyApp uses the Bar class defined in the package com.foo:

package com.foo; import org.log4j.Category; public class Bar { static Category cat = Category.getInstance(Bar.class.getName()); public void doIt() { cat.debug("Did it again!"); } } 

In MyApp, the invocation of the BasicConfigurator.configure() method creates a rather simple log4j setup. That method is hardwired to add to the root category a FileAppender printing on the console. The output will be formatted by using a PatternLayout set to the pattern %-4r [%t] %-5p %c %x - %m%n.

Note that by default, the root category is assigned to Priority.DEBUG.

The output of MyApp is:

0 [main] INFO MyApp - Entering application. 36 [main] DEBUG com.foo.Bar - Did it again! 51 [main] INFO MyApp - Exiting application. 

Figure 1 depicts MyApp's object diagram immediately after it calls the BasicConfigurator.configure() method.

The MyApp class configures log4j by invoking BasicConfigurator.configure() method. Other classes need only import the org.log4j.Category class, retrieve the categories they want to use and log away.

The previous example always outputs the same log information. Fortunately, it is easy to modify MyApp so that the log output can be controlled at runtime. Below, you'll see a slightly modified version:

 import com.foo.Bar; import org.log4j.Category; import org.log4j.PropertyConfigurator; public class MyApp { static Category cat = Category.getInstance(MyApp.class.getName()); public static void main(String[] args) { // BasicConfigurator replaced with PropertyConfigurator. PropertyConfigurator.configure(args[0]); cat.info("Entering application."); Bar bar = new Bar(); bar.doIt(); cat.info("Exiting application."); } } 

This version of MyApp instructs PropertyConfigurator to parse a configuration file and set up logging accordingly.

Let's look at a sample configuration file that results in exactly the same output as the previous BasicConfigurator-based example:

# Set root category priority to DEBUG and its only appender to A1. log4j.rootCategory=DEBUG, A1 # A1 is set to be a FileAppender which outputs to System.out. log4j.appender.A1=org.log4j.FileAppender log4j.appender.A1.File=System.out # A1 uses PatternLayout. log4j.appender.A1.layout=org.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n 

Suppose we no longer wish to see the output of any component belonging to the com.foo package. The following configuration file shows one possible way of achieving that:

log4j.rootCategory = DEBUG, A1 log4j.appender.A1 = org.log4j.FileAppender log4j.appender.A1.File = System.out log4j.appender.A1.layout = org.log4j.PatternLayout # Drucken Sie das Datum im ISO 8601-Format log4j.appender.A1.layout.ConversionPattern = % d [% t]% -5p% c -% m% n # Nur Nachrichten mit Priorität WARN oder höher im Paket com.foo drucken. log4j.category.com.foo = WARNUNG