Ring middleware to log response time and other details of each request that arrives to your server.
transform-fn
to
transform to other representations (string, JSON).log-fn
for switching to other log backends (timbre, etc.)Add the dependency to your project.
Leiningen:
[ring-logger "1.1.1"]
deps.edn:
ring-logger/ring-logger {:mvn/version "1.1.1"}
Add the middleware to your ring stack:
(ns foo
(:require [ring.adapter.jetty :as jetty]
[ring.logger :as logger]))
(defn my-ring-app [request]
{:status 200
:headers {"Content-Type" "text/html"}
:body "Hello world!"})
(jetty/run-jetty (logger/wrap-with-logger my-ring-app) {:port 8080})
Example output:
INFO ring.logger: {:request-method :get, :uri "/", :server-name "localhost", :ring.logger/type :starting}
DEBUG ring.logger: {:request-method :get, :uri "/", :server-name "localhost", :ring.logger/type :params, :params {:name "ring-logger", :password "[REDACTED]"}}
INFO ring.logger: {:request-method :get, :uri "/", :server-name "localhost", :ring.logger/type :finish, :status 200, :ring.logger/ms 11}
ring.logger comes with more fine-grained middleware apart from wrap-with-logger
:
wrap-log-request-start
: Logs the start of the requestwrap-log-response
wrap-log-request-params
: Logs the request parameters, using redaction to hide sensitive values (passwords, tokens, etc)To log just the start and finish of requests (no parameters):
(-> handler
logger/wrap-log-response
;; more middleware to parse params, cookies, etc.
logger/wrap-log-request-start)
To measure request latency, wrap-log-response
will use the ring.logger/start-ms
key added by wrap-log-request-start
if both middlewares are being used, or will call System/currentTimeMillis
to obtain the value by itself.
To explicitly log start, finish, and parameters of requests:
(-> (handler)
wrap-log-response
;; the following line must come before `wrap-params`
(wrap-log-request-params {:transform-fn #(assoc % :level :info)})
wrap-keyword-params ;; optional
wrap-params ;; required
wrap-log-request-start)
Note that in the above example, the :transform-fn
is optional. You can either bump your log level to DEBUG or use :transform-fn to adjust the log level of params logging before it hits the log-fn. The latter is often preferable, since DEBUG tends to be very noisy.
Also see the example.
Other logging backends can be plugged by passing the log-fn
option. This is how you could use
timbre instead of c.t.logging:
(require '[timbre.core :as timbre])
(-> handler
(logger/wrap-log-response {:log-fn (fn [{:keys [level throwable message]}]
(timbre/log level throwable message))}))
All messages will be usually timestamped by your logging infrastructure.
This is especially useful when also using ring.middleware.stacktrace.
(wrap-with-logger app {:log-exceptions? false})
Sensitive information in params and headers can be redacted before it's sent to the logs.
This is very important: Nobody wants user passwords or authentication tokens to get to the logs and live there forever, in plain text, right?
By default, ring-logger will redact any parameter whose name is in the
ring.logger/default-redact-key?
set (:password, :token, :secret, etc).
You can pass your own set or function to determine which keys to redact
as the redact-key?
option
(wrap-with-logger app {:redact-key? #{:senha :token})
If you wish to restrict logging to certain paths (or other conditions), combine ring-logger with ring.middleware.conditional, like so:
(:require [ring.middleware.conditional :as c :refer [if-url-starts-with
if-url-doesnt-start-with
if-url-matches
if-url-doesnt-match]])
(def my-ring-app
(-> handler
(if-url-starts-with "/foo" wrap-with-logger)
;; Or:
;; (c/if some-test-fn wrap-with-logger)
;; etc.
wrap-with-other-handler))
Consult the ring.middleware.conditional docs for full details.
pjlegato/ring.middleware.logger: ring-logger started as a fork of ring.middleware.logger. It's a great option if you don't mind pulling a transitive dependency on onelog & log4j.
lambdaisland/ring.middleware.logger: a fork of r.m.logger that replaces onelog with clojure.tools.logging
Pull requests are welcome!
Copyright (C) 2015-2018 Nicolás Berger Copyright (C) 2012-2014 Paul Legato.
Distributed under the Eclipse Public License, the same as Clojure.