Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

Getting Rollbar's Java SDK 1.7.10 Working In Adobe ColdFusion 2021

By Ben Nadel on
Tags: ColdFusion

As I mentioned the other day, I'm preparing to pour some love into my ColdFusion blogging platform. One area in much need of love is my error logging. If you can even imagine, this blog still uses email as the primary means to report errors! *Ring ring ring* - Hello. What's that? The 1990's called and they want their error handling back? As a step towards modernization, I thought I would try out Rollbar - they have both a client-side JavaScript SDK and a server-side Java SDK. And, I think they have a cool name. Getting Rollbar's Java SDK 1.7.10 working with Adobe ColdFusion 2021 turned out to be a bit of a battle.

Since my blog is on Adobe ColdFusion 2021, I can't use Lucee CFML's ability to load JAR files on-the-fly with createObject(). As such, I'm going to use the JavaLoader ColdFusion project to create a dynamic class-loader using JAR file references.

First, I went to the Maven Repository and downloaded the JAR files for rollbar-java 1.7.10. Then, my first attempt looked something like this:

var classLoader = javaLoaderFactory.getJavaLoader([
	expandPath( "/jars/rollbar-1.7.10/rollbar-api-1.7.10.jar" ),
	expandPath( "/jars/rollbar-1.7.10/rollbar-java-1.7.10.jar" ),
	expandPath( "/jars/rollbar-1.7.10/slf4j-api-1.7.25.jar" )
]);

var config = classLoader.create( "com.rollbar.notifier.config.ConfigBuilder" )
	.withAccessToken( accessToken )
	.environment( environment )
	.build()
;

var rollbar = classLoader.create( "com.rollbar.notifier.Rollbar" )
	.init( config )
;

This threw the following ColdFusion error:

Could not initialize class com.rollbar.notifier.sender.BufferedSender

java.lang.NoClassDefFoundError:

Could not initialize class com.rollbar.notifier.sender.BufferedSender
  at com.rollbar.notifier.sender.BufferedSender$Builder.<init>(BufferedSender.java:170)
  at com.rollbar.notifier.config.ConfigBuilder.build(ConfigBuilder.java:497)
  at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
  at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
  at java.base/java.lang.reflect.Method.invoke(Unknown Source)
  at coldfusion.runtime.StructBean.invoke(StructBean.java:509)

My first thought was that this had something to do with a conflicting class-loader problem. I thought maybe the Sender was using the base class-loader and not my custom class-loader. I ran into a similar issue when getting the LaunchDarkly Java SDK working with Adobe ColdFusion 10. So, I tried to solve the problem with the switchThreadContextClassLoader() method:

var classLoader = javaLoaderFactory.getJavaLoader([
	expandPath( "/jars/rollbar-1.7.10/rollbar-api-1.7.10.jar" ),
	expandPath( "/jars/rollbar-1.7.10/rollbar-java-1.7.10.jar" ),
	expandPath( "/jars/rollbar-1.7.10/slf4j-api-1.7.25.jar" )
]);

var ConfigBuilder = classLoader.create( "com.rollbar.notifier.config.ConfigBuilder" );

// Trying to get around a possible class loading problem by brute-forcing all the code
// to run in the same class loader
var config = classLoader.switchThreadContextClassLoader(
	() => {

		return(
			ConfigBuilder
				.withAccessToken( accessToken )
				.environment( environment )
				.build()
		);

	}
);

var rollbar = classLoader.create( "com.rollbar.notifier.Rollbar" )
	.init( config )
;

This ended up breaking the request so hard that I couldn't even really see the underlying error.

So then I thought maybe the version 1.7.10 wasn't fully supported. I looked at the Rollbar docs and I saw something about version 1.5.2. So, I downloaded those JAR files from Maven and tried removing the switchThreadContextClassLoader() call:

var classLoader = javaLoaderFactory.getJavaLoader([
	expandPath( "/jars/rollbar-1.5.2/rollbar-api-1.5.2.jar" ),
	expandPath( "/jars/rollbar-1.5.2/rollbar-java-1.5.2.jar" ),
	expandPath( "/jars/rollbar-1.5.2/slf4j-api-1.7.25.jar" )
]);

var config = classLoader.create( "com.rollbar.notifier.config.ConfigBuilder" )
	.withAccessToken( accessToken )
	.environment( environment )
	.build()
;

var rollbar = classLoader.create( "com.rollbar.notifier.Rollbar" )
	.init( config )
;

This gave me a totally new error:

java.lang.LinkageError: loader constraint violation:

when resolving method 'org.slf4j.ILoggerFactory org.slf4j.impl.StaticLoggerBinder.getLoggerFactory()' the class loader com.compoundtheory.classloader.NetworkClassLoader @695d9aea of the current class, org/slf4j/LoggerFactory, and the class loader 'app' for the method's defining class, org/slf4j/impl/StaticLoggerBinder, have different Class objects for the type org/slf4j/ILoggerFactory used in the signature (org.slf4j.LoggerFactory is in unnamed module of loader com.compoundtheory.classloader.NetworkClassLoader @695d9aea, parent loader 'bootstrap';

org.slf4j.impl.StaticLoggerBinder is in unnamed module of loader 'app')
  at org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:418)
  at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:357)
  at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:383)
  at com.rollbar.notifier.sender.AbstractSender.<init>

Then, I tried the switchThreadContextClassLoader() method again. And, once again, the page broke so badly I couldn't even see the actual error.

So then, I thought I would revert to the newer 1.7.10 version and try using the ColdFusion application framework's this.javaSettings configuration to just load the Rollbar JAR files right into the ColdFusion application's main class-loader:

// In Application.cfc pseudo-constructor.
this.javaSettings = {
	loadColdFusionClassPath: true,
	reloadOnChange: true,
	loadPaths: [
		( this.directory & "extensions/jars/rollbar-1.7.10/rollbar-api-1.7.10.jar" ),
		( this.directory & "extensions/jars/rollbar-1.7.10/rollbar-java-1.7.10.jar" ),
		( this.directory & "extensions/jars/rollbar-1.7.10/slf4j-api-1.7.25.jar" )
	]
};

// .... truncated ....

var config = createObject( "java", "com.rollbar.notifier.config.ConfigBuilder" )
	.withAccessToken( accessToken )
	.environment( environment )
	.build()
;

var rollbar = createObject( "java", "com.rollbar.notifier.Rollbar" )
	.init( config )
;

This gave me the same java.lang.LinkageError: loader constraint violation error (regardless of whether I had loadColdFusionClassPath set to true or false).

At this point, I was like 3-hours into debugging and couldn't figure out what the heck was going on. I just kept tweaking my Google searches and shooting in the dark. Finally, I came across a GitHub Issue in the Rollbar Java SDK project where Jared Anderton mentioned that he fixed one of his issue by loading the No-Op (No-Operation) logger for slf4j.

So, I figured it was worth a shot:

var classLoader = javaLoaderFactory.getJavaLoader([
	expandPath( "/jars/rollbar-1.7.10/rollbar-api-1.7.10.jar" ),
	expandPath( "/jars/rollbar-1.7.10/rollbar-java-1.7.10.jar" ),
	expandPath( "/jars/rollbar-1.7.10/slf4j-api-1.7.25.jar" )
	// Have to include this for some reason!
	// READ MORE: https://github.com/rollbar/rollbar-java/issues/85
	expandPath( "/jars/rollbar-1.7.10/slf4j-nop-1.7.25.jar" )
]);

var config = classLoader.create( "com.rollbar.notifier.config.ConfigBuilder" )
	.withAccessToken( accessToken )
	.environment( environment )
	.build()
;

var rollbar = classLoader.create( "com.rollbar.notifier.Rollbar" )
	.init( config )
;

And kablamo!! My Adobe ColdFusion 2021 application started-up with a reference to the Rollbar Java client instance!

As much as I love that ColdFusion is built on top of Java; and, that we can more-or-less reach down into the Java layer for fun-and-profit; not having a strong Java background can really bite me. I have no idea what was going on here. And, I have no idea why adding the slf4j-nop library fixed anything. But, "fixed" is good enough for me! Now, it's time to start integrating Rollbar into my ColdFusion error handling!



Reader Comments

@All,

So, one thing that has "thrown me off" a bit is the fact that the Rollbar SDK uses java.lang.Throwable in its method signatures. I use errors in ColdFusion all the time; but, I basically never think about the data type that they are. Now, this SDK is forcing me to consider my data more closely. Which makes me wonder why there's no isError() decision function in ColdFusion. I gave it a ponder:

www.bennadel.com/blog/4148-considering-an-iserror-decision-function-in-coldfusion.htm

Ultimately, I don't think I would use isError() if it existed. But, it was a good thought experiment.

Reply to this Comment

I 💯 agree that it's cool to be able to reach down into the Java layer when needed. It's amazing when it works. It's so sO SO frustrating when it does not. It's times like that I definitely wish I had a stronger background in Java too. I guess that was the long way of saying...I feel your pain 🤕

Reply to this Comment

Hey, Ben, it wasn't clear from this post (or I missed it): are you confirming that if you DID do just the createobject naming that one jar in Lucee, it would have worked without any need to also load also anywhere that slf4j-nop jar?

Just curious, for the sake of understanding what's amiss. (Maybe it's in some classpath that's loaded by Lucee even when you don't name it.)

Not at all disagreeing that it would be nice if CF added that ability to load classes naming the jar location, like Lucee can. :-)

Reply to this Comment

@Charlie,

I did not actually try it. I was just hypothesizing that having Lucee's on-the-fly JAR loading may have helped. But, once I realized what the solution was (including that No-Op logging implementation), I have to assume the same exact issue would have happened in Lucee as well.

One of the Dev-Rel people at Rollbar actually messaged me on LinkedIn to say they are checking on this to see if it was a failure of documentation on their part; or, if there is something wrong with the implementation. I'll report back anything I hear.

Reply to this Comment

Just gave it a try in lucee, placed the 3 class files in the proper folder and restarted the service.

var config = createObject("java", "com.rollbar.notifier.config.ConfigBuilder" ).withAccessToken('The Token').environment('Test').build();

var rollbar = createObject("java", "com.rollbar.notifier.Rollbar").init(config);

dump(rollbar.log('banane'));

The log appeared in Rollbar 😀

How do you plan to integrate that into your blog application? Just putting the code in the onError function of the application.cfc?

Reply to this Comment

@Frédéric,

Oh, interesting -- are you saying that you did not have to include slf4j-nop-1.7.25.jar in order for this to work? If so, that's very interesting indeed.

Re: onError() - you are spot-on - my blog is really just a series of switch / include tags - really old-school, like FuseBox back in the day.

Reply to this Comment

@Ben,

No, I do required the file at the same level then then the 2 others. The com.rollbar.notifier.sender.BufferedSender class requires some function that are in slf4j-nop-1.7.25.jar to be initialized properly.

The difference in ACF is that your example is using javaLoaderFactory, while lucee loads the librairies when it starts. So in the end... just a different way to get to the same point.

Now, like you, I am at the step of figuring out the API on how to build a proper payload... the class requires some parameters of certain types (they need to be java objects), just dumping the error structure in there won't work... Well it will but it is not pretty when you view it in rollbar.

I am reading about this right now :

https://helpx.adobe.com/coldfusion/cfml-reference/coldfusion-functions/functions-c-d/CreateDynamicProxy.html

I feel like I am going down the rabbit hole...

Reply to this Comment

@Frédéric,

Ben.... this is what happens before I comment without my first coffee you are right Lucee can do WITHOUT the slf4j-nop-1.7.25.jar

These are the files I have in my library folder :

rollbar-api-1.7.10.jar
rollbar-java-1.7.10.jar
slf4j-api-1.7.32.jar

Reply to this Comment

@Frédéric,

Very cool! I wonder why this worked in Lucee but not in ACF? I wonder if there's some additional logging libraries that somehow in the version of Java or something that Lucee is using? No idea. Thanks for testing that out!

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Blog
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.