An Architect's View

CFML, Clojure, Software Design, Frameworks and more...

An Architect's View

Clojure and log4j

May 20, 2011 · 2 Comments

As my readers will no doubt know by now, I have a large CFML application and I'm migrating low-level parts of it to Clojure to that I can reuse those pieces from both CFML and Clojure (and Scala). I recently migrated all of the environment control logic, which automatically configures the application based on which server the code is running on top of, to Clojure so that my CFML, Clojure and Scala code could all leverage the same environment control logic. I'll probably blog about that at some point. I've also created a Clojure replacement for the CFML ORM we've been using (Reactor). Today, however, I want to talk about logging.

Logging is pretty fundamental so we want to be able to use the same core logging functionality across all parts of our application, no matter what language those parts are written in. Clojure 1.3.0 has an updated logging library called clojure.tools.logging which acts as a wrapper around several standard Java logging libraries and automatically selects one as the implementation (it supports Apache Commons Logging, SL4J, log4j and java.util.logging out of the box). This looked like a good candidate but I was concerned about the number of custom log appenders we have in our current application. After talking to Alex Taggart (author of clojure.tools.logging) and reading the log4j documentation, I was convinced it would serve as a capable replacement so the only parts of the puzzle left were to figure out how to use log4j from Clojure and how to write a custom log appender in Clojure.

Using log4j from Clojure

The first thing was to add clojure.tools.logging and log4j to my Clojure project. I updated my project.clj file to include this:

  :dependencies [...
                 [org.clojure/tools.logging "0.1.2"]
                 [log4j/log4j "1.2.16"]
                 ...]

Then I ran lein deps to fetch the libraries from Maven. Next I created a basic log4j.properties file in the src/ folder of my project:

# Set root logger level to DEBUG and its only appender to A1.
log4j.rootLogger=DEBUG, A1

# A1 is set to be a ConsoleAppender.
log4j.appender.A1=org.apache.log4j.ConsoleAppender

# A1 uses PatternLayout.
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n

This is the standard example from the log4j docs. I fired up a REPL and typed:

(use 'clojure.tools.logging)
(spy :debug { :a 1 :b 2 })

Sure enough, I was greeted with the following in the console log:

29707 [Thread-11] DEBUG user  - {:a 1, :b 2}
=> {:a 1, :b 2}

Excellent! Now it's time to write a log appender in Clojure!

Log Appender in Clojure

The log4j package provides an abstract class org.apache.log4j.AppenderSkeleton that has most of the boilerplate already written for you. All you need to add is an append(LoggingEvent event) method and implement close() and requiresLayout() from the underlying interface org.apache.log4j.Appender. Using the :gen-class machinery in Clojure, that's pretty easy:

(ns example.logging.core
  (:import org.apache.log4j.AppenderSkeleton)
  ;; specify the Java class name to generate and what it extends:
  (:gen-class :name example.logging.Logger
              :extends org.apache.log4j.AppenderSkeleton))

;; implement void append(LoggingEvent event):
(defn -append [_ event] ; first argument is 'this', ignored
  (println (.getMessage event))) ; just print the event message (to *out*)

;; implement void close()
(defn -close [_]) ; no resources to clean up

;; implement boolean requiresLayout()
(defn -requiresLayout [_] false) ; we handle all formatting

Now we just need to configure this new log appender:

# Set root logger level to DEBUG and its only appender to A2.
log4j.rootLogger=DEBUG, A2

# A2 is set to be my Clojure appender.
log4j.appender.A2=example.logging.Logger

Now we just fire up the REPL again, compile our new appender and use it:

(compile 'example.logging.core)
(use 'clojure.tools.logging)
(spy :debug { :a 1 :b 2 })

This time we're greeted with our log output in the console of the REPL itself, as expected from a println call:

{:a 1, :b 2}
=> {:a 1, :b 2}

That's all there is to it. Much simpler than I expected.

What about the CFML code?

The large CFML application uses the ColdBox framework which has a sophisticated logging module that is essentially a full port of log4j to CFML. Whilst it's impressive - and works just like log4j - we can't use it from our Clojure code (because you can't currently call CFML from Java without a lot of work, even tho' CFML compiles to Java bytecode). Now that I know how to write log appenders for log4j in Clojure, it will be fairly easy to rewrite our custom CFML log appenders in Clojure and then swap out our dependence on ColdBox logging for a simple CFML wrapper around clojure.tools.logging, so we can rely on that standard Clojure library everywhere instead.

Tags: clojure · coldbox · coldfusion

2 responses so far ↓

  • 1 Ken Redler // May 21, 2011 at 2:31 PM

    Sean, I'm following your cfml/Clojure/Scala integration journey with great interest. Are you finding that rewriting portions of your application in Clojure (or Scala) has resulted in material performance improvements? Obviously environment sensing and configuration is not going to be a performance bottleneck, and I understand your goals in focusing on the ORM and logging machinery. But do you think there's a potential benefit in identifying suboptimal cfml hotspots and reimplementing them in Clojure or Scala?
  • 2 Sean Corfield // May 23, 2011 at 2:04 AM

    @Ken, well the Scala code we have at work specifically addresses performance problems we'd had previously with CFML for a long-running, data-intensive process.

    The performance improvements we've seen recently with the switch to Clojure have come primarily from replacing Reactor with the new ORM - because Reactor adds a lot of overhead which is not present in the new ORM code.

    I expect we will see some improvement with the switch from ColdBox logging to log4j but it will be incremental and due simply to having less code being executed.

    Mostly what is driving the shift from CFML to Clojure is better / safer / easier concurrency support. That said, yes, if you have an identifiable bottleneck in your CFML code, reimplementing it in another JVM-based language may well provide measurable performance improvements (but try algorithmic improvements in CFML first!).

Leave a Comment

Leave this field empty