Last active
December 10, 2017 06:23
-
-
Save rarous/2afcf6d6b4106c74aadc199b03dce771 to your computer and use it in GitHub Desktop.
Structured logging adapter for ring-logger middleware
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns ring.logger.cambium | |
(:require | |
[cambium.core :as log] | |
[ring.logger.messages :as messages] | |
[ring.logger.protocols :refer [Logger]] | |
[ring.logger :as logger])) | |
(defrecord CambiumLogger [] | |
Logger | |
(add-extra-middleware [_ handler] handler) | |
(log [_ level throwable message] | |
(if throwable | |
(log/log level (ex-data throwable) throwable message) | |
(log/log level message)))) | |
(defn make-cambium-logger [] | |
(CambiumLogger.)) | |
(defn wrap-with-logger | |
"Returns a Ring middleware handler which uses Cambium as logger. | |
Supported options are the same as of ring.logger/wrap-with-logger, except of | |
:logger-impl which is fixed to a CambiumLogger instance" | |
([handler options] | |
(logger/wrap-with-logger | |
handler | |
(merge options {:logger (make-cambium-logger) | |
:printer :structured}))) | |
([handler] (wrap-with-logger handler {}))) | |
(defn wrap-with-body-logger | |
"Returns a Ring middleware handler which logs request body payloads using | |
Cambium as logger." | |
[handler] | |
(logger/wrap-with-body-logger handler (make-cambium-logger))) | |
(defn- redact-map [m {:keys [redact-fn]}] | |
(if redact-fn (redact-fn m) m)) | |
(defn- make-structured-starting-message | |
[options {:keys [request-method uri remote-addr query-string headers]}] | |
{:method request-method | |
:uri (str uri (if query-string (str "?" query-string))) | |
:remote-addr remote-addr | |
:headers (redact-map headers options)}) | |
(defmethod messages/starting :structured | |
[options req] | |
(log/info (make-structured-starting-message options req) "Starting request")) | |
(defmethod messages/request-details :structured | |
[_ req] | |
(log/debug | |
(select-keys req [:character-encoding | |
:content-length | |
:content-type | |
:query-string | |
:remote-addr | |
:request-method | |
:scheme | |
:server-name | |
:server-port | |
:uri]) | |
"Request details")) | |
(defmethod messages/request-params :structured | |
[options {:keys [params]}] | |
(when params | |
(log/info (redact-map params options) "Params"))) | |
(defmethod messages/sending-response :structured | |
[options response] | |
(log/trace | |
(cond-> response | |
(:cookies response) (update-in [:cookies] keys) | |
(:headers response) (update-in [:headers] #(redact-map % options))) | |
"Sending response")) | |
(defn- make-and-log-finished-message | |
[{:keys [timing]} | |
{:keys [request-method uri remote-addr query-string]} | |
{:keys [status] :as resp} | |
title | |
time] | |
(let [log-message | |
{:method request-method | |
:uri (str uri (if query-string (str "?" query-string))) | |
:remote-addr remote-addr | |
:status status} | |
log-message (if timing | |
(assoc log-message :duration time) | |
log-message) | |
log-message (if (= status 302) | |
(assoc log-message :redirect (get-in resp [:headers "Location"])) | |
log-message)] | |
(if (and (number? status) (>= status 500)) | |
(log/error log-message "Error") | |
(log/info log-message title)))) | |
(defn- get-total-time [{:keys [logger-start-time logger-end-time] :as req}] | |
(- logger-end-time logger-start-time)) | |
(defmethod messages/finished :structured | |
[{:keys [timing] :as options} req resp] | |
(make-and-log-finished-message | |
options | |
req | |
resp | |
"Finished" | |
(when timing (get-total-time req)))) | |
(defn- redact-request [req options] | |
(let [redact #(redact-map % options)] | |
(-> req | |
(update-in [:headers] redact) | |
(update-in [:params] redact) | |
;;take the keys from :form-params, because they might be nested | |
;;and we can't redact them in that case | |
(update-in [:form-params] keys)))) | |
(defmethod messages/exception :structured | |
[{:keys [timing] :as options} | |
{:keys [request-method uri remote-addr] :as request} | |
throwable] | |
(let [message {:method request-method | |
:uri uri | |
:remote-addr remote-addr | |
:request (redact-request request options)} | |
message (if timing | |
(assoc message :duration (get-total-time request)) | |
message)] | |
(log/error message throwable "Uncaught exception"))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment