A quick tutorial on how to set up request logging for a Clojure web app backend when using Ring and Jetty adapter.
First, create a new project with clj-new.
clojure -Tclj-new app :name acme/app
And then add the Ring dependencies to the deps.edn
file.
< :deps {org.clojure/clojure {:mvn/version "1.11.1"}}
---
> :deps {org.clojure/clojure {:mvn/version "1.11.1"}
> ring/ring-core {:mvn/version "1.6.3"}
> ring/ring-jetty-adapter {:mvn/version "1.6.3"}
> ring-logger/ring-logger {:mvn/version "1.1.1"}}
Next, set up the clojure tools logging with log4j2 logging backend.
Update the deps.edn
file by adding clj-log4j2
dependency and set:jvm-opts
5c5,6
< ring-logger/ring-logger {:mvn/version "1.1.1"}}
---
> ring-logger/ring-logger {:mvn/version "1.1.1"}
> clj-log4j2/clj-log4j2 {:mvn/version "0.4.0"}}
7c8,9
< {:run-m {:main-opts ["-m" "acme.app"]}
---
> {:run-m {:main-opts ["-m" "acme.app"]
> :jvm-opts ["-Dclojure.tools.logging.factory=clojure.tools.logging.impl/log4j2-factory"]}
Create a file resources/log4j2.properties
and save the file with the following content. Read more about the configuration options from the here.
status = warn
monitorInterval = 5
appender.console.type = Console
appender.console.name = STDOUT
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %date %level:%logger: %message%n%throwable
rootLogger.level = debug
rootLogger.appenderRef.stdout.ref = STDOUT
Next wrap the ring handler with the wrap-with-logger
middleware to log the requests. Configuration instructions can be found from the library github page.
(ns acme.app
(:require [clojure.tools.logging :as logging]
[ring.middleware.params]
[ring.adapter.jetty :as jetty]
[ring.logger :as logger])
(:gen-class))
(def port 3000)
(defn ring-handler [_request]
(logging/info "Info message")
(logging/debug "Debug message")
(logging/warn "Warn message")
{:status 200
:body "OK"})
(defonce server (atom nil))
(def app
(-> #'ring-handler
logger/wrap-with-logger))
(defn start! []
(logging/info "Listening on port: " port)
(reset! server
(jetty/run-jetty #'app {:port port})))
(defn -main []
(start!))
Now you are ready to run the server with clj -M:run-m
and you should see something like this in your console when the API is called with a POST
request.
❯ clj -M:run-m
2023-08-12 14:23:50.358:INFO::main: Logging initialized @1171ms
2023-08-12 14:23:50,440 INFO:acme.app: Server starting with arguments: {}
2023-08-12 14:23:50.452:INFO:oejs.Server:main: jetty-9.2.21.v20170120
2023-08-12 14:23:50.472:INFO:oejs.ServerConnector:main: Started ServerConnector@4602f874{HTTP/1.1}{0.0.0.0:3000}
2023-08-12 14:23:50.472:INFO:oejs.Server:main: Started @1284ms
2023-08-12 14:23:51,716 INFO:ring.logger: {:request-method :post, :uri "/", :server-name "localhost", :ring.logger/type :starting}
2023-08-12 14:23:51,717 INFO:acme.app: Info message
2023-08-12 14:23:51,717 WARN:acme.app: Warn message
2023-08-12 14:23:51,717 INFO:ring.logger: {:request-method :post, :uri "/", :server-name "localhost", :ring.logger/type :finish, :status 200, :ring.logger/ms 1}
That's about it for the minimal setup. Read more about advanced configuration from the sources.
Thanks for reading, hope you found this helpful.