|
| 1 | +(ns example.common.slf4j.utils |
| 2 | + "Macros for SLF4J logging with bound, current or explicit context." |
| 3 | + (:require [steffan-westcott.clj-otel.context :as context] |
| 4 | + [steffan-westcott.clj-otel.util :as util]) |
| 5 | + (:import (io.opentelemetry.context Context) |
| 6 | + (org.slf4j Logger LoggerFactory Marker) |
| 7 | + (org.slf4j.event Level) |
| 8 | + (org.slf4j.spi LoggingEventBuilder))) |
| 9 | + |
| 10 | +(def ^:private levels |
| 11 | + {:trace Level/TRACE |
| 12 | + :debug Level/DEBUG |
| 13 | + :info Level/INFO |
| 14 | + :warn Level/WARN |
| 15 | + :error Level/ERROR}) |
| 16 | + |
| 17 | +(defn log* |
| 18 | + "Internal fn to use `logger` to log a `level` message specified by `more`. |
| 19 | + `more` is `context? marker* throwable? kvs? (message arg*)?` where `kvs` is a |
| 20 | + map with string keys and vals that are string or fn that returns a string, |
| 21 | + `message` is a string or fn that returns a string and each arg may be an |
| 22 | + object or fn that returns an object." |
| 23 | + [^Logger logger ^Level level & more] |
| 24 | + (let [builder (.makeLoggingEventBuilder logger level) |
| 25 | + [context more] (if (instance? Context (first more)) |
| 26 | + [(first more) (rest more)] |
| 27 | + [nil more]) |
| 28 | + [^LoggingEventBuilder builder more] (loop [^LoggingEventBuilder builder builder |
| 29 | + more more] |
| 30 | + (if (instance? Marker (first more)) |
| 31 | + (recur (.addMarker builder (first more)) |
| 32 | + (rest more)) |
| 33 | + [builder more])) |
| 34 | + [^LoggingEventBuilder builder more] (if (instance? Throwable (first more)) |
| 35 | + [(.setCause builder (first more)) (rest more)] |
| 36 | + [builder more]) |
| 37 | + [^LoggingEventBuilder builder more] (if (map? (first more)) |
| 38 | + [(reduce-kv |
| 39 | + (fn [^LoggingEventBuilder builder ^String k v] |
| 40 | + (if (fn? v) |
| 41 | + (.addKeyValue builder k (util/supplier v)) |
| 42 | + (.addKeyValue builder k v))) |
| 43 | + builder |
| 44 | + (first more)) (rest more)] |
| 45 | + [builder more]) |
| 46 | + [message & args] more |
| 47 | + ^LoggingEventBuilder builder (if message |
| 48 | + (if (fn? message) |
| 49 | + (.setMessage builder (util/supplier message)) |
| 50 | + (.setMessage builder (str message))) |
| 51 | + builder) |
| 52 | + ^LoggingEventBuilder builder (reduce (fn [^LoggingEventBuilder builder arg] |
| 53 | + (if (fn? arg) |
| 54 | + (.addArgument builder (util/supplier arg)) |
| 55 | + (.addArgument builder arg))) |
| 56 | + builder |
| 57 | + args)] |
| 58 | + (if context |
| 59 | + (context/with-context! context |
| 60 | + (.log builder)) |
| 61 | + (.log builder)))) |
| 62 | + |
| 63 | +(defmacro log' |
| 64 | + "Internal macro to log a message." |
| 65 | + [^Level level & args] |
| 66 | + `(let [^Logger logger# (LoggerFactory/getLogger (str ~*ns*))] |
| 67 | + (when (.isEnabledForLevel logger# ~level) |
| 68 | + (log* logger# ~level ~@args)))) |
| 69 | + |
| 70 | +(defmacro log |
| 71 | + "Logs a `level` message specified by `more`. `more` is `marker* throwable? |
| 72 | + kvs? (message arg*)?` where `kvs` is a map with string keys and vals that |
| 73 | + are string or fn that returns a string, `message` is a string or fn that |
| 74 | + returns a string and each arg may be an object or fn that returns an object." |
| 75 | + [level & args] |
| 76 | + `(log' (get levels ~level Level/ERROR) ~@args)) |
| 77 | + |
| 78 | +(defmacro error |
| 79 | + "Write an ERROR message to the log. If first arg is a context, use as |
| 80 | + explicit context for the message. Otherwise, use bound or current context." |
| 81 | + [& args] |
| 82 | + `(log' Level/ERROR ~@args)) |
| 83 | + |
| 84 | +(defmacro warn |
| 85 | + "Write a WARN message to the log. If first arg is a context, use as |
| 86 | + explicit context for the message. Otherwise, use bound or current context." |
| 87 | + [& args] |
| 88 | + `(log' Level/WARN ~@args)) |
| 89 | + |
| 90 | +(defmacro info |
| 91 | + "Write an INFO message to the log. If first arg is a context, use as |
| 92 | + explicit context for the message. Otherwise, use bound or current context." |
| 93 | + [& args] |
| 94 | + `(log' Level/INFO ~@args)) |
| 95 | + |
| 96 | +(defmacro debug |
| 97 | + "Write a DEBUG message to the log. If first arg is a context, use as |
| 98 | + explicit context for the message. Otherwise, use bound or current context." |
| 99 | + [& args] |
| 100 | + `(log' Level/DEBUG ~@args)) |
| 101 | + |
| 102 | +(defmacro trace |
| 103 | + "Write a TRACE message to the log. If first arg is a context, use as |
| 104 | + explicit context for the message. Otherwise, use bound or current context." |
| 105 | + [& args] |
| 106 | + `(log' Level/TRACE ~@args)) |
0 commit comments