I have implemented a socket listener that runs on my Linux Ubuntu server accepting connections and then starting up a new thread to listen on those connections (classic socket listener approach). The socket listener is persistant and the way it is restarted is by restarting the server.
I have implemented a logging feature in the code so that information such as the data received, any events that have occurred etc are logged to a file. All threads log to this same file and the file is named by the day it's on. So I end up with log files such as 2012-May-01.txt
, 2012-May-02.txt
etc etc.
In order to provide this logging, I implemented a couple of classes to handle the logging.
EventLog
- Just a wrapper to hide away my actual logger and what is used by the various threads etc for loggingLogstream
- The class that does the logging to file
I'm a bit concerned about this class and ensuring I capture all log events. As it can be called by multiple threads I do not want to lose any information.
I'm pretty sure logging is such a common issue so I'm hoping someone can point out the issues in this code and provide a much more robust solution. I am potentially going to look into other existing logging solutions as suggested by sudmong, but for now this is the easiest for me to get my head around (assuming it's a valid solution).
Important: I want to keep the facility to log items in a file naming convention that fits the day the item was logged. I don't want one log file that spans multiple days etc
EDIT: Following from X-Zero's comments I've changed my logging mechanism to use a BlockingQueue
approach. Does this seem like a scalable solution if I was to look at logging perhaps up to 100 lines per second? The main issue I can see is perhaps the opening and closing of my log file for every log write and whether I should catch the FileWriter
handle and only re-create on each new day?
// Class interface through which all logging occurs
public class EventLog {
private static Logstream _eventLog;
public static Logstream create(BlockingQueue<StringBuilder> queue, String rootPath, int port)
{
_eventLog = new Logstream(queue, rootPath, port);
return _eventLog;
}
public static Logstream create(String rootPath, int port)
{
return create(new LinkedBlockingQueue<StringBuilder>(), rootPath, port);
}
public static void write(Exception ex, String cls, String func)
{
write("Exception: " + ex.toString(), cls, func);
}
public static void write(String msg, String cls, String func)
{
_eventLog.write(msg, cls, func);
}
}
And the actual logging class:
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.text.SimpleDateFormat;
public class Logstream implements Runnable {
private final String _path; // The root path we will use for logging
private final int _port; // the port of the application using this logger
private final BlockingQueue<StringBuilder> _queue; // Used to queue up messages for logging
private final Thread _writeThread; // Thread to run this logstream on
private int _dayOfMonth = -1; // The current day of the month
private String _cachedPath = ""; // Our log path. Changes for each day of the month
protected Logstream(BlockingQueue<StringBuilder> queue, String rootPath, int port) {
_port = port;
_path = rootPath;
_queue = queue; // LinkedBlockingQueue<StringBuilder>();
_writeThread = new Thread(this);
_writeThread.start();
}
public void write(String msg, String cls, String func)
{
queue(msg, cls, func);
}
public void run()
{
// logging never stops unless we restart the entire server so just loop forever
while(true) {
try {
StringBuilder builder = _queue.take();
flush(builder);
} catch(InterruptedException ex) {
flush(ex.toString());
System.out.println("Exception: LogStream.run: " + ex.getMessage());
}
}
}
private void flush(StringBuilder builder) {
flush(builder.toString());
}
private void flush(String data) {
BufferedWriter writer = null;
try {
System.out.println(data);
writer = getOutputStream();
writer.write(data);
writer.newLine();
writer.flush();
}
catch(IOException ex) {
// what to do if we can't even log to our log file????
System.out.println("IOException: EventLog.flush: " + ex.getMessage());
}
finally {
closeOutputStream(writer);
}
}
private boolean dayOfMonthHasChanged(Calendar calendar) {
return calendar.get(Calendar.DAY_OF_MONTH) != _dayOfMonth;
}
private String getPath() {
Calendar calendar = Calendar.getInstance();
if(dayOfMonthHasChanged(calendar)) {
StringBuilder pathBuilder = new StringBuilder();
SimpleDateFormat df = new SimpleDateFormat("dd-MMM-yy");
Date date = new Date(System.currentTimeMillis());
_dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
pathBuilder.append(_path);
pathBuilder.append(df.format(date)).append("_");
pathBuilder.append(_port);
pathBuilder.append(".txt");
_cachedPath = pathBuilder.toString();
}
return _cachedPath;
}
private BufferedWriter getOutputStream() throws IOException {
return new BufferedWriter(new FileWriter(getPath(), true));
}
private void closeOutputStream(BufferedWriter writer) {
try {
if(writer != null) {
writer.close();
}
}
catch(Exception ex) {
System.out.println("Exception: LogStream.closeOutputStream: " + ex.getMessage());
}
}
private StringBuilder queue(String msg, String cls, String func) {
Date date = new Date(System.currentTimeMillis());
// $time . ": " . $func . ": " . $msg ."\n"
StringBuilder msgBuilder = new StringBuilder();
msgBuilder.append(new SimpleDateFormat("H:mm:ss").format(date));
msgBuilder.append(": ");
msgBuilder.append(cls);
msgBuilder.append("->");
msgBuilder.append(func);
msgBuilder.append("()");
msgBuilder.append(" :: ");
msgBuilder.append(msg);
try {
_queue.put(msgBuilder);
}
catch (InterruptedException e) {
flush(new StringBuilder(e.toString()));
flush(msgBuilder);
}
return msgBuilder;
}
}
So I created a unit test to check if overloading the log writing with multiple writes at once will ensure all events get written. I toggled between using two blockingqueue approaches.
ArrayBlockingQueue
, with capacity set to 1LinkedBlockingQueue
When using these I noticed that LinkedBlockingQueue
could not keep up, where as ArrayBlockingQueue
seemed to work 100% of the time. The LinkedBlockingQueue
would often get to about 100 and stop outputing lines to the console where as the ArrayQueue
would go through the full 1000.
My unit test code was:
@Test
public void testLogStreamArrayBlockingQueue() {
EventLog.create(new ArrayBlockingQueue<StringBuilder>(1), "", 1);
for(int i = 0; i < 1000; i++) {
EventLog.write("ArrayBlockingQueue: " + i, "EventLogUnitTest", "testLogStreamArrayBlockingQueue");
}
Assert.assertTrue(true);
}
@Test
public void testLogStreamLinkedBlockingQueue() {
EventLog.create(new LinkedBlockingQueue<StringBuilder>(), "", 1);
for(int i = 0; i < 1000; i++) {
EventLog.write("LinkedBlockingQueue: " + i, "EventLogUnitTest", "testLogStreamLinkedBlockingQueue");
}
Assert.assertTrue(true);
}
Vector
is probably an implementation of Blocking Queue, which is threadsafe. Just let it block ontake()
until something is returned, and you're golden on the consumer end. \$\endgroup\$