package org.skywalking.apm.agent.core.logging;
import com.lmax.disruptor.EventFactory;
import com.lmax.disruptor.EventHandler;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.util.DaemonThreadFactory;
import org.skywalking.apm.agent.core.conf.Config;
import org.skywalking.apm.agent.core.conf.Constants;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Callable;
/**
* The <code>FileWriter</code> support async file output, by using a queue as buffer.
*
* @author wusheng
*/
public class FileWriter implements IWriter, EventHandler<LogMessageHolder> {
private static FileWriter INSTANCE;
private static final Object CREATE_LOCK = new Object();
private Disruptor<LogMessageHolder> disruptor;
private RingBuffer<LogMessageHolder> buffer;
private FileOutputStream fileOutputStream;
private volatile boolean started = false;
private volatile int fileSize;
private volatile int lineNum;
public static FileWriter get() {
if (INSTANCE == null) {
synchronized (CREATE_LOCK) {
if (INSTANCE == null) {
INSTANCE = new FileWriter();
}
}
}
return INSTANCE;
}
private FileWriter() {
disruptor = new Disruptor<LogMessageHolder>(new EventFactory<LogMessageHolder>() {
@Override
public LogMessageHolder newInstance() {
return new LogMessageHolder();
}
}, 1024, DaemonThreadFactory.INSTANCE);
disruptor.handleEventsWith(this);
buffer = disruptor.getRingBuffer();
lineNum = 0;
disruptor.start();
}
@Override
public void onEvent(LogMessageHolder event, long sequence, boolean endOfBatch) throws Exception {
if (hasWriteStream()) {
try {
lineNum++;
write(event.getMessage() + Constants.LINE_SEPARATOR, endOfBatch);
} finally {
event.setMessage(null);
}
}
}
private void write(String message, boolean forceFlush) {
try {
fileOutputStream.write(message.getBytes());
fileSize += message.length();
if (forceFlush || lineNum % 20 == 0) {
fileOutputStream.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
switchFile();
}
}
private void switchFile() {
if (fileSize > Config.Logging.MAX_FILE_SIZE) {
forceExecute(new Callable() {
@Override
public Object call() throws Exception {
fileOutputStream.flush();
return null;
}
});
forceExecute(new Callable() {
@Override
public Object call() throws Exception {
fileOutputStream.close();
return null;
}
});
forceExecute(new Callable() {
@Override
public Object call() throws Exception {
new File(Config.Logging.DIR, Config.Logging.FILE_NAME)
.renameTo(new File(Config.Logging.DIR,
Config.Logging.FILE_NAME + new SimpleDateFormat(".yyyy_MM_dd_HH_mm_ss").format(new Date())));
return null;
}
});
forceExecute(new Callable() {
@Override
public Object call() throws Exception {
fileOutputStream = null;
started = false;
return null;
}
});
}
}
private void forceExecute(Callable callable) {
try {
callable.call();
} catch (Exception e) {
e.printStackTrace();
}
}
private boolean hasWriteStream() {
if (fileOutputStream != null) {
return true;
}
if (!started) {
File logFilePath = new File(Config.Logging.DIR);
if (!logFilePath.exists()) {
logFilePath.mkdirs();
} else if (!logFilePath.isDirectory()) {
System.err.println("Log dir(" + Config.Logging.DIR + ") is not a directory.");
}
try {
fileOutputStream = new FileOutputStream(new File(logFilePath, Config.Logging.FILE_NAME), true);
fileSize = Long.valueOf(new File(logFilePath, Config.Logging.FILE_NAME).length()).intValue();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
started = true;
}
return fileOutputStream != null;
}
@Override
public void write(String message) {
long next = buffer.next();
try {
LogMessageHolder messageHolder = buffer.get(next);
messageHolder.setMessage(message);
} finally {
buffer.publish(next);
}
}
}