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