Logging with BFO

The release of 2.11.6 of the BFO PDF library sees the introduction of a consolidated logging API that can be integrated into customer's existing logging systems.

Background

Prior to 2.11.6 logging from the BFO PDF library would simply write warnings (and debug, if enabled) to System.err. When things were running smoothly this would generate little or no output, but dodgy PDFs could throw a large number of messages, and although these could always be turned off, there was no way to manage the logging in more detail.

The new Logging API

Fixing this required some flexibility. Some of our API's still run on Java 1.3, so we can't rely on java.util.logging, and some customers prefer to use Log4j instead. Apache Commons logging is broken by design, so we've written our own wrapper which will log to Log4j if it's available and configured, falling back to java.util.logging if it's availabe or System.err if it's not.

Logging with Log4J

If Log4J is available and a log4j.properties file configured, BFO libraries will use it. If you've installed it we'll assume you know how to configure it, so we won't cover that here in too much detail - as a quick example, here's an example log4j.properties that would write output to the console.

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=\
    %d{yyyy-MM-dd HH:mm:ss} %p %m %x%n
log4j.rootLogger=debug, stdout

This would log all warnings, but in the event you want individual warning messages turned off or changed to a different level you can do that too.

log4j.logger.org.faceless.pdf2.warning.PD6 = OFF
log4j.logger.org.faceless.pdf2.warning.RD4 = FATAL

This would disable the "PD6" warnings and make RD4 warnings fatal. As you can see, all warnings from the PDF library wil be in the "org.faceless.pdf2" package, which makes them easy to redirect to a separate file if necessary.

If you prefer to use the XML method for configuring Log4J, that will work too. The configuration file is called log4j.xml rather than log4j.properties - the equivalent of the above example is this:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

 <appender name="stdout" class="org.apache.log4j.ConsoleAppender">
  <param name="threshold" value="debug" />
  <layout class="org.apache.log4j.PatternLayout">
   <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %p %m %x%n" />
  </layout>
 </appender>

 <category name="org.faceless.pdf2.warning.PD6">
  <priority value="off"/>
 </category>

 <category name="org.faceless.pdf2.warning.RD4">
  <priority value="fatal"/>
 </category>

 <root>
  <priority value="debug" />
  <appender-ref ref="stdout" />
 </root>
</log4j:configuration>

Logging with java.util.logging

The java.util.logging API was added in Java 1.4, although it has a number of problems by design which make this package next to useless out of the box. Some highlights are:

  • Classloader wide configuration, which makes logging multiple threads impractical without extending.
  • Really ugly default formatting of messages
  • An inability to write to multiple files or change this formatting easily

However, as it is supplied with Java 1.4 we need to work around these points. Customers deploying in a servlet environment will typically have two of these points fixed by the servlet engine. Apache Tomcat, for example, provides their own JULI logging framework which will build on the default, however configuration can be cryptic.

You can configure JULI logging in a couple of ways, but while getting started the simplest approach is to place a file called logging.properties in the WEB-INF/classes folder of your web application. Here's a simple example:

handlers = org.apache.juli.FileHandler
org.apache.juli.FileHandler.directory = ${catalina.base}/logs
org.apache.juli.FileHandler.prefix = mywebapp.

As with Log4J, all messages are logged under the "org.faceless.pdf2" logger and you can turn logging for individual message on or off very easily by setting the appropriate levels in logging.properties. We've included a Formatter so you can also format the output in the same way as the Log4J PatternLayout (you can download it here, it's public domain).

org.faceless.pdf2.warning.F4 = OFF
org.faceless.pdf2.warning.RD4 = SEVERE
org.apache.juli.FileHandler.formatter = \
    org.faceless.util.log.PatternFormatter
org.faceless.util.log.PatternFormatter.format = \
    %d{yyyy-MM-dd HH:mm:ss} %p %m %x%n

This will turn off F4 warnings, make RD4 warnings fatal, and cause the remaining warning message to be formatted to fit on one line.

That's fine if you're logging from within Tomcat, but what about if you're running a regular application from the command line? Here's an example showing how you might see a warning message from this:

java org.faceless.pdf2.viewer2.PDFViewer corrupt.pdf
WARNING RD6: Stream 4/0 is 8 bytes too long - PDF may be corrupt
WARNING RD6: Stream 9/0 is 8 bytes too long - PDF may be corrupt

Although not obvious, these messages are logged using java.util.logging as well: by default if no custom configuration is found, we install our own PatternFormatter to format messages onto one line.

If you want more control over the logging, you can set up your own logging.properties. Here's an example that would log all Token debug entries to a file:

java.util.logging.FileHandler.formatter = org.faceless.util.log.PatternFormatter
java.util.logging.FileHandler.pattern = bfodebug.log
org.faceless.util.log.PatternFormatter.format = %m%n

org.faceless.pdf2.level = ALL
org.faceless.pdf2.debug.Token = FINE
org.faceless.pdf2.handlers = java.util.logging.ConsoleHandler, java.util.logging.FileHandler
org.faceless.pdf2.useParentHandlers = false

Save that file and make sure it's passed into Java

java -Djava.util.logging.config.file=logging.properties ...

Logging without a Logger

For customers still on Java 1.3, messages are sent to System.err, and you can turn messages on or off using System properties:

java -Dorg.faceless.pdf2.warning.RD4=off ...

The warning codes

Full details of the org.faceless.pdf system properties and warning messages can be found in Appendices A & B of the PDF Library user guide.