A Clojure wrapper for log4j2, intended as a partial replacement for
clojure.tools.logging
, that
supports MDC (mapped diagnostic context), NDC (nested diagnostic context)
and markers directly, in a Clojure-friendly manner.
This library explicitly depends on log4j2 and all of the bridge libraries that route other logging frameworks to log4j2 (jcl, jul, log4j 1.x, slf4j 1.x and 2.x).
Note: requires Clojure 1.11 or later.
Add the following dependency to your deps.edn
file:
com.github.seancorfield/logging4j2 {:mvn/version "1.0.2"}
Note: this library is a work in progress -- feedback is appreciated!
Require the main org.corfield.logging4j2
namespace (:as
whatever alias
you prefer -- the examples below use logger
):
The library provides the following macros that you can use to log information:
trace
, debug
, info
, warn
, error
, and fatal
. There is also a
generic log
macro that accepts a level keyword as its first argument.
(logger/info "Hello, world!")
(logger/log :info "Hello, world!") ; equivalent to the above
Support for MDC and NDC is provided via the with-log-context
, with-log-tag
,
and with-log-uuid
macros (see MDC and NDC below for more detail):
with-log-context
accepts a hash map as its first argument, followed by a
body of code to execute. The keys and values in the hash map are added to the
context as strings, for the execution of the body.with-log-tag
accepts a keyword or string as its first argument, followed
by a body of code to execute. The tag is pushed onto the context, for
the execution of the body.with-log-uuid
accepts a body of code to execute. A unique tag is pushed onto the
context, for the execution of the body.(logger/with-log-context {:uid (:id user)}
(logger/info "Hello, world!")) ; INFO {uid=1234} Hello, world!
(logger/with-log-tag (str "user_" (:id user))
(logger/info "Hello, world!")) ; INFO [user_1234] Hello, world!
(logger/with-log-uuid
(logger/info "Hello, world!")) ; is equivalent to
(logger/with-log-tag (str (random-uuid))
(logger/info "Hello, world!")) ; INFO [8b21769c-33c5-42cb-b6c4-146ce8bb875f] Hello, world!
Support for markers is provided by the as-marker
function, which accepts
one or more keywords or strings and returns a marker object that can be passed
as the first argument to any of the logging macros (or the second argument to
the generic log
macro).
(as-marker :sql-update :sql)
returns a marker object that represents the
string "SQL_UPDATE"
with the marker "SQL"
as its parent.
Most logging will look like this:
(logger/info "some message")
;; or
(logger/info my-exception "some message")
;; or
(logger/info my-marker "some message")
;; or
(logger/info my-marker my-exception "some message")
If multiple message arguments are provided, they are generally converted to strings and concatenated with spaces between them, except as follows:
{}
placeholders, then the remaining arguments are treated as values for
those placeholders and a
ParameterizedMessage
is constructed.MapMessage
is constructed, with the keys of the Clojure hash map converted to strings and
the values left as-is.You can build a Message
directly with logger/as-message
:
(logger/as-message "Hello, {}!" "Parameter")
;; => ParameterizedMessage
(logger/as-message {:hello "world"})
;; => MapMessage
(logger/as-message "Hello," "World!")
;; => SimpleMessage
If you are using Clojure 1.12 (or later), you can provide a (fn [] ...)
which
will be used as a MessageSupplier
object:
(logger/info #(logger/as-message "Hello, Supplier!"))
On Clojure 1.11, you have to construct the MessageSupplier
object directly,
for example by using reify
to implement the MessageSupplier
interface
and provide the get
method:
(logger/info (reify org.apache.logging.log4j.message.MessageSupplier
(get [_] (logger/as-message "Hello, Supplier!"))))
Mapped Diagnostic Context (MDC) and Nested Diagnostic Context (NDC) are supported
(as noted above) by the three with-log-*
macros. Nested calls to these macros
will accumulate the map context and the stack of tags automatically, within
the current thread.
The underlying context for log4j2 is thread-local by default: each thread
starts out with an empty context. This library also tracks MDC and NDC using
a dynamic var in Clojure, so you can use with-log-inherited
inside a spawned
thread to inherit the MDC and NDC from the parent thread.
(logger/with-log-context {:uid (:id user)}
(future
(logger/with-log-inherited
(logger/info "Hello, world!")))) ; INFO {uid=1234} Hello, world!
If you're passing functions between threads, you may need to use bound-fn
or bound-fn*
in order to convey the dynamic context into the new thread.
Note:
with-log-inherited
will inherit the entire MDC/NDC from the dynamic parent context, even if there are intervening calls towith-log-context
,with-log-tag
, orwith-log-uuid
in the child thread, that did not inherit the context.
java.util.logging
BridgeYou may also need the following JVM properties when running your code to ensure that the log4j2 JUL bridge works correctly:
clojure -J-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \
-J-Dlog4j2.julLoggerAdapter=org.apache.logging.log4j.jul.CoreLoggerAdapter \
-M -m your.namespace
java -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \
-Dlog4j2.julLoggerAdapter=org.apache.logging.log4j.jul.CoreLoggerAdapter \
-jar your.jar
See JUL-to-Log4j bridge docs for more information about the first of those two properties, and this log4j-jul issue for more information about the second property.
;; they can also be set in your deps.edn file, under an alias:
:jvm-opts ["-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager"
"-Dlog4j2.julLoggerAdapter=org.apache.logging.log4j.jul.CoreLoggerAdapter"]
clojure.tools.logging
Migration/FAQc.t.l has logf
but this library does not. For most uses (with %s
), you
can use a ParameterizedMessage
-- replace %s
with {}
and you
should get the same behavior. For more complex cases, you can use
clojure.core/format
with the arguments to build a string for logging.
You will no longer need -Dclojure.tools.logging.factory=clojure.tools.logging.impl/log4j2-factory
as this library uses log4j2 directly (c.t.l defaults to slf4j
).
If you want to get a Logger
object directly, you can use
org.apache.logging.log4j.LogManager/getLogger
, passing a namespace
name as a string. c.t.l provided a generic get-logger
in its impl
namespace.
Copyright © 2024-2025 Sean Corfield.
Distributed under the Eclipse Public License version 1.0.
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close