Create a Custom Camel logging handler for WildFly

A Custom logging handler allows you to define a custom destination for your WildFly / JBoss AS 7 server logging events. You would typically define a custom hanlder to log to a Socket, a Database, to a remote location or a JMS Queue. In this tutorial we will learn how to base your custom handler on a Camel route so that you might choose any possible route for your logs!

The first step for creating a custom handler is extending the java.util.logging.Handler, implementing its mandatory methods.

Here's the skeleton of our CamelHandler:

package com.mastertheboss.camel.handler;

import java.util.logging.Handler;
import java.util.logging.LogRecord;
import org.apache.camel.CamelContext;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.impl.DefaultCamelContext;

import org.apache.camel.ProducerTemplate;

public class CamelHandler extends Handler {

	//Publish a LogRecord.
	public void publish(LogRecord record) {
	}

	//Flush any buffered output.
	public void flush() {
	}

	//Close the Handler and free all associated resources.
	public void close() throws SecurityException {
	}

}

Within it, we will add a class variable for storing the destination of your Camel route, an init() method for creating the org.apache.camel.CamelContext and the org.apache.camel.ProducerTemplate. The publish method will send the LogRecord to the Camel's direct component. Here's the full version of our CamelHandler:

package com.mastertheboss.camel.handler;

import java.util.logging.Handler;
import java.util.logging.LogRecord;
import org.apache.camel.CamelContext;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.impl.DefaultCamelContext;

import org.apache.camel.ProducerTemplate;

public class CamelHandler extends Handler {
	CamelContext context;
	ProducerTemplate template;

	// Set on WildFly
	private String destination;

	public String getDestination() {
		return destination;
	}

	public void setDestination(String destination) {
		this.destination = destination;
		init();
	}

	private void init() {

		context = new DefaultCamelContext();
		try {
			context.addRoutes(new RouteBuilder() {
				public void configure() {
					from("direct:logger").to(destination);
				}
			});
			context.start();
		} catch (Exception e) {

			e.printStackTrace();
		}

		template = context.createProducerTemplate();
	}



	@Override
	public void publish(LogRecord record) {

		template.sendBody("direct:logger", record.getMessage());

	}

	@Override
	public void flush() {
		// TODO Auto-generated method stub

	}

	@Override
	public void close() throws SecurityException {
		try {
			template.stop();
			context.stop();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

}

You need to package this class in a JAR and install it as a module. Your module will include as well the camel-core library and any component's library that is used by the route. For keep it simple, we will start using a File route which is a built-in component, so we will just need the camel-core-2.18.2.jar.

module add --name=com.camelhandler --resource-delimiter=, --resources=camel-handler.jar,camel-core-2.18.2.jar --dependencies=org.slf4j,javax.xml.bind.api,javax.api,sun.jdk

And here's the module.xml file created:

<module xmlns="urn:jboss:module:1.1" name="com.camelhandler">

    <resources>
        <resource-root path="camel-handler.jar"/>
        <resource-root path="camel-core-2.18.2.jar"/>
    </resources>

    <dependencies>
        <module name="org.slf4j"/>
        <module name="javax.xml.bind.api"/>
        <module name="javax.api"/>
        <module name="sun.jdk"/>
    </dependencies>
</module>

Great, now let's define a custom handler on the application server which used this module:

<custom-handler name="CamelHandler" class="com.mastertheboss.camel.handler.CamelHandler" module="com.camelhandler">  
	<properties>
	 	<property name="destination" value="file:/var/log/camel?fileName=server.log" />
	</properties>
	<level name="INFO"/>  
	<formatter>  
	    <pattern-formatter pattern="%d{HH:mm:ss,SSS} %-5p [%c] (%t) %s%E%n"/>  
	</formatter>  
</custom-handler>

Now either define a Logger for a specific package or, to have a quick try, include this handler in the root logger:

<root-logger>
        <level name="INFO"/>
        <handlers>
            <handler name="CamelHandler"/>
            <handler name="CONSOLE"/>
            <handler name="FILE"/>
        </handlers>
</root-logger>

We're done! reload your server and check that the server log messages are being written also in the path defined in your "destination".

Besides writing to a File, which is a Camel built-in component, you have an unlimited set of options for routing your log messages. For example, by providing an ActiveMQConnectionFactory to the CamelContext then you could push your log messages to an ActiveMQ server with small adjustments to your Handler:

package com.mastertheboss.camel.handler;

import java.util.logging.Handler;
import java.util.logging.LogRecord;
import org.apache.camel.CamelContext;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.impl.DefaultCamelContext;

import org.apache.camel.ProducerTemplate;

import javax.jms.ConnectionFactory;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.camel.component.jms.JmsComponent;
 

public class CamelHandler extends Handler {
	CamelContext context;
	ProducerTemplate template;

	// Set on WildFly
	private String destination;

	private void init() {

		context = new DefaultCamelContext();
		ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
                "tcp://0.0.0.0:61616");
                context.addComponent("jms",
                JmsComponent.jmsComponentAutoAcknowledge(connectionFactory));
        
		try {
			context.addRoutes(new RouteBuilder() {
				public void configure() {
					from("direct:logger").to(destination);
				}
			});
			context.start();
		} catch (Exception e) {

			e.printStackTrace();
		}

		template = context.createProducerTemplate();
	}

	public String getDestination() {
		return destination;
	}

	public void setDestination(String destination) {
		this.destination = destination;
		init();
	}

	@Override
	public void publish(LogRecord record) {

		template.sendBody("direct:logger", record.getMessage());

	}

	@Override
	public void flush() {
		// TODO Auto-generated method stub

	}

	@Override
	public void close() throws SecurityException {
		try {
			template.stop();
			context.stop();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

}

That would require some additional JARS to be placed in your module:

<module xmlns="urn:jboss:module:1.1" name="com.camelhandler">

    <resources>
        <resource-root path="camel-handler.jar"/>
        <resource-root path="camel-core-2.18.2.jar"/>
        <resource-root path="camel-jms-2.18.2.jar"/>
        <resource-root path="activemq-camel-5.13.4.jar" />
        <resource-root path="activemq-broker-5.13.4.jar" />
        <resource-root path="activemq-client-5.13.4.jar" />
        <resource-root path="activemq-pool-5.13.3.jar" />

    </resources>

    <dependencies>
        <module name="org.slf4j"/>
        <module name="javax.xml.bind.api"/>
        <module name="javax.api"/>
        <module name="sun.jdk"/>
    </dependencies>
</module>

Now you can choose as destination for your log an ActiveMQ queue, instead of a File:

<custom-handler name="CamelHandler" class="com.mastertheboss.camel.handler.CamelHandler" module="com.camelhandler">  
	<properties>
	 	<property name="destination" value="jms:queue:activemq/queue/ServerLogQueue" />
	</properties>
	<level name="INFO"/>  
	<formatter>  
	    <pattern-formatter pattern="%d{HH:mm:ss,SSS} %-5p [%c] (%t) %s%E%n"/>  
	</formatter>  
</custom-handler>

Enjoy logging with your Camel handler!

Follow us on Twitter