Getting Rollbar's Java SDK 1.7.10 Working In Adobe ColdFusion 2021
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!
Want to use code from this post? Check out the license.
Reader Comments
Can't wait to see the next post about that ;-)
@Frederic,
Ha ha, hopefully not too soon. Gotta wrap my head around the API.
@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 noisError()
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.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 🤕
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. :-)
@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.
🍿 (got my popcorn...can't wait to hear back from Rollbar)
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?
@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 ofswitch
/include
tags - really old-school, like FuseBox back in the day.@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 inslf4j-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...
@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
@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!
Using version 1.8.0_292 of OpenJDK, maybe that has something to do with it.
Post A Comment — ❤️ I'd Love To Hear From You! ❤️
Post a Comment →