I’ve been experimenting with Clojure recently and I’m a huge fan so far, it reminds me of days long since passed learning Scheme. There’s a floppy disk somewhere in VA containing my Scheme elevator controller GUI written awhile back using a random IDE called Dr. Scheme, which is singlehandedly the most unassuming yet epic IDE to ever exist. Most everyone using Clojure appears to be using Leiningen to manage project builds/dependencies/etc., which initially I had my doubts about because it seemed like a needless abstraction. I changed my mind about that when I realized all the work that goes on behind the scenes in order to run Clojure projects on the JVM.
After I got a basic Hello World working, I struggled too long to find a way to use log4j2 from my Clojure files. There’s alot of info out there about how to use lo4j1.x, but I could find no one using log4j2 with Clojure. I’m slowly beginning to migrate parts of the OpenPplTools codebase from Java to Clojure, and I want to keep log4j2 around, mainly because my config file won’t work with 1.x.
The steps I took to use log4j2 are below. This is basic Clojure/lein 101, nothing crazy, I’m just putting this out there since there seems to be nothing (as of this writing) talking about log4j2 and Clojure.
Add log4j2 Dependencies to Your
There seem to be several logging abstractions available for Clojure projects, but I could get none of them to work with log4j2. I tried adding the “clojure/tools.logging” dependency to my project, but it would only work with log4j1.x. At that point, I gave up on finding a convenient abstraction/macro dependency and settled for simply being able to call log4j2 from .clj files in my project.
First, add the
log4j-core artifacts as dependencies to your
project.clj file; my complete :dependencies entry looks like the following:
Excerpt of project.clj
As of this writing, the latest release of log4j2 is 2.0-beta9. If it’s been awhile since this was written, search for “log4j” at the Maven Central Repository and use the latest GroupId/ArtifactId and Latest Version values (lein uses Maven to manage dependencies).
You should be able to run
lein deps and see lein pull in the appropriate JARs, although I believe you can skip this step, as lein will pull them in the next time you call
Calling log4j2 from Clojure
I wrote the following basic program to call log4j2:
I’ll likely end up writing a wrapper for this since Java interop within Clojure leads to code that’s not really all that idiomatic/appealing. For now it works. After making the following modifications to my
Excerpt of project.clj
I could run
lein run and successfully view log4j2 output for the INFO and WARN statements emitted in the code.
One other thing that took me too long to get was that my log4j2.xml configuration file needed to be on the classpath defined by/in lein; you can view that classpath using
lein classpath. The
resources/ directory within my project directory is in that classpath, so after placing log4j2.xml in
resources/ (note: it must be named log4j2.xml for it to be picked up by log4j2 at runtime, otherwise you’ll have to pass an argument to the JVM), I created a custom Appender for use from my Clojure files that excluded the
%t conversion specifier; this specifier outputs the name of the thread that generates each logging event present in the output, which will vary depending upon whether log4j2 is being called from within the context of the REPL (
lein repl) or
lein run, so I just took it out to clean things up:
Excerpt of resources/log4j2.xml
lein run again resulted in use of the new format, and at this point everything was good to go for me:
Note Regarding Logger Names
Typically, when using log4j2 within Java classes, you’ll give each class requiring logging facilities a static Logger object that’s named with the (package-prefixed) name of the class containing it:
There’s not really an equivalent to this practice in Clojure, at least that I’ve found, given the fact that classes (as they exist in Java) do not exist in Closure. Right now I’ve settled for hard-coding the name of the namespace as a string and passing that to
getLogger(<logger-name>), although in all likelihood there’s probably a better way to do this.
There is indeed a better way to do that. Hard-coding the namespace as a string is not necessary; passing it to the
str form and using that as the Logger name is much cleaner. This can be wrapped up in a separate logging namespace, i.e.:
and then called via other namespaces like so:
Sebastian Hennebrueder emailed me to tell me that you can use tools logging without any changes via slf4j to log4j2 by adding the following dependencies to your