Logging in Camel on Spring-Boot using the Fuse Logger

Martien van den Akker
virtualsciences
Published in
7 min readMar 16, 2022

Of course, in Camel, you can log using the log-activity. And you can log anything with it. And it supports the Simple expression language too.

In our former ESB projects we have a generic logging standard, to log several steps in the VETRO pattern (Validate, Enrich, Transport, Route, Operate), or at least when those steps fail. Those log-lines must be recognizable. And logged against a configurable log appender, to be able to set the proper log level.

To have a simple way to enrich our camel-context with a generic logging solution, I created a FuseLogger solution in our observability library. It is a quite simple class, but surprisingly configurable.

Standard Camel Logging

A log activity in Camel XML DSL looks like this:

The attributes in this are:

  • id: it is always advisable to have a proper, functional, and unique activity id.
  • logName: this is the log appender reference. Based on this string you can set the minimum level to log. The value {{logging.name}} refers to the Spring-boot property with that name.
  • loggingLevel: the actual logging level of this particular log message.
  • message: the actual message to log. Supporting the simple expression language.

This shows that for each particular log-message you have to particularly set the logName, and the particular loggingLevel. It helps to get the logName from a Spring-Boot property. But the loggingLevel attribute does not accept a property, and of course, it is not so useful either.

FuseLogger

The FuseLogger solution provides a bean that is configured based on Spring-boot properties. And two embedded property files. It can be used by a bean-method invocation from the Camel context. By providing a particular logCode a standard log message is shown. The available logCodes are registered in the logInfo.properties file. For each logCode there is an entry in logInfoLevel.properties to indicate with which log level the message is to be logged.

Some log messages accept an extra parameter that is expanded in the message. Currently, only one parameter is supported. But, that can be expanded quite easily.

In your Camel context, you possibly use processors or other bean methods. It is then convenient to be able to use the same logger as the Camel Context. That turns out to be very easy as well.

Configuration

To use the FuseLogger you will need to perform a few configurations. First, build and install the fuse-lib-util-observability library. You can do a simple mvn clean install in this demo.

Then in the pom.xml of your adapter/integration project add it as a dependency:

Then, add the following properties to the application.properties:

  • logging.level.nl.vs.fuse.demo=INFO
  • logging.name=nl.vs.fuse.demo
  • logging.id=VSFUSE

The logging.name is used as a log-appender reference, like the logName attribute in the Camel log activity. The logging.level.${logging.name} is a Spring-boot property to set the minimum level to log. In the application.properties you should not set it below INFO. In most cases, it is not allowed to log message-body values, in particular, privacy-sensitive values in a production environment. For a test environment, you can override that.

Lastly, add the following annotation to the Application.java:

Component scan in Application.java

This will scan for the configuration class that loads the FuseLogger bean, as I’ll explain later. The “{nl}” as parameter in the component scan, refers to the first part of the java-package to scan for Spring-Boot components.

In the next paragraphs, I’ll show the usage. I use the XML DSL, but you should be able to translate that to Java DSL.

Logging without parameters

To have a simple log based on only a logCode add the following at the proper location in your Camel context:

FuseLogger — logCode

In the Fuse Integration Designer it looks like:

Bean method with only a logCode

Pretty simple. In the logging you will see:

Logging of LogCode without parameters

A few things happened here. Using the provided logCode HIP-001 the method fetched the text “Message received successfully.” from logInfo.properties. It then expanded the code HIP-001 with the value of the logging.id-property from the application.properties to ‘HIP-VSFUSE-001’. And then it is logged using the log name from the application property logging.name and the log level that is fetched from logInfoLevel.properties.

Logging without parameters

The logCode() method also accepts an extra parameter. This is only relevant for log codes in logInfo.properties that have positional parameters denoted with for example {0}.

In the Camel context this is added as:

FuseLogger — logCode with one parameter

And in the Camel Context Designer it is done with:

Bean method with a logCode and an extra parameter

As you see, you can provide a Simple expression in your extra parameter. In the logging, this shows up as:

Log with one parameter

Start and End logging

You may want to have a standard way to start and end the main route of your Camel Context with a few log lines. For instance, an INFO statement stating that a message is received, and a DEBUG line that also posts the message content. For that the logStart() and logEnd() methods are introduced.

At the top of your main route, add:

FuseLogger — LogStart

And in the designer:

Bean method logStart

In the logging, this results in:

Start Logging

Similarly, you can add an end logging:

FuseLogger — logEnd

That shows up in the Designer as:

Bean method logEnd

And in the logging, this results in:

End logging

Logging in Java methods and processors

Usually, in Java classes, we declare a class-specific logger. However, for methods that are called from a Camel-Context, it may be desired to use the same FuseLogger as used from the Camel-Context. This is quite easy and comparable to logging in the Camel-Context. At the top of the class, add the following snippet to declare the fuseLogger:

Declare fuseLogger in java class

In the Java code the logging can be called as:

FuseLogger — logCode in java

This leads to similar logging as earlier.

Ad-Hoc logging

The logging described so far, uses logCodes that result in standard log messages. Often, you might want to log your own messages. But, with the same properties as with the other logCodes. For this purpose, the following codes are added:

  • HIP-990 for ad-hoc debug messages
  • HIP-992 for ad-hoc info messages
  • HIP-994 for ad-hoc warning messages
  • HIP-996 for ad-hoc error messages
FuseLogger — Ad-hoc message

Again, in the designer this looks like this:

Bean method ad-hoc logging

The provided parameter is the text to be logged. Notice that here also the Simple-Expression parameters can be used. For Camel properties, the *${property}* notation should be used, to enable Simple to expand them. For Spring-Boot properties use the double-curly-brackets notation as mentioned earlier.

How does it all work?

Central is the FuseLogger.java class. It’s a quite simple class. It declares the logInfo and logInfoLevel properties as ResourceBundles.

Declare logInfo and logInfoLevel as ResourceBundles

Then it uses 3 message formats to format the log messages:

The default constructor creates a default logger on the FuseLogger.class, but the constructor with logName as a parameter does this on the logName attribute through the setLogName() method.

Both logCode() methods fetch the log messages through the logInfo resource bundle. Using the logCode() with parameter method uses the fetched message as a template in the MessageFormat.format() method to expand the message with the parameter.

The method getAppLogCode() is used to insert the adapter’s logging.id into the logCode.

The formatMessage() creates the actual message based on the expansion of the MESSAGE_FORMAT and the logCode and log message.

The doLogCode() method fetches the log level from the logInfoLevel bundle.

The doLog() method validates the provided logLevel, defaults to INFO, and calls the proper method on the logger.

The FuseLogger bean is declared in util-observability.xml, using the logging.name and logging.id properties and proper defaults.

Using the ObservabilityConfiguration.java class this XML is automatically loaded. Provided that you add the following annotation to the Application.java, as explained in the configuration section:

Component scan in Application.java

Conclusion

This way you can have a few reusable expandable log messages that can be added to your Camel Context, Java bean methods, and processors. These statements can be inserted in a standard way in different parts of your project and added to new projects. Configuration is done centrally and can be overridden using environment variables in your Kubernetes pods.

I‘m actually surprised myself, how such a simple class can make my logging so much more generic. And expandable with just a few extra lines in a property file.

A possible improvement would be to support multiple expandable positional properties. And to be able to have some route instance-specific properties like instance message-ids. But then I need to be able to use the scope attribute on the bean as in Camel 3.15+ Bean Component. Unfortunately, that is not supported in Red Hat Fuse. Then I should implement a workaround to provide a breadcrumb object as an extra parameter. Which makes the method autographs more complex, unfortunately.

--

--

Martien van den Akker
virtualsciences

Technology Architect at Oracle Netherlands. The views expressed on this blog are my own and do not necessarily reflect the views of Oracle