The ch.codesmith/logger
library is thin macro layer on top of Slf4j. It simplifies the usage
of the Logstash Logback Encoder
in Clojure projects. The Logstash Logback Encoder is an encoder for Logback
that emits a Logstash compatible JSON string for every logging events. We use the special
Markers
to log contexts as
well StructuredArguments
to structure the log entries.
To use, include in your deps.edn
{:mvn/version "0.6.76"}
The logging macros are in the namespace ch.codesmith.logger
.
The library requires a Logger instance in
every namespace where the logging macros is used. This is accomplished by calling the macro deflogger
, typically
called at the top of the namespace file. The macro creates a var in the namespace with the name ⠇⠕⠶⠻
. You must avoid
creating a var with the same name.
(ns example
(require [ch.ch.codesmith.logger :as log]))
(log/deflogger)
;; The logging macros can now be used in the example namespace.
The logger macros come in three families: context/structured arguments (ending with -c
), message (ending with -m
)
and error (ending with -e
). Each family has a macro for one of the 5 log levels, with prefixes trace
,
debug
, info
, warn
and error
. We illustrate for the log level info
; the others are similar.
info-c
The macro info-c
is used to transmit a context and structured argument to the log entry. With the LogstashEncoder
(
cf. below), the context is added under the key "context"
in the emitted JSON. The macro comes with 3 variants: context
only; context and message; context, format string and varars with key/value pairs that are passed as structured
arguments.
(log/info-c {:key "value"})
;; {"@timestamp":"...","@version":"1","message":"","logger_name":"example",...,"context":{"key":"value"}}
(log/info-c {:key "value"} "important message")
;; {"@timestamp":"...","@version":"1","message":"important message","logger_name":"example",...,"context":{"key":"value"}}
(log/info-c {:key "value"} "import message for {}" :user "stan")
;; {"@timestamp":"...","@version":"1","message":"important message for user=\"stan\"","logger_name":"example",...,"context":{"key":"value"}, "user":"stan"}
The info-c
macro produces code that checks if the info log level is enabled and it guaranties that the json
transformation happens exactly once. The json transformation is handled by the library jsonista
. If the context or
structured argument value is not encodable by jsonista, the context is encoded via pr-str
and emitted as string. The
library logs a warning in that case.
(log/info-c {:key +})
;; {"@timestamp":"...","@version":"1","message":"Serialization error","logger_name":"ch.ch.codesmith.logger.core","level":"WARN",...,"stack_trace":"com.fasterxml.jackson.core.JsonGenerationException: Cannot JSON encode object of class: class clojure.core$_PLUS_: clojure.core$_PLUS_@7a5a9ca1...."}
;; {"@timestamp":"...","@version":"1","message":"","logger_name":"example","context":"{:key #object[clojure.core$_PLUS_ 0x7a5a9ca1 \"clojure.core$_PLUS_@7a5a9ca1\"]}"}
info-m
The macro info-m
is used for simple message formating. To have structured arguments, you must use the log/kv
function.
(log/info-m "import message for {}, status {}" (log/kv :user "stan") 400)
;; {"@timestamp":"...","@version":"1","message":"import message for user=stan, status 400","logger_name":"example","user":"stan"...}
info-e
The macro info-e
is used to log errors (Throwables). The macro comes with 3 variants: error only; error and context;
error, context and message. In the first case, the message from the error is used as message
via Throwable#getMessage()
and the result of ex-data
is put ins the json under the key exdata
(by default). The
dyadic version takes a context as second argument. The triadic version allows to give an alternative error message.
(log/info-e (IllegalStateException. "fatal error"))
;; {"@timestamp":"...","@version":"1","message":"fatal error","logger_name":"example",...,"stack_trace":"..."}
(log/info-e (ex-info "fatal error" {:key "value"}))
;; {"@timestamp":"...","@version":"1","message":"fatal-error","logger_name":"example",...,"stack_trace":"...","exdata":{"key":"value"}}
(log/info-e (ex-info "fatal error" {:key "value"}) {:more "information"})
;; {"@timestamp":"...","@version":"1","message":"fatal error","logger_name":"example",...,"stack_trace":"...","exdata":{"key":"value"},"context":{"more":"information"}}
(log/info-e (ex-info "fatal error" {:key "value"}) {:more "information"} "important message")
;; {"@timestamp":"...","@version":"1","message":"important message","logger_name":"example",...,"stack_trace":"...","exdata":{"key":"value"},"context":{"more":"information"}}
As for the info-c
macro, the info-e
macro produces code that checks if the info log level is enabled and it
guaranties that the json transformation happens exactly once. The json transformation is handled by the
library jsonista
. If the first argument is not a Throwable, the library will wrap a string representation of that
value with ex-info
. The library logs a warning in that case.
(log/info-e 1)
;; {"@timestamp":"???","@version":"1","message":"Value 1 is not a throwable; wrapping in ex-info","logger_name":"ch.ch.codesmith.logger.core","level":"WARN",...}
;; {"@timestamp":"???","@version":"1","message":"1","logger_name":"example",...,"stack_trace":"clojure.lang.ExceptionInfo: 1","exdata":{}}
(log/info-e (ex-info "important message" {:key +}))
;; {"@timestamp":"???","@version":"1","message":"Serialization error","logger_name":"ch.ch.codesmith.logger.core","level":"WARN",...,"stack_trace":"com.fasterxml.jackson.core.JsonGenerationException: Cannot JSON encode object of class: class clojure.core$_PLUS_: clojure.core$_PLUS_@7a5a9ca1..."}
;; {"@timestamp":"???","@version":"1","message":"important message","logger_name":"example",...,"stack_trace":"...","exdata":"{:key #object[clojure.core$_PLUS_ 0x7a5a9ca1 \"clojure.core$_PLUS_@7a5a9ca1\"]}"}
spy
The library has also a spy
macro to intercept and log an expression and its value. The monadic variant logs with
the debug
level. The dyadic variant takes the log level as first argument; the log level can be given as string,
keyword or symbol.
(log/spy :info (+ 1 2))
;; {"@timestamp":"...","@version":"1","message":"spy","logger_name":"example","context":{"expression":"(+ 1 2)","value":3}}
;; 3
The library has three functions to configure it: set-context-logging-key!
, set-ex-data-logging-key!
and
set-context-pre-logging-transformation!
. The functions set-context-logging-key!
and set-ex-data-logging-key!
take
a string as argument. They configures the string that is used as a key to output the context and the exception data
respectively in the JSON output of the Logstash appender. By default, they are "context"
and "exdata"
respectively.
The function set-context-pre-logging-transformation!
takes a monadic function as an argument. It is applied to the
context before it is encoded in JSON. By default, it is the identity function. For instance, this function can be used
to filter out keys from the context.
For server/productive usage, you want to configure logback appenders with the LogstashEncoder
encoder. This will cause
logback to emit one line of JSON for every log entry. In the following example, we configure the ConsoleAppender
to
use the LogstashEncoder
.
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="false" debug="true">
<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>
<!-- libraries -->
<logger name="ch.qos.logback.classic" level="WARN"/>
<logger name="org.apache.http" level="WARN"/>
<logger name="ch.ch.codesmith.logger" level="WARN"/>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
While it is possible to use the previous configuration for development, it can be problematic to read stack traces
of Throwable
logged events as they will on a (very long) line. For development, we recommand to use the standard
pattern encoder. Use the %marker
pattern to print out the context. In the following example, we also keep the JSON
encoder so that you can see how the logs will be on production.
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" debug="true" scanPeriod="20 seconds">
<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/>
<appender name="STDOUT-JSON" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>
<appender name="STDOUT-TEXT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd'T'HH:mm:ss.SSS} %-5level %-18thread - %marker - %msg%n
</pattern>
</encoder>
</appender>
<!-- libraries -->
<logger name="ch.qos.logback.classic" level="WARN"/>
<logger name="ch.ch.codesmith.logger" level="WARN"/>
<root level="INFO">
<appender-ref ref="STDOUT-JSON"/>
<appender-ref ref="STDOUT-TEXT"/>
</root>
</configuration>
You can find these example configurations in the folder examples
.
Copyright 2017-2018 — AVA-X GmbH (Original code included in commit 5ab43e44008d08301a16d47dcf6bd8080cc8c288)
Copyright 2020 — Codesmith GmbH (All modifications since 5ab43e44008d08301a16d47dcf6bd8080cc8c288)
Codesmith Logger is licensed under Eclipe Public License v1.0
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close