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