Wiki

Clone wiki

Okapi / OkapiLogging

Introduction

For a long time the logging in okapi used the java.util.logging. But as okapi started being used in all kind of other products, both server and desktop, it became more and more necessary to allow for more flexibility, so that we can accommodate the logging in the hosting environment.

Also, having more flexibility in specifying what to log and what not was also something we would have liked.

So we looked at log4j (for flexibility), and Apache commons logging (for “swapping” the logger), but in the end we decided to go with slf4j. How we got to that decision is another story, and it is not the something for this document :-)

What we wanted

  • The flexibility to plug in whatever logging system one would want, and that is achieved by slf4j.
  • To be able to enable/disable logging per component at build time, to reduce the noise. And we got that by binding slf4j to log4j for building.
  • We wanted to allow the developers using the libraries to bind slf4j to something else, whatever they want.
  • To cause minimal/no disruption to the developers using the libraries, or the users of the okapi applications, so we decided to bind slf4j to the JDK logger for the end result. But we used log4j at build time. As you can imagine, this complicates things a bit.

Java class loading and log binding

I will not explain here Java the complete mechanism used by Java to find classes. The relevant part for us is the behavior of specifying a classpath in a jar manifest (with Class-Path), versus a CLASSPATH environment variable or a the -classpath command line:

  • it does not accept wildcards, so one cannot specify “everything in this folder”
  • if specified, it overrides both the command line, and the CLASSPATH environment variable

And traditionally the okapi libraries list all the libraries needed in a manifest.

On the other side, slf4j binds to whatever logging system it finds in the classpath.

So we needed a way to allow one to change the binding without modifying the original okapi jar files.

We did that by introducing logbind.jar in the lib/logger folder. All okapi jars that need logging list it as a dependency, and that jar is the one the manifest, listing the logging components to be used for binding.

Okapi users

Customizing default (JDK) logging

By default the logging of the official okapi release uses the JDK loggers. It is controlled by the logging.properties file in the lib subfolder of the JRE.

You can specify an alternate one using -Djava.util.logging.config.file=log.properties in your java command line.

In the config file you can change logging level, handlers, logging pattern, etc.

Using another logging system (log4j)

Warning: tikal and rainbow “listen” to the logging in order to present it in a more user-friendly manner. For this we had to work around the slf4j limitations and talk directly with the underlying logger. This means we had to write logger dependent code. And we only did it for the JDK logger and log4j only. So even if slf4j allows for other bindings, okapi is limited to these two (for now).

How to change it?

In the lib/logger folder you will find logbind.jar.

It’s manifest (META-INF/MANIFEST.MF) has a Class-Path, and by default it contains binding to the current path and the JDK logger:

Class-Path: . slf4j-jdk14-1.7.1.jar

If you want to bind to something else, change it to contain all the libraries used for binding. That means the slf4j binding library, and the libraries of the logger proper (see http://www.slf4j.org/manual.html)

So this is what you would need in order to use log4j:

Class-Path: . slf4j-log4j12-1.7.1.jar log4j-1.2.17.jar

You can add the log4j.properties file in the logger folder, or in logbind.jar (in the root).

If you want color output, you can download the color-loggers library from http://github.com/mihnita/java-color-loggers/downloads and add it to the classpath:

Class-Path: . slf4j-log4j12-1.7.1.jar log4j-1.2.17.jar color-loggers-1.0.2.jar

And change the appender:

log4j.appender.A1=com.colorlog.log4j.AnsiColorConsoleAppender

And (of course) make sure that the jar files mentioned in the manifest exist in the lib/logger folder.

Developers building the okapi libraries

If you want to build okapi from sources, the logging at build time uses log4j.

I would assume the most common case is that you want to keep logging with log4j, but want to change the logging level. And you might want to do that globally, or for a component.

You can fine tune the logging level for various components by changing the root logging level and the level for various namespaces.

Example:

log4j.rootLogger=ERROR, A1
log4j.logger. net.sf.okapi.filters.openoffice=TRACE
log4j.logger.net.sf.okapi.filters.openoffice.ui=TRACE

This will hide all log entries for all components except for the errors, but will enable logging all the way to trace level for everything related to the openoffice filter.

Customizing logging for command line builds

Most of these are variations on the same question: how to force java to use the log4j configuration file I want?

There are several ways to do this, and which one do you choose might depend on your style. I use option 1, on Windows.

  1. You only see logging output during the unit tests steps of the build. Since we do this with maven, you can specify an alternate log4j configuration file in MAVEN_OPTS:

Windows example:

set MAVEN_OPTS=%MAVEN_OPTS% "-DargLine=-Dlog4j.configuration=file:C:/MyLogger/okapi/log4j.properties"

Mac/Linux example (not tested):

MAVEN_OPTS=$MAVEN_OPTS% "-DargLine=-Dlog4j.configuration=file:/MyLogger/okapi/log4j.properties"

Note that this is not an argument to pass to the JVM running maven, but an argument that maven should pass to the JVM that runs the tests, hence -DargLine=... instead of just –Dlog4j.configuration=...

This will affect all modules.

  1. Another option is to change default log4j.properties

The binding is done using the okapi\deployment\logbind-log4j module, which is (almost) manifest-only. The manifest lists slf4j-log4j12-1.7.1.jar and log4j-1.2.17.jar in Class-Path and also contains the default log4j.properties file.

So you can change the file in okapi/deployment/logbind-log4j/src/main/resources folder and build. But you will have to make you don’t submit it by mistake :-)

This will affect all modules.

  1. Yet another option is to change the configuration file in the local maven repository (repository/net/sf/okapi/logbind/build-log4j/{okapiVersion}). You can find it inside the jar, or take it out.

This will affect all modules, and you will lose your changes if logbind-log4j gets rebuilt.

  1. And the last option listed is to add a config file to the module you are debugging. It should be in the target/test-classes or target/classes folder, and should be named log4j.properties.

In general I find that finding the log4j configuration file can be pretty tricky. At times I am using a file activity monitoring application that can list where does log4j try to open a log4j.properties file (the tool on Windows is procmon, from Windows Sysinternals suite).

You might even have a global (system wide) log4j.properties by packing it in a jar and put it in an extension folder, like %SystemRoot%\Sun\Java\lib\ext on Windows or /usr/java/packages/lib/ext on Linux (see http://docs.oracle.com/javase/tutorial/ext/basics/install.html)

Customizing logging for Eclipse builds

I would assume that you already have you run configurations working in Eclipse.

All you have to do is add a folder containing log4j.properties to the classpath.

Step 1: Create a “Classpath Variable”

For more flexibility you can also use a “Classpath Variable” instead of a folder. This would cut some folder navigation (if you have a lot of run configurations) and will allow you to update things in one place.

From Eclipse main menu select “Window” -> “Preferences”.

In the “Preferences” window navigate to “Java” -> “Build Path” -> “Classpath Variables”

Click the “New” button, give the variable a name (let’s say OKAPI_BUILD_LOG), click “Folder” and navigate to the folder that contains your custom log4j.properties, and click “OK”

Step 2: Add the classpath variable to your run configuration classpath

In the Eclipse main menu select “Run” -> “Run Configurations...”, then select “Classpath” tab. Click “User Entries” so that the buttons on the right get enabled, and click “Advanced...”.

Select the “Add Classpath Variable” radio button and click “OK”, select your variable (OKAPI_BUILD_LOG in our example) and click “OK” again.

The trick is to make sure that your log4j.properties is found before the one in build-log4j-{version}.jar (in the local maven repository). So select the newly added entry (OKAPI_BUILD_LOG) in the “User Entries” and click the “Up” button until it gets before the “Maven Dependencies” entry.

This will affect everything for that run configuration.

Bonus step: color logging

1. Install the “ANSI in Eclipse Console” plugin from http://www.mihai-nita.net/eclipse/

Because I prefer a dark environment, so from the “Preferences” dialog I go to “Run/Debug” – “Console” and change the “Background color” to black and “Standard Out text color” to light gray. Then also change the “Background color” for “Ansi Console” to black.

2. Download the color-loggers.jar from https://github.com/mihnita/java-color-loggers/downloads and save it somewhere (I keep it in the same folder as the log4j.properties)

3. Create a “User Library”

From Eclipse main menu select “Window” -> “Preferences”.

In the “Preferences” window navigate to “Java” -> “Build Path” -> “User Libraries”

Click the “New” button, give the library a name (let’s say ColorLog4J) and click “OK”

Select it, click “Add JARs...”, navigate to contains color-loggers.jar, select it, and click “OK”

Click “OK” again to close the “Preferences” dialog.

4. Add the color logger to Classpath

In the Eclipse “Run Configurations...” dialog, “Classpath” tab, “User Entries” section (same as before) click “Advanced...”, select the “Add Library” radio button, and click “OK”

In the “Add Library” dialog select “User Library”, check the checkbox of ColorLog4J and click “Finish”

5. Update the log4j to use the color logger

Change the log4j appender to use com.colorlog.log4j.AnsiColorConsoleAppender:

log4j.appender.A1=com.colorlog.log4j.AnsiColorConsoleAppender

Updated