SeamFramework.orgCommunity Documentation

Chapter 52. Logging, redesigned

52.1. Features
52.2. Typed loggers
52.3. Native logger API
52.4. Typed message bundles
52.5. Implementation classes
52.5.1. Enabling generated proxies
52.5.2. Generating concrete implementation classes

Seam Solder brings a fresh perspective to the ancient art of logging. Rather than just giving you an injectable version of the same old logging APIs, Solder logging goes the extra mile by embracing the type-safety of CDI and eliminating brittle, boilerplate logging statements. And no matter how you decide to roll it out, you still get to keep your logging engine of choice.

Solder builds on JBoss Logging 3 to provide the following feature set:

To define a typed logger, first create an annotated interface with methods configured as log commands:

import org.jboss.seam.solder.logging.LogMessage;

import org.jboss.seam.solder.logging.Message;
import org.jboss.seam.solder.logging.MessageLogger;
      
@MessageLogger
public interface TrainSpotterLog {
   @LogMessage @Message("Spotted %s diesel trains") 
   void dieselTrainsSpotted(int number);
}

We have configured the log messages to use printf-style interpolations of parameters (%s).

You can then inject the typed logger with no further configuration necessary. We use another annotation to set the category of the logger to "trains" at the injection point:

   @Inject @Category("trains")

   private TrainSpotterLog log;

We log a message by simply invoking a method of the typed logger interface:

   log.dieselTrainsSpotted(7);

The default locale will be used unless overridden. Here we configure the logger to use the UK locale:

   @Inject @Category("trains") @Locale("en_GB")

   private TrainSpotterLog log;

Typed loggers also provide internationalization support. Simply add the @MessageBundle annotation to the logger interface (planned).

You can also log exceptions.

import org.jboss.seam.solder.logging.Cause;

import org.jboss.seam.solder.logging.LogMessage;
import org.jboss.seam.solder.logging.Message;
import org.jboss.seam.solder.logging.MessageLogger;
@MessageLogger
public interface TrainSpotterLog {
   @LogMessage @Message("Failed to spot train %s") 
   void missedTrain(String trainNumber, @Cause Exception exception);
}

You can then log a message with an exception as follows:

catch (Exception e) {

   log.missedTrain("RH1", e);
}

The stacktrace of the exception parameter will be written to the log along with the message.

You can also inject a "plain old" Logger (from the JBoss Logging API):

import javax.inject.Inject;


import org.jboss.logging.Logger;
public class LogService {
   @Inject
   private Logger log;
   public void logMessage() {
      log.info("Hey sysadmins!");
   }
}

Log messages created from this Logger will have a category (logger name) equal to the fully-qualified class name of the bean implementation class. You can specify a category explicitly using an annotation.

   @Inject @Category("billing")

   private Logger log;

You can also specify a category using a reference to a type:

   @Inject @TypedCategory(BillingService.class)

   private Logger log;

Often times you need to access a message directly. For example, you need to localize an exception message. Solder let's you define an injectable typed message bundle.

First, declare the message bundle as an annotated interface with methods configured as message retrievers:

import org.jboss.seam.solder.logging.Message;

import org.jboss.seam.solder.logging.MessageBundle;
   
@MessageBundle
public interface TrainMessages {
   @Message("No trains spotted due to %s") 
   String noTrainsSpotted(String cause);
}

Inject it:

   @Inject @MessageBundle

   private TrainMessages messages;

And use it:

   throw new BadDayException(messages.noTrainsSpotted("leaves on the line"));

You may have noticed that throughout this chapter, we've only defined interfaces. Yet, we are injecting and invoking them as though they are concrete classes. So where's the implementation?

Good news. The typed logger and message bundle implementations are generated automatically. You'll see this strategy used often in Seam 3. It's declarative programming at its finest (or to an extreme, depending on how you look at it). Either way, it saves you from a whole bunch of typing.

There are (currently) two types of implementations that can be used:

We'll go over your two options.

Once you are ready to use the typed loggers and message bundles seriously (or you are tired of dealing with the system property), you should generate the concrete implementation classes as part of the build. These classes are generated by using an annotation process provided by Solder. Don't worry, it's a lot simpler than it sounds. You just need to do these two simple steps:

Setting the Java compliance to 1.6 enables any annotation processors on the classpath to be activated during compilation.

If you're using Maven, here's how the configuration in your POM file looks:


<dependencies>
    <!-- Annotation processor for generating typed logger and message bundle classes -->
    <dependency>
        <groupId>org.jboss.seam.solder</groupId>
        <artifactId>seam-solder-tooling</artifactId>
        <version>${solder.version}</version>
        <optional>true</optional>
    </dependency>
    ...
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>1.6</source>
                <target>1.6</target>
            </configuration>
        </plugin>
    </plugins>
</build>

Now you can add typed loggers and message bundles at will and not have to worry about unsatisified dependencies.