Flattr tinylog Star

Extend

tinylog can be extended. It is possible to develop own writers. Also, new labelers and policies can be created for the RollingFileWriter. How this works, what should be noted and what options are available is explained in each case using an example.

Writers

A custom writer has to implement the interface Writer and must be thread-safe, because it can and will be called simultaneously from multiple threads. The method getRequiredLogEntryValues() defines which values of the LogEntry are required by the writer and must be set by tinylog. All available values are documented in the enum LogEntryValue.

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() // Define all required values for log entries } @Override public void init(Configuration configuration) { // Allocate resources, such as opening a file } @Override public void write(LogEntry logEntry) { // Output the log entry } @Override public void flush() { // Output buffered log entries, if exist } @Override public void close() { // Free resources, such as closing a file } }

For example, a writer that writes all log entries to a text file by a BufferedOutputStream could be implemented in this way (a simplified variant of the existing 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); // Only the final rendered log entry is required } @Override public void init(final Configuration configuration) throws IOException { stream = new BufferedOutputStream(new FileOutputStream(filename)); VMShutdownHook.register(this); // Make sure that close() will be called during termination of JVM } @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); // Closing of the file is not anymore necessary during termination of JVM stream.close(); } }

A writer must be registered if it should be possible to create it in an other way than programmatically. This works similar to services in Java (keyword: ServiceLoader). First, a text file with the filename "org.pmw.tinylog.writers" must be created in the directory "META-INF/services" in your own Java project. The class name (including package) of the new writer can be added into this text file. Multiple writers can be split by newlines.

my.package.MyFileWriter

Then tinylog has to know how the new writer should be called and which parameters exist. Both can be defined by the annotation @PropertiesSupport. A matching constructor must exist for the defined parameters.

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; } [...] }

Now, it is possible to initialize the new writer by a properties file.

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

Labelers

Labelers are used by the RollingFileWriter to manage log files. A labeler does not just name the log files, but must also handle the deletion of outdated backups. A custom labeler has to implement the interface Labeler. As the RollingFileWriter always calls them synchronously, they are automatically thread-safe.

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) { // Initialize labeler } @Override public File getLogFile(File baseFile, int maxBackups) { // Determine the active log file and delete old log files, if there are too much backups } @Override public File roll(File file, int maxBackups) { // Start a new log file and delete old log files, if there are too much backups } }

For example, a labeler that creates a separate log file for every active instance of an application could be implemented in this way (a simplified variant of the existing 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) If necessary, delete old backups */ deleteOldBackups(baseFile.getParentFile(), maxBackups); /* 2) Put process ID in front of the file name */ postfix = "-" + baseFile.getName(); return new File(baseFile.getParent(), pid + postfix); } @Override public File roll(final File file, final int maxBackups) { /* 1) Delete file if it already exists */ if (file.exists()) { file.delete(); } /* 2) If necessary, delete old backups */ deleteOldBackups(file.getParentFile(), maxBackups); /* 3) Continue with the same filename */ 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) { /* All files that matches with the name schema */ return name.endsWith(postfix); } }); if (files.length > maxBackups) { Arrays.sort(files, new Comparator<File>() { @Override public int compare(final File file1, final File file2) { /* Newest files first */ return Long.valueOf(file2.lastModified()).compareTo(file1.lastModified()); } }); /* Delete the oldest log files */ for (int i = maxBackups; i < files.length; ++i) { files[i].delete(); } } } }

A labeler must be registered if it should be possible to create it in an other way than programmatically. This works similar to services in Java (keyword: ServiceLoader). First, a text file with the filename "org.pmw.tinylog.labelers" must be created in the directory "META-INF/services" in your own Java project. The class name (including package) of the new labeler can be added into this text file. Multiple labelers can be split by newlines.

my.package.MyLabeler

Then tinylog has to know how the new labeler should be called. The name can be defined by the 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 { [...] }

If the labeler should be parameterizable, a constructor that expects exactly one string has to be implemented (see TimestampLabeler for example).

Now, it is possible to use the new labeler with the RollingFileWriter in a properties file.

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

Policies

Policies are used by the RollingFileWriter to determine when a new log file has to be started. A custom policy has to implement the interface Policy. As the RollingFileWriter always calls them synchronously, they are automatically thread-safe.

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) { // Initialize policy } @Override public boolean check(File logFile) { // Determine whether an existing log file can be continued } @Override public boolean check(String logEntry) { // Determine whether the current log entry can be written to the active log file } @Override public void reset() { // Reset the policy after starting a new log file } }

For example, a policy that starts a new log file as soon as a defined file size is reached could be implemented in this way (a simplified variant of the existing 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; } }

A policy must be registered if it should be possible to create it in an other way than programmatically. This works similar to services in Java (keyword: ServiceLoader). First, a text file with the filename "org.pmw.tinylog.policies" must be created in the directory "META-INF/services" in your own Java project. The class name (including package) of the new policy can be added into this text file. Multiple policies can be split by newlines.

my.package.MyPolicy

Then tinylog has to know how the new policy should be called. The name can be defined by the annotation @PropertiesSupport. If the policy should be parameterizable, a constructor can be implemented that expects exactly one string.

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; } /* Does not necessarily to be public */ MySizePolicy(final String maxSize) { this.maxSize = Long.parseLong(maxSize); } [...] }

Now, it is possible to use the new policy with the RollingFileWriter in a properties file.

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