bnadlerjr/logfmt

0.1.0


A clojure library for emitting logfmt.

dependencies

io.aviso/pretty
0.1.34
org.clojure/clojure
1.9.0



(this space intentionally left almost blank)
 

Utility library for emitting messages to STDOUT in logfmt. Intended to be used for projects that will be deployed to Heroku.

(ns logfmt.core
  (:require [io.aviso.ansi :as ansi]))

Indicates whether development mode is active.

(def 
  dev-mode false)

Updates dev-mode.

(defn set-dev-mode!
  [value]
  (alter-var-root #'dev-mode (constantly value)))
(defn- string-with-slash-or-whitespace?
  [s]
  (and (string? s)
       (re-find #"(\/|\s)" s)))

Formats a key / value pair as k=v. If the value contains a slash or whitespace, it is escaped with double quotes.

(defn- format-key-value-pairs
  [[k v]]
  (let [format-str (if (string-with-slash-or-whitespace? v)
                     "%s=\"%s\""
                     "%s=%s")]
    (format format-str (name k) v)))

When dev-mode is true, messages are colorized according to level.

(defn- determine-color-fn
  [level]
  (cond
    (= :info level) ansi/cyan
    (= :error level) ansi/red
    :else identity))

Build the message based on level, text and attrs. When dev-mode is true, messages are output in a slightly more readable format:

<level> | <text> <attr_key>=<attr-value> ...

When dev-mode is false, level and message are converted to key / value pairs and formatted just like attrs:

at=<level> msg=<text> <attr_key>=<attr_value> ...

(defn- full-message
  [level text attrs]
  (let [level-str (name level)
        color (determine-color-fn level)
        attr-str (clojure.string/join " " (map format-key-value-pairs attrs))]
    (if dev-mode
      (clojure.string/trimr
        (format "\n%s | %s %s" (color level-str) (color text) attr-str))
      (clojure.string/trim
        (format "at=%s msg=\"%s\" %s" level-str text attr-str)))))

Generic log function. For JVM projects, Heroku recommends using System.out.println, so we use that as opposed to *out* or Clojure's println.

(defn- log
  ([level message]
   (log level message {}))
  ([level message attrs]
   (.println System/out (full-message level message attrs))))

Log information level messages.

(def 
  info (partial log :info))

Log error level messages.

(def 
  error (partial log :error))
 

Ring middleware helpers.

(ns logfmt.ring.middleware
  (:require [logfmt.core :refer [info]])
  (:import (java.util UUID)))

Wraps a Ring request outputting two logging messages: one for the request and another for the response. Includes useful attributes such as path, method, status, etc.

Also adds a request-id attribute via the x-request-id header to both messages, so that messages can be matched. If the x-request-id header is not available, one will be generated.

Heroku will add a x-request-id header to all requests automatically, so the generation feature is mainly used when the Ring application is deployed elsewhere (i.e. locally).

(defn wrap-logger
  [handler]
  (fn [req]
    (let [start (System/currentTimeMillis)
          request-id (get-in req [:headers "x-request-id"] (UUID/randomUUID))
          method (clojure.string/upper-case (name (:request-method req)))
          path (str (:uri req)
                    (if-let [query-string (:query-string req)]
                      (str "?" query-string)))]
      (info (format "Started %s '%s'" method path)
            {:method method
             :path path
             :params (:params req)
             :request-id request-id})
      (let [response (handler req)
            status (:status response)
            duration (str (- (System/currentTimeMillis) start) "ms")]
        (info (format "Completed %s in %s" status duration)
              {:method method
               :path path
               :status status
               :duration duration
               :request-id request-id})
        response))))