Tag Archives: logging

Collecting java.util.logging to log4j2

Everybody wants to write a log. And in Java everybody wants to write their own logging framework or at least use of the many different ones. Then someone comes up with logging framework framework such as SLF4J.

OK but what was I about to say. As so many times, I had a piece of Java software writing a log file using Log4J2. I was using some libs/someone elses code that uses java.util.logging to write their log. I wanted to capture those logs and include them in my Log4J2 log file for debugging, error resolution or whatever.

This case was when trying to log errors from the InfluxDB Java driver. The driver uses java.util.logging for minimal external dependencies or something. I used Log4J2 in my app.

So the usual question of how do you merge java.util.logging code, that you do not control, with your own code using Log4J2 to produce a single unified log file?

Most Googling would tell me all about SLF4J etc. I did not want yet-another framework on top of existing frameworks, and yet some more (transitive) dependencies and all sorts of weird stuff. Because I am old and naughty and don’t like too many abstractions just because.

So the code to do this with zero external dependencies.

First a log Handler object for java.util.logging to write to Log4J2:

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;

/**
* @author Daddy Bigbelly.
*/
public class JekkuHandler extends Handler {
//notice that this is the Log4J2 logger here, inside a java.util.logging Handler object
private static final Logger log = LogManager.getLogger();

  @Override
  public void publish(LogRecord record) {
    Level level = record.getLevel();
    if (level.intValue() == Level.SEVERE.intValue()) {
      log.error(record.getMessage(), record.getThrown());
    } else if (level.intValue() >= Level.INFO.intValue()) {
      log.info(record.getMessage(), record.getThrown());
    } else {
      log.debug(record.getMessage(), record.getThrown());
    }
  }

  @Override
  public void flush() {}

  @Override
  public void close() throws SecurityException {}
}

Next setting it up and using it, with the InfluxDB Java driver as an example:

import org.influxdb.InfluxDB;
import org.influxdb.InfluxDBFactory;
import org.influxdb.dto.BatchPoints;
import org.influxdb.dto.Point;
import org.influxdb.dto.Query;
import org.influxdb.impl.BatchProcessor;

import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

/**
* @author Daddy Bigbelly.
*/

public class LogCaptureExample {
  public static void main(String[] args) throws Exception {
    //oh no the root password is there
    InfluxDB db = InfluxDBFactory.connect("http://myinfluxdbhost:8086", "root", "root");
    String dbName = "aTimeSeries";
    db.createDatabase(dbName);
    db.enableBatch(2000, 1, TimeUnit.SECONDS);

    //if you look at the influxdb driver code for batchprocessor, 
    //where we wanted to capture the log from, you see it using the classname to set up the logger. 
    //so we get the classname here and use it to hijack the writes for that logger (the one we want to capture)
    System.out.println(BatchProcessor.class.getName());
    Logger logger = Logger.getLogger("org.influxdb.impl.BatchProcessor");
    Handler handler = new JekkuHandler();
    logger.addHandler(handler);

    //this runs forever, but the batch mode can throw an error if the network drops.
    //so disconnect network to test this in middle of execution
    while (true) {
      Point point1 = Point.measurement("cpu")
        .time(System.currentTimeMillis(), TimeUnit.MILLISECONDS)
        .addField("idle", 90L)
        .addField("user", 9L)
        .addField("system", 1L)
        .build();
      db.write(dbName, "autogen", point1);
    }
  }
}

You could probably quite easily configure a global java.util.logger that would capture all logging written with java.util.logging this way. I did not need it so its not here.

In a similar way, you should be able to capture java.util.logging to any other log framework just by changing where the custom Handler writes the logs to.

Well there you go. Was that as exciting for you as it was for me?

programmatic jdk logging configuration

something so wrong with all this being so hard.. so lets try to document a few words

problem:how do you programmatically configure the JDK logging so you get two different log streams, one to standard output and another to a file? everything i tried was either outputting everything or nothing.

trying to search for a solution you come up with all sorts of weird stuff. some of these shortly.

logger.setUseParentHandlers(false);

well, the line above should stop the logging hierarchy from spouting stuff through the global loggers that are supposedly always there. not that this ever did anything for me, but the whole thing with the “” name for the root logger and default configurations is just weird. so I am still using the above line just to be sure but whatever.

another attempt

LogManager.getLogManager().reset();
for (Handler handler : java.util.logging.Logger.getLogger(“”).getHandlers()) {
handler.setLevel(Level.OFF);
}

so some claim this will remove any excess loggers that are in by default. did nothing for me. similarly tried to remove all handlers on startup. did nothing for me.

ok so use the consolehandler, which sort of works

logger = java.util.logging.Logger.getLogger(name);
logger.setUseParentHandlers(false);
logger.setLevel(Level.ALL);
Handler console = new ConsoleHandler();
//custom formatter
console.setFormatter(new LogFormatter());
console.setLevel(Level.INFO);
logger.addHandler(console);

So, first of all, the logger should have the level Level.ALL since it will block any messages below this level from reaching the handlers attached to it. So is is the first level of filtering. The second level are the attached handlers.

Now, with the above the loggers actually outputs the stuff and only the INFO level stuff and on the console. But on the stderr, the System.err stream. I want stdout, the System.out stream. And how do you fix that?

Lots of people suggest using

Handler console = new StreamHandler(System.out, new LogFormatter());

Which just prints some of the stuff and not others. Probably messes up the stream in some handy way. whooppee.

Finally, this works

/** This is where the log messages are written. */
private PrintStream out = System.out;
/** For formatting the log statements. */
private LogFormatter formatter = new LogFormatter();

@Override
public void publish(LogRecord record) {
if (record.getLevel().intValue() < getLevel().intValue()) {
return;
}
out.print(formatter.format(record));
}

@Override
public void flush() {
//system.out handles itself
}

@Override
public void close() throws SecurityException {
//system.out handles itself
}

That is, create your own handler with the code above and configure that to your logger. It’s magic.

As for the file stream, the FileLogHandler from the JDK works fine and you just add it similarly to the examples above.