contact@letor.ca • (416) 803–3050
June 11, 2009

Single line Java Logging

Before Java Logging API became part of the JDK (1.4) the de facto standard was the Log4J logging library. It was customizable, easy to setup and configure.

Along came Java 1.4 and the new Java Logging API. Somewhat clumsy and much harder to configure or customize, it has not changed over the course of the subsequent releases.

One of the most annoying things about it was that out of the box it would write out the log message on two lines:

Jun 11, 2009 9:11:21 PM com.letor.example.logging.StandardTest 
INFO: Line 1
Jun 11, 2009 9:11:21 PM com.letor.example.logging.StandardTest 
INFO: Line 2
Jun 11, 2009 9:11:21 PM com.letor.example.logging.StandardTest 
INFO: Line 3

Now, I don't know about you, but I find this very hard on my eyes. It is much harder to get a feel for how many messages were printed, what is the difference between them and is significantly harder to spot what you are looking for.

To rectify this, we can use the following simple, single-line formatter.

You will need to create two files - a configuration file:

logging.properties
# log everything to the console
handlers = java.util.logging.ConsoleHandler

# set the default logging level for the root logger
.level = FINE
    
# set the default logging level for new ConsoleHandler instances
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = com.letor.util.SingleLineFormatter
    
# set the default logging level for the logger named com.mycompany
com.letor.level = INFO

And the Formatter class used for formatting the log record

SingleLineFormatter.java
package com.letor.util;

import java.text.SimpleDateFormat;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;

public class SingleLineFormatter extends Formatter
{
    private static SimpleDateFormat FRMT_DATE;
    
    public SingleLineFormatter()
    {
        // check if the date format was specified
        // by default this is used for quick and dirty runs, don't care about the year date
        String dateFormat = System.getProperty("java.util.logging.dateFormat", "HH:mm:ss"); 
        FRMT_DATE = new SimpleDateFormat(dateFormat);
    }
    
    public String format(LogRecord record)
    {
        // use the buffer for optimal string construction
        StringBuffer sb = new StringBuffer();

        // level
        sb.append(record.getLevel().toString().toLowerCase());
        sb.append(": ");

        // format time
        sb.append(FRMT_DATE.format(record.getMillis())).append(" ");

        // thread
        sb.append("[").append(Thread.currentThread().getName()).append("] ");

        // package/class name, logging name
        String name = record.getLoggerName();
        if (name.startsWith("com.letor.")) // truncate the logging name, reduce the clutter
            name = name.substring("com.letor.".length());

        sb.append(name);
        sb.append("   ");
        sb.append(record.getMessage());
        
        // if there was an exception thrown, log it as well
        if (record.getThrown() != null)
        {
            sb.append("\n").append(printThrown(record.getThrown()));
        }

        sb.append("\n");

        return sb.toString();
    }
    
    private String printThrown(Throwable thrown)
    {
        StringBuffer sb = new StringBuffer();
        
        sb.append("").append(thrown.getClass().getName());
        sb.append(" - ").append(thrown.getMessage());
        sb.append("\n");

        for (StackTraceElement trace : thrown.getStackTrace())
            sb.append("\tat ").append(trace).append("\n");

        Throwable cause = thrown.getCause();
        if (cause != null)
            sb.append("\n").append(printThrown(cause));
        
        return sb.toString();
    }
}

You will need to pass the configuration file location as a VM argument to your application.

java -Djava.util.logging.config.file=src/logging.properties your.package.YourApplication

Running this will result in the following output

info: 21:25:00 [main] example.logging.StandardTest   Line 1
info: 21:25:00 [main] example.logging.StandardTest   Line 2
info: 21:25:00 [main] example.logging.StandardTest   Line 3

Now that's much nicer! Feel free to modify the Java file to your preferred format.

The SingleLineFormatter is also capable of logging thrown exceptions and nested exceptions. Here is an example output:

info: 11 Jun 21:29:03 [main] example.logging.Test   Nice, clean single line output
severe: 11 Jun 21:29:03 [main] example.logging.Test   Divided by zero
java.lang.ArithmeticException - / by zero
	at com.letor.example.logging.Test.(Test.java:21)
	at com.letor.example.logging.Test.main(Test.java:37)

severe: 11 Jun 21:29:03 [main] example.logging.Test   Nested operation threw an exception
java.lang.RuntimeException - Parent exception
	at com.letor.example.logging.Test.(Test.java:26)
	at com.letor.example.logging.Test.main(Test.java:37)

java.lang.ArithmeticException - / by zero
	at com.letor.example.logging.Test.(Test.java:21)
	at com.letor.example.logging.Test.main(Test.java:37)

Hopefully this will your Java Logging experience.