package org.cloudname.log.archiver;
import org.cloudname.idgen.TimeProvider;
import org.cloudname.log.pb.Timber;
import java.io.File;
import java.io.IOException;
/**
* This class implements a very simplistic log archiver that will
* archive logs using the Timber format to archive log messages in
* archive slots.
*
* <i> This implementation was originally written for the Timber log
* server but was moved here since we want to use the same code in
* multiple different settings. This is why this log archiver has
* some machinery for managing multiple open slots; in a server
* setting we have to assume that some portion of the log messages
* will be delayed. This code will be revisited and optimized if it
* is deemed necessary.</i>
*
* @author borud
*/
public class Archiver {
private static final int MAX_FILES_OPEN = 2;
private String service = "";
private final SlotMapper slotMapper = new SlotMapper();
private final SlotLruCache<String,Slot> slotLruCache = new SlotLruCache<String,Slot>(MAX_FILES_OPEN);
private final String logPath;
private final long maxFileSize;
private TimeProvider timeProvider;
private boolean closed = false;
/**
* The directory
* @param logPath folder to store logs
* @param service name of the service storing logs
* @param maxFileSize maximum file size in bytes
*/
public Archiver(String logPath, String service, long maxFileSize) {
this(logPath, service, maxFileSize, new TimeProvider() {
@Override
public long getTimeInMillis() {
return System.currentTimeMillis();
}
});
}
/**
* The directory
* @param logPath folder to store logs
* @param service name of the service storing logs
* @param maxFileSize maximum file size in bytes
* @param timeProvider option custom TimeProvider
*/
public Archiver(String logPath, String service, long maxFileSize, TimeProvider timeProvider) {
this.logPath = logPath;
this.service = service;
this.maxFileSize = maxFileSize;
this.timeProvider = timeProvider;
}
/**
* Initialize the archiver. If the logging directory specified in
* the constructor does not exist it will be created.
*/
public void init() {
final File logDir = new File(logPath);
// Make the root log directory if it does not exist
if (! logDir.exists()) {
logDir.mkdirs();
}
}
/**
* Append log event to the appropriate slot file given the
* timestamp of the LogEvent.
*
* @param logEvent the LogEvent we wish to log.
* @throws IllegalStateException if the archiver was closed.
* @throws ArchiverException if an io error occurred when trying
* to write a log event. The original IO exception causing the
* problem will be chained.
* @return WriteReport containing information about the write operation.
*/
public WriteReport handle(final Timber.LogEvent logEvent) {
if (closed) {
throw new IllegalStateException("Archiver was closed");
}
try {
return getSlot(logEvent).write(logEvent);
} catch (IOException e) {
throw new ArchiverException("Got IOException while handling logEvent", e);
}
}
/**
* Ensure that all currently opened slot files are flushed to
* disk.
*
* @throws ArchiverException if an io error occurred when trying
* to flush the slot files. The original IO exception causing
* the problem will be chained.
*
*/
public void flush() {
for (final Slot slot : slotLruCache.values()) {
try {
slot.flush();
} catch (IOException e) {
throw new ArchiverException("Got IOException while flushing " + slot.toString(), e);
}
}
}
/**
* Close the archiver. Closes all the currently open slot files.
* After an Archiver has been closed it cannot be re-opened. Any
* attempt at logging messages to a closed Archiver will result in
* an IllegalStateException.
*
* @throws ArchiverException if an io error occurred when trying
* to flush the slot files. The original IO exception causing
* the problem will be chained.
*/
public void close() {
for (final Slot slot : slotLruCache.values()) {
try {
slot.close();
} catch (IOException e) {
throw new ArchiverException("Got IOException while closing " + slot.toString(), e);
}
}
closed = true;
}
/**
* @return the slot a Timber.LogEvent belongs in.
*/
private Slot getSlot(Timber.LogEvent event) {
String slotPathPrefix = logPath + File.separator + slotMapper.map(event.getTimestamp(), service);
Slot slot = slotLruCache.get(slotPathPrefix);
if (null != slot) {
return slot;
}
slot = new Slot(slotPathPrefix, maxFileSize, timeProvider);
slotLruCache.put(slotPathPrefix, slot);
return slot;
}
}