/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.f1x.log.file;
import org.f1x.api.session.SessionID;
import org.f1x.log.LogFormatter;
import org.f1x.util.TimeSource;
import java.io.*;
/**
* Start a new log file when size of current file exceeds given maximum.
*
* NOTE: to minimize file switching effect on logging clients, this implementation switches to the next file asynchronously.
* More specifically it happens during asynchronous flush. As result it is possible that length of each file will exceed given maximum.
*/
public class RollingFileMessageLog extends PeriodicFlushingMessageLog {
private final OutputStreamFactory streamFactory;
private final long bytesPerFile;
private volatile long bytesWritten; // updated under OutputStreamMessageLog.lock
private final File [] logFiles;
/**
* @param timeSource time source used for formatting timestamps
* @param bytesPerFile Logger will roll to the next file once current log file size exceeds this limit (in bytes).
*/
public RollingFileMessageLog(File [] logFiles, OutputStreamFactory streamFactory, TimeSource timeSource, long bytesPerFile) {
super(streamFactory.create(logFiles[0]), timeSource);
this.logFiles = logFiles;
this.streamFactory = streamFactory;
this.bytesPerFile = bytesPerFile;
}
/**
* @param bytesPerFile Logger will roll to the next file once current log file size exceeds this limit (in bytes).
*/
public RollingFileMessageLog(File [] logFiles, OutputStreamFactory streamFactory, LogFormatter formatter, long bytesPerFile) {
super(streamFactory.create(logFiles[0]), formatter);
this.logFiles = logFiles;
this.streamFactory = streamFactory;
this.bytesPerFile = bytesPerFile;
}
@Override
public void log (boolean isInbound, byte[] buffer, int offset, int length) {
try {
synchronized (lock) {
if (formatter != null) {
bytesWritten += formatter.log(isInbound, buffer, offset, length, os);
} else {
os.write(buffer, offset, length);
bytesWritten += length;
}
}
} catch (IOException e) {
LOGGER.error().append("Error writing FIX message into the log.").append(e).commit();
}
}
/**
* @param sessionID identifies session for this log
* @param timeSource time source used for formatting timestamps
* @param flushPeriod This setting determines how often logger flushes the buffer (in milliseconds). Negative or zero value disables periodic flushing.
*/
@Override
public void start(SessionID sessionID, TimeSource timeSource, int flushPeriod) {
if (flushPeriod > 0) {
flusher = new RollingFlusher(sessionID, timeSource, flushPeriod);
flusher.start();
}
}
private class RollingFlusher extends Flusher {
private int fileIndex = 0;
private long bytesBeforeNextFile;
protected RollingFlusher(SessionID sessionID, TimeSource timeSource, int flushPeriod) {
super(sessionID, timeSource, flushPeriod);
bytesBeforeNextFile = RollingFileMessageLog.this.bytesPerFile;
}
@Override
protected void onFlushComplete() {
if (bytesBeforeNextFile > 0 && bytesWritten >= bytesBeforeNextFile) {
final OutputStream next = nextOutputStream();
final OutputStream prev = os;
synchronized (lock) {
os = next;
}
safeClose(prev);
bytesBeforeNextFile += bytesPerFile;
}
}
private OutputStream nextOutputStream() {
File nextFile = logFiles[++fileIndex % logFiles.length]; // accessed only by Flusher thread
return streamFactory.create(nextFile);
}
}
}