Logging – it can get really messy

There are different ways to create a log in Java

Recently, I have read quite interesing article regarding logs in Java – Logger to też jest biznes. I guess, it summarize experience of most of the people who work with Java code. I guess, most of us went throuhg different means of logging: System.out, Apache Log4j, Java platform based Logger, Apache Commons Logging, etc. We all have already been there. I bet, everyone has worked with different logger at least once during his/hers career. But it can get even worse.

Native code jumps in

There is even more into the game when you start embed native code calls inside Java code. And, there is a new player to the game, every time you call your Java from a native application (e.g. SOAP based calls from GSOAP based code). These are the places where real headache starts when it comes to Grand Logger Unification.

System.out is not always what you think it is

In general, all stdout streams (in different languages) are a little bit different things. System.out, stdout – in C, unit 6 – in Fortran, sys.stdout – in Python. All of these may behave slightly different and catching and piping data into one, unified logger might be a tricky thing to do. Remember that most of the legacy code, in C/Fortan, will typicaly print messages directly to stdout. You have to handle this situation somehow.

Getting rid of debug code – Apache FreeMarker to the rescue

If you code in C, you are probably familiar with preprocessor commands that can throw parts of the code away from your sources. It means that the code is really not there, in the final binary. This is something different comparing to logging with DEBUG level. In this case, there is no chance to accidentally call part of the code (e.g. by changing logger’s settings). This is just because of the fact, that code is not there. This might be useful in case you want to make sure nobody sees your “@#$%*&” messages – even accidentally.

You can easily simulate this kind of behavior with Apache FreeMarker.

Take a look below

import freemarker.template.*;
import java.util.*;
import java.io.*;

public class Parser {

  public static void main(String[] args) throws Exception {

    if(args.length != 2) {
      System.out.println("You have to specify source file and it's location");
      System.out.println("This way, you can easily parse lots of files with find");
      System.out.println("");
      System.out.println("find src -name \"*.java\" \\");
      System.out.println("-exec basename {} \\; \\");
      System.out.println("-exec dirname {} \\; \\");
      System.out.println("| tr '\\n' ' ' | xargs java -cp .:./freemarker.jar Parser");
      System.exit(1);
    }

    Configuration cfg = new Configuration(Configuration.VERSION_2_3_27);
    cfg.setDirectoryForTemplateLoading(new File(args[1]));
    Template temp = cfg.getTemplate(args[0]);

    Writer out = new OutputStreamWriter(System.out);
    Map root = new HashMap();

    root.put("debug", new Boolean(true));
    temp.process(root, out);

    root.put("debug", new Boolean(false));
    temp.process(root, out);
  }
}

and let’s say, we have src structure like this

src
`-- Test.java

and Test.java file looking like this

public class Test {
  // Debugger is set to: ${debug?c}

  public static void main(String [] arg) {
    /*<#if debug>*/ System.out.println("DEBUG message"); /*</#if>*/
    System.out.println("This code is always executed");
  }
}

we can easily generate debug/release versions of the file following way

> find src -name "*.java" \
-exec basename {} \; \
-exec dirname {} \; \
| tr '\n' ' ' | xargs java -cp .:./freemarker.jar Parser

It’s worth noting, that source file itself (Test.java) is a valid Java code. It means, we don’t destroy things to get FreeMarker features.

You can also find quite interesting approach to the topic here: Making fun with Java and C preprocessor. This is quite different approach as we end up with broken Java code. Still, if you generate your code automatically, it might be the case you don’t care much whether code compiles or not before it goes through the preprocessor.

And remember, that sometimes, printf is not a bad idea

A few well-chosen test cases and a few print statements in the code may be enough.

Some programs are not handled well by debuggers: multi-process or multi-thread programs, operating systems, and distributed systems must often be debugged by lower-level approaches. In such situations, you’re on your own, without much help besides print statements and your own experience and ability to reason about code.

— The Practice of Programming – Brian W. Kernighan and Rob Pike