The log system

In all the examples in this chapter, we write information in the console using the System.out.println() method. When you implement an enterprise application that is going to execute in a production environment, it's a better idea to use a log system to write debug and error information. In Java, log4j is the most popular log system. In this example, we are going to implement our own log system implementing the producer/consumer concurrency design pattern. The tasks that will use our log system will be the producers and a special task (executed as a thread), which will write the log information into a file, will be the consumer. The components of this log system are:

  • LogTask: This class implements the log consumer that after every 10 seconds reads the log messages stored in the queue and writes them to a file. It will be executed by a Thread object.
  • Logger: This is the main class of our log system. It has a queue where the producers will store the information and the consumer will read it. It also includes the methods to add a message into the queue and a method to get all the messages stored in the queue and writes them to disk.

To implement the queue, as happens with the cache system, we need a concurrent data structure to avoid any data inconsistency errors. We have two options:

  • Use a blocking data structure, which blocks the thread when the queue is full (in our case, it will never be full) or empty.
  • Use a non-blocking data structure, which returns a special value if the queue is full or empty.

We have chosen a non-blocking data structure, the ConcurrentLinkedQueue class, which implements the Queue interface. We use the offer() method to insert elements in the queue and the poll() method to get elements from it.

The LogTask class code is very simple:

  public class LogTask implements Runnable { 
 
    @Override 
    public void run() { 
      try { 
        while (Thread.currentThread().interrupted()) { 
          TimeUnit.SECONDS.sleep(10); 
          Logger.writeLogs(); 
        } 
      } catch (InterruptedException e) { 
    } 
    Logger.writeLogs(); 
  } 
} 

The class implements the Runnable interface and, in the run() method, calls the writeLogs() method of the Logger class every 10 seconds.

The Logger class has five different static methods. First of all is a static block of code that initializes and starts a thread that executes the LogTask and creates the ConcurrentLinkedQueue class used to store the log data:

public class Logger { 
 
  private static ConcurrentLinkedQueue<String>logQueue = new
ConcurrentLinkedQueue<String>(); private static Thread thread; private static final String LOG_FILE = Paths.get("output",
"server.log").toString(); static { LogTask task = new LogTask(); thread = new Thread(task); }

Then, there is a sendMessage() method that receives a string as parameter and stores that message in the queue. To store the message, it uses the offer() method:

public static void sendMessage(String message) { 
  logQueue.offer(new Date()+": "+message); 
} 

A critical method of this class is the writeLogs() class. It obtains and deletes all the log messages stored in the queue using the poll() method of the ConcurrentLinkedQueue class and writes them to a file:

public static void writeLogs() { 
  String message; 
  Path path = Paths.get(LOG_FILE); 
  try (BufferedWriterfileWriter = Files.newBufferedWriter(path,
StandardOpenOption.CREATE,
StandardOpenOption.APPEND)) { while ((message = logQueue.poll()) != null) { fileWriter.write(new Date()+": "+message); fileWriter.newLine(); } } catch (IOException e) { e.printStackTrace(); } }

Finally, two methods: one to truncate the log file and the other to finish the executor of the log system, which interrupts the thread that is executing LogTask:

public static void initializeLog() { 
  Path path = Paths.get(LOG_FILE); 
  if (Files.exists(path)) { 
    try (OutputStream out = Files.newOutputStream(path,
StandardOpenOption.TRUNCATE_EXISTING)) { } catch (IOException e) { e.printStackTrace(); } } thread.start(); } public static void shutdown() { thread.interrupt(); }