-
Notifications
You must be signed in to change notification settings - Fork 6
/
timbre.clj
184 lines (156 loc) · 5.8 KB
/
timbre.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
(ns com.palletops.log-config.timbre
"Timbre middleware functions"
(:require
[clojure.string :refer [join upper-case]]
[taoensso.timbre :as timbre]))
;;; # Namespace Specific Level Filtering
(defn min-level
"Returns a timbre middleware to filter messages for namespace by min
level."
[ns level]
(fn min-level [msg]
(if (or (not= ns (:ns msg))
(<= 0 (#'timbre/levels-compare (:level msg) level)))
msg)))
(defn min-levels
"Returns a timbre middleware to filter messages for namespace by a
map of min level for each ns."
[level-map]
(fn min-levels [msg]
(if-let [level (get level-map (:ns msg))]
(if (<= 0 (#'timbre/levels-compare (:level msg) level))
msg)
msg)))
(defn min-level-appender
"Returns a timbre appender that wraps the specified appender and
filters messages for namespace by a map of min level for each ns,
given in the :min-levels config key."
[appender]
(fn min-level-appender [{:keys [ap-config ns level] :as msg}]
(if-let [min-lvl (level (get (:min-levels ap-config {}) (:ns msg)))]
(if (<= 0 (#'timbre/levels-compare level min-lvl))
(appender msg))
(appender msg))))
;;; # Add Log Message Key based on a Var
(defn add-var
"Return a timbre middleware to add the value of the given var as a
kw in the message map."
[kw v]
{:pre [(var? v)]}
(fn add-var
[msg]
(assoc msg kw (var-get v))))
;;; # Context in Log Messages
(def ^{:dynamic true :doc "Thread specific context"}
*context* nil)
(defn context
"Return the current context."
[]
*context*)
(defmacro with-total-context
"Execute body within the given context."
[context & body]
`(binding [*context* ~context]
~@body))
(defmacro with-context
"Execute body with the given context merged onto the current context."
[context & body]
`(binding [*context* (merge *context* ~context)]
~@body))
(defmacro with-context-update
"Execute body with the given context merged onto the current context."
[[path f & args] & body]
`(binding [*context* (update-in *context* ~path ~f ~@args)]
~@body))
(def context-msg
"Add context to log messages on the :context key"
(add-var :context #'*context*))
(defn format-with-context
"A formatter that shows values in the :context key."
[{:keys [level throwable message timestamp hostname ns context] :as ev}
;; Any extra appender-specific opts:
& [{:keys [nofonts?] :as appender-fmt-output-opts}]]
(format "%s %s %s [%s]%s - %s%s"
timestamp hostname
(-> level name upper-case)
(if (seq context)
(str " " (join " " (map (fn [[k v]] (str k " " v)) context)))
"")
ns (or message "")
(or (timbre/stacktrace throwable "\n" (when nofonts? {})) "")))
;;; # Tags
;;; Tags provide a set of keywords on which log messages can be filtered. The
;;; `tags-message` middleware is used to add them to the log message.
(def ^:dynamic *tags* nil)
(defmacro with-tags
"Set the tags for any log messages in body."
[tags & body]
`(binding [*tags* ~tags]
(assert (set? *tags*) "The tags must be a set of Named items.")
~@body))
(def tags-msg
"Add tags to log messages on the :tags key"
(add-var :tags #'*tags*))
;;; # Domain logging
;;; Domain logging adds a keyword which is used as an alternative to
;;; the namespace.
;;; The `format-with-domain` timbre formatter will show the domain in
;;; preference to the namespace.
(def ^:dynamic *domain* nil)
(defmacro with-domain
"Set the domain for any log messages in body."
[domain & body]
`(binding [*domain* ~domain]
(assert (or (string? *domain*) (keyword? *domain*) (symbol? *domain*))
"The domain must be a string, keyword or symbol.")
~@body))
(def domain-msg
"Add domain to log messages on the :domain key"
(add-var :domain #'*domain*))
(defn format-with-domain
"A formatter that shows domain rather than ns when it is set."
[{:keys [level throwable message timestamp hostname ns domain]}
& [{:keys [nofonts?] :as appender-fmt-output-opts}]]
;; <timestamp> <hostname> <LEVEL> [<domain or ns>] - <message> <throwable>
(format "%s %s %s [%s] - %s%s"
timestamp hostname
(-> level name upper-case)
(or (and domain (name domain)) ns)
(or message "")
(or (timbre/stacktrace throwable "\n" (when nofonts? {})) "")))
;;; # Formatter for Context and Domain
(defn format-with-domain-context
"A formatter that shows domain rather than ns when it is set, and
adds any :context values."
[{:keys [level throwable message timestamp hostname ns domain context]}
& [{:keys [nofonts?] :as appender-fmt-output-opts}]]
;; <timestamp> <hostname> <LEVEL> [<domain or ns>] - <message> <throwable>
(format "%s %s %s [%s]%s - %s%s"
timestamp hostname
(-> level name upper-case)
(or (and domain (name domain)) ns)
(if (seq context)
(str " " (join " " (map (fn [[k v]] (str k " " v)) context)))
"")
(or message "")
(or (timbre/stacktrace throwable "\n" (when nofonts? {})) "")))
;;; # Logging Threshold Fixture
(defn logging-threshold-fixture
"Change the logging threshold inside a scope."
([level appender]
(fn [f]
(let [config @timbre/config]
(timbre/set-config! [:appenders appender :min-level] level)
(try (f)
(finally (timbre/merge-config! config))))))
([level] (logging-threshold-fixture level :standard-out))
([] (logging-threshold-fixture :warn :standard-out)))
(defmacro suppress-logging
"Suppress all logging inside the scope of the function."
[& body]
`(let [config# @timbre/config]
(try
(doseq [appender# (keys (:appenders config#))]
(timbre/set-config! [:appenders appender# :enabled?] false))
~@body
(finally (timbre/merge-config! config#)))))