Star

Erweitern

tinylog ist erweiterbar. So ist es möglich, eigene Writers zu entwickeln. Für den RollingFileWriter können außerdem neue Labelers und Policies erstellt werden. Wie dies funktioniert, was zu beachten ist und welche Möglichkeiten es gibt, wird jeweils anhand eines Beispiels erklärt.

Writers

Ein eigener Writer muss das Interface Writer implementieren und threadsafe sein, da er gleichzeitig aus mehreren Threads aufgerufen werden kann. In der Methode getRequiredLogEntryValues() wird definiert, welche Werte im LogEntry belegt sein müssen. Alle verfügbaren Werte sind im Enum LogEntryValue dokumentiert.

import java.util.Set; import org.pmw.tinylog.Configuration; import org.pmw.tinylog.writers.LogEntry; import org.pmw.tinylog.writers.LogEntryValue; import org.pmw.tinylog.writers.Writer; public class MyWriter implements Writer { @Override public Set<LogEntryValue> getRequiredLogEntryValues() // Benötigte Werte im Log-Eintrag definieren } @Override public void init(Configuration configuration) { // Ressourcen allokieren, wie z.B. eine Datei öffnen } @Override public void write(LogEntry logEntry) { // Log-Eintrag ausgeben } @Override public void flush() { // Gegebenenfalls gepufferte Log-Einträge ausgeben } @Override public void close() { // Ressourcen freigeben, wie z.B. eine geöffnete Datei schließen } }

So könnte beispielsweise ein Writer aussehen, der alle Log-Einträge mit Hilfe des BufferedOutputStream in eine Log-Datei schreibt (eine vereinfachte Variante des vorhandenen FileWriter):

import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.EnumSet; import java.util.Set; import org.pmw.tinylog.Configuration; import org.pmw.tinylog.writers.LogEntry; import org.pmw.tinylog.writers.LogEntryValue; import org.pmw.tinylog.writers.VMShutdownHook; import org.pmw.tinylog.writers.Writer; public final class MyFileWriter implements Writer { private final String filename; private OutputStream stream; public MyFileWriter(final String filename) { this.filename = filename; } @Override public Set<LogEntryValue> getRequiredLogEntryValues() { return EnumSet.of(LogEntryValue.RENDERED_LOG_ENTRY); // Nur der gerenderte Log-Eintrag wird benötigt } @Override public void init(final Configuration configuration) throws IOException { stream = new BufferedOutputStream(new FileOutputStream(filename)); VMShutdownHook.register(this); // Stellt sicher, dass beim Beenden der JVM close() aufgerufen wird } @Override public void write(final LogEntry logEntry) throws IOException { String entry = logEntry.getRenderedLogEntry(); stream.write(entry.getBytes()); } @Override public void flush() throws IOException { stream.flush(); } @Override public void close() throws IOException { VMShutdownHook.unregister(this); // Schließen der Datei beim Beenden der JVM nicht mehr notwendig stream.close(); } }

Damit ein Writer nicht nur programmatisch erstellt werden kann, muss dieser tinylog bekannt gemacht werden. Dies funktioniert ähnlich, wie man es von Diensten in Java kennt (Stichwort: ServiceLoader). Dazu erstellt man im eigenen Java-Projekt eine Textdatei mit dem Dateinamen "org.pmw.tinylog.writers" im Verzeichnis "META-INF/services". In dieser Textdatei wird der Klassenname (inklusive Package) des neuen Writer eingetragen. Mehrere Writers können durch Zeilenumbrüche getrennt werden.

my.package.MyFileWriter

Anschließend muss tinylog noch wissen, über welchen Namen der Writer angesprochen werden soll und welche Parameter es gibt. Dies erfolgt über die Annotation @PropertiesSupport. Zu den definierten Parametern muss ein passender Konstruktor existieren.

import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.EnumSet; import java.util.Set; import org.pmw.tinylog.Configuration; import org.pmw.tinylog.writers.LogEntry; import org.pmw.tinylog.writers.LogEntryValue; import org.pmw.tinylog.writers.PropertiesSupport; import org.pmw.tinylog.writers.Property; import org.pmw.tinylog.writers.VMShutdownHook; import org.pmw.tinylog.writers.Writer; @PropertiesSupport(name = "myfile", properties = { @Property(name = "filename", type = String.class) }) public final class MyFileWriter implements Writer { [...] public MyFileWriter(final String filename) { this.filename = filename; } [...] }

Nun ist es möglich, den neuen Writer über eine Properties-Datei zu initialisieren.

tinylog.writer = myfile
tinylog.writer.filename = log.txt

Labelers

Labelers werden vom RollingFileWriter verwendet, um die Log-Dateien zu verwalten. Dabei bestimmt der Labeler nicht nur den Namen der Log-Dateien, sondern muss sich auch um das Löschen von veralteten Backups kümmern. Ein eigener Labeler muss das Interface Labeler implementieren. Da der RollingFileWriter Labelers immer synchron aufruft, sind diese automatisch threadsafe.

import java.io.File; import java.io.IOException; import org.pmw.tinylog.Configuration; import org.pmw.tinylog.labelers.Labeler; public class MyLabeler implements Labeler { @Override public void init(Configuration configuration) { // Labeler initialisieren } @Override public File getLogFile(File baseFile, int maxBackups) { // Aktive Log-Datei bestimmen und gegebenenfalls alte löschen } @Override public File roll(File file, int maxBackups) { // Neue Log-Datei beginnen und gegebenenfalls alte löschen } }

So könnte beispielsweise ein Labeler aussehen, der für jede aktive Instanz einer Anwendung anhand der Prozess-ID eine separate Log-Datei erstellt (eine vereinfachte Variante des vorhandenen ProcessIdLabeler):

import java.io.File; import java.io.FilenameFilter; import java.util.Arrays; import java.util.Comparator; import org.pmw.tinylog.Configuration; import org.pmw.tinylog.EnvironmentHelper; import org.pmw.tinylog.labelers.Labeler; public class MyProcessIdLabeler implements Labeler { private String pid; private String postfix; @Override public void init(final Configuration configuration) { pid = EnvironmentHelper.getProcessId().toString(); } @Override public File getLogFile(final File baseFile, final int maxBackups) { /* 1. Falls erforderlich, Backups löschen */ deleteOldBackups(baseFile.getParentFile(), maxBackups); /* 2. Process ID vor den Dateinamen stellen */ postfix = "-" + baseFile.getName(); return new File(baseFile.getParent(), pid + postfix); } @Override public File roll(final File file, final int maxBackups) { /* 1. Falls Datei bereits vorhanden, diese löschen */ if (file.exists()) { file.delete(); } /* 2. Falls erforderlich, Backups löschen */ deleteOldBackups(file.getParentFile(), maxBackups); /* 3. Mit gleichem Dateinamen fortfahren */ return file; } private void deleteOldBackups(final File folder, final int maxBackups) { File[] files = folder.listFiles(new FilenameFilter() { @Override public boolean accept(final File dir, final String name) { /* Alle Dateien, die dem Namensschema folgen */ return name.endsWith(postfix); } }); if (files.length > maxBackups) { Arrays.sort(files, new Comparator<File>() { @Override public int compare(final File file1, final File file2) { /* Zuletzt geänderte Dateien zuerst */ return Long.valueOf(file2.lastModified()).compareTo(file1.lastModified()); } }); /* Die ältesten Log-Dateien löschen */ for (int i = maxBackups; i < files.length; ++i) { files[i].delete(); } } } }

Damit ein Labeler nicht nur programmatisch erstellt werden kann, muss dieser tinylog bekannt gemacht werden. Dies funktioniert ähnlich, wie man es von Diensten in Java kennt (Stichwort: ServiceLoader). Dazu erstellt man im eigenen Java-Projekt eine Textdatei mit dem Dateinamen "org.pmw.tinylog.labelers" im Verzeichnis "META-INF/services". In dieser Textdatei wird der Klassenname (inklusive Package) des neuen Labeler eingetragen. Mehrere Labelers können durch Zeilenumbrüche getrennt werden.

my.package.MyLabeler

Anschließend muss tinylog noch wissen, über welchen Namen der Labeler angesprochen werden soll. Dies erfolgt über die Annotation @PropertiesSupport.

import java.io.File; import java.io.FilenameFilter; import java.util.Arrays; import java.util.Comparator; import org.pmw.tinylog.Configuration; import org.pmw.tinylog.EnvironmentHelper; import org.pmw.tinylog.labelers.Labeler; import org.pmw.tinylog.labelers.PropertiesSupport; @PropertiesSupport(name = "mypid") public class MyProcessIdLabeler implements Labeler { [...] }

Falls der Labeler parametrierbar sein soll, kann dies durch die Implementierung eines Konstruktors erfolgen, der genau einen String erwartet (siehe z. B. TimestampLabeler).

Nun ist es möglich, den neuen Labeler mit dem RollingFileWriter in einer Properties-Datei zu verwenden.

tinylog.writer = rollingfile
tinylog.writer.filename = log.txt
tinylog.writer.backups = 10
tinylog.writer.label = mypid

Policies

Policies werden vom RollingFileWriter verwendet, um zu bestimmen, wann eine neue Log-Datei begonnen werden soll. Eine eigene Policy muss das Interface Policy implementieren. Da der RollingFileWriter Policies immer synchron aufruft, sind diese automatisch threadsafe.

import java.io.File; import org.pmw.tinylog.Configuration; import org.pmw.tinylog.policies.Policy; public class MyPolicy implements Policy { @Override public void init(Configuration configuration) { // Policy initialisieren } @Override public boolean check(File logFile) { // Überprüfen, ob eine vorhandene Log-Datei fortgesetzt werden kann } @Override public boolean check(String logEntry) { // Überprüfen, ob der Log-Eintrag in die aktive Log-Datei geschrieben werden darf } @Override public void reset() { // Policy nach dem Start einer neuen Log-Datei zurücksetzen } }

So könnte beispielsweise eine Policy aussehen, die eine neue Log-Datei beginnt, sobald die konfigurierbare Dateigröße erreicht ist (eine vereinfachte Variante der vorhandenen SizePolicy):

import java.io.File; import org.pmw.tinylog.Configuration; import org.pmw.tinylog.policies.Policy; public class MySizePolicy implements Policy { private final long maxSize; private long size; public MySizePolicy(final long maxSize) { this.maxSize = maxSize; } @Override public void init(final Configuration configuration) { size = 0L; } @Override public boolean check(final File logFile) { if (logFile.exists()) { size = logFile.length(); return size <= maxSize; } else { return true; } } @Override public boolean check(final String logEntry) { size += logEntry.getBytes().length; return size <= maxSize; } @Override public void reset() { size = 0L; } }

Damit eine Policy nicht nur programmatisch erstellt werden kann, muss diese tinylog bekannt gemacht werden. Dies funktioniert ähnlich, wie man es von Diensten in Java kennt (Stichwort: ServiceLoader). Dazu erstellt man im eigenen Java-Projekt eine Textdatei mit dem Dateinamen "org.pmw.tinylog.policies" im Verzeichnis "META-INF/services". In dieser Textdatei wird der Klassenname (inklusive Package) der neuen Policy eingetragen. Mehrere Policies können durch Zeilenumbrüche getrennt werden.

my.package.MyPolicy

Anschließend muss tinylog noch wissen, über welchen Namen die Policy angesprochen werden soll. Dies erfolgt über die Annotation @PropertiesSupport. Falls die Policy parametrierbar sein soll, kann dies durch die Implementierung eines Konstruktors erfolgen, der genau einen String erwartet.

import java.io.File; import org.pmw.tinylog.Configuration; import org.pmw.tinylog.policies.Policy; import org.pmw.tinylog.policies.PropertiesSupport; @PropertiesSupport(name = "mysize") public class MySizePolicy implements Policy { [...] public MySizePolicy(final long maxSize) { this.maxSize = maxSize; } /* Muss nicht "public" sein */ MySizePolicy(final String maxSize) { this.maxSize = Long.parseLong(maxSize); } [...] }

Nun ist es möglich, die neue Policy mit dem RollingFileWriter in einer Properties-Datei zu verwenden.

tinylog.writer = rollingfile
tinylog.writer.filename = log.txt
tinylog.writer.backups = 10
tinylog.writer.policies = mysize: 8192