-
Notifications
You must be signed in to change notification settings - Fork 1
/
timbre.clj
277 lines (243 loc) · 12.7 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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
(ns active.clojure.logger.config.timbre
(:require [active.timbre-logstash :as timbre-logstash]
[active.timbre-riemann :as timbre-riemann]
[clojure.string :as string]
[riemann.client :as riemann]
[taoensso.encore :as encore]
[taoensso.timbre :as timbre]
[taoensso.timbre.appenders.core :as timbre-appenders]
[taoensso.timbre.appenders.3rd-party.rotor :as timbre-rotor]
[active.clojure.condition :as c]
[active.clojure.condition-hooks :as condition-hooks]
[active.clojure.config :as config]
[active.clojure.logger.config.riemann :as logger-riemann]))
;; Basic and initial timbre config, before the config is loaded and
;; applied. Note that appenders defined here, are completely replace
;; with the configured appenders.
(def basic-timbre-config ;; TODO: tune that a little
(dissoc timbre/example-config :appenders)) ;; we don't want to keep the initial appenders (a println-appender)
(defn make-timbre-config
[cmap]
(encore/merge-deep basic-timbre-config cmap))
(def log-events-command-config-setting
(config/setting :log-events-command-config
"Monad command config for running event log commands."
(config/one-of-range #{:timbre} :timbre)))
(def timbre-level-setting
(config/setting :level
"Log level for Timbre"
(config/one-of-range #{:trace :debug :info :warn :error :fatal :report}
:debug)))
(def timbre-appenders-setting
(config/setting :appenders
"Appender map for Timbre, where appenders must be defined as a vector of a function creating an appender, and it's arguments. So instead of (appender x) define it as [appender x]."
(config/optional-default-range
(config/map-of-range
config/keyword-range ;; arbitrary name
(config/any-value-range nil)) ;; resp [:spit-appender <any>]; see timbre-spec->appender
{:default '(println)})))
(def timbre-ns-whitelist-setting
(config/setting :ns-whitelist
"Whitelist for Timbre"
(config/predicate-range "Timbre whitelist"
vector?
[])))
(def timbre-ns-blacklist-setting
(config/setting :ns-blacklist
"Blacklist for Timbre"
(config/predicate-range "Timbre blacklist"
vector?
[])))
(def timbre-middleware-setting
(config/setting :middleware
"Middleware for Timbre, where middleware must be defined as a vector of a function creating the middleware, and it's arguments. So instead of (middleware x) define it as [middleware x]."
(config/predicate-range "Timbre middleware"
vector?
[])))
(def timbre-hostname-setting
(config/setting :hostname
"Hostname added to the context of all timbre events. Per default inherited from the toplevel :hostname setting."
(config/default-string-range (timbre/get-hostname))
:inherit? true))
(def timbre-application-setting
(config/setting :application
"Application name added to the context of all timbre events. Per default inherited from the toplevel :application setting."
(config/default-string-range nil)
:inherit? true))
(def timbre-timestamp-opts-setting
(config/setting :timestamp-opts
"Timestamp options for Timbre"
(config/schema-range
(config/schema
"Timestamp options for Timbre"
(config/setting
:pattern
"Pattern for Timbre's timestamp."
(config/any-range
(config/default-string-range "yyyy-MM-dd HH:mm:ss.SSS")
(config/one-of-range #{:iso8601 :rss2} :iso8601)))
(config/setting
:locale
"Locale language tag for Timbre's timestamp."
(config/any-range
(config/one-of-range #{:jvm-default} :jvm-default)
(config/default-string-range (.toLanguageTag ^java.util.Locale (java.util.Locale. "en")))))
(config/setting
:timezone
"Timezone id for Timbre's timestamp."
(config/any-range
(config/one-of-range #{:jvm-default :utc} :jvm-default)
(config/default-string-range (.getID ^java.util.TimeZone (java.util.TimeZone/getTimeZone "Europe/Amsterdam")))))))))
(def timbre-config-section
(config/section :timbre-config
(config/schema
"Configuration for Timbre, merged into the default configuration."
;; These could use fleshing out. Or not.
timbre-level-setting
timbre-appenders-setting
timbre-ns-whitelist-setting
timbre-ns-blacklist-setting
timbre-middleware-setting
timbre-timestamp-opts-setting
timbre-hostname-setting
timbre-application-setting)))
;; dummy: but it needs to exist for event logging to be a service
(defn pr-exception-stacktrace [err]
;; expecially prints conditions more pretty
(condition-hooks/print-stack-trace-of err))
(defn timbre-event->riemann [data]
;; return zero or more riemann events for one timbre appender data structure.
(let [{:keys [context instant hostname_ msg_ level]} data
stacktrace (when-let [?err (and (:?err_ data) (force (:?err_ data)))]
;; Note: ?err_ will be replaced by ?err in newer timbre versions.
(with-out-str (pr-exception-stacktrace ?err)))
[time time-ms] (logger-riemann/current-time-for-riemann)]
;; Note: context already may contain some riemann specific props too, like
;; :service, :state, :metric, :ttl.
[(-> (cond-> {:host (force hostname_)
;; :state nil
;; :description nil
;; :metric nil
;; :tags nil
:time time
:time-ms time-ms
;; :ttl nil
}
(some? stacktrace) (assoc :stacktrace stacktrace)
(= "event" (:type context)) (assoc :level (name level)
:description (force msg_)))
(merge context))]))
(declare -log-event!)
(defn riemann-appender [& [opts]]
(let [riemann-opts (-> {:host "localhost" :port 5555}
(merge (select-keys opts [:host :port
:tls? :key
:cert :ca-cert
:cache-dns?])))
;; Note: creating the client never throws
client (riemann/tcp-client riemann-opts)]
(let [at (str (:host riemann-opts) ":" (:port riemann-opts))]
(if (not (riemann/connected? client))
;; this'll be visible on the console, before timbre is reconfigured:
(-log-event! :warn (str "Could not connect to Riemann at " at ". Some messages will be dropped until Riemann can be reached."))
(-log-event! :debug (str "Connected to Riemann at " at "."))))
(-> (timbre-riemann/riemann-appender
(merge {:client client
:retry-fn (fn [promise repeat-nr message-nr]
;; we could (deref promise) here, resulting in a result message from the riemann server, or
;; in an IOException("no channels available") when the connection drops/server stopped, or in
;; an OverloadedException when the client produces more message than the server can handle.
;; For now, we think it's more important that the platform keeps on running.
false ;; don't retry.
)
:events-fn timbre-event->riemann}
opts))
;; cleanup-fn is our own extension to the appenders spec; see destroy-timbre-config! below
(assoc :cleanup-fn (fn [this]
(riemann/close! client))))))
(defn logstash-appender [host port & [opts]]
(timbre-logstash/timbre-json-appender host port
(merge {:pr-stacktrace pr-exception-stacktrace} opts)))
(defn rotor-appender [opts]
(timbre-rotor/rotor-appender opts))
(defn spit-appender [opts]
(timbre-appenders/spit-appender opts))
(defn println-appender [& [opts]]
(timbre-appenders/println-appender opts))
(defn timbre-spec->appender
"Convert an EDN Timbre appender spec to an actual appender.
An appender spec is a list starting with one of `{spit, rotor, logstash, println}`,
followed by keyword parameters corresponding to the respective appender."
[v]
(cond
(and (list? v) (not (empty? v)) (symbol? (first v)))
(case (first v)
;; example args: ({:fname "my.log"})
spit (apply spit-appender (rest v))
;; example args: ({:path "my.log" :max-size 1048576 :backlog 5}), :max-size is given in bytes
rotor (apply rotor-appender (rest v))
;; example args: ("localhost" 4660)
logstash (apply logstash-appender (rest v))
;; a :stream arg would be possible
println (apply println-appender (rest v))
riemann (apply riemann-appender (rest v))
(c/error `timbre-spec->appender "invalid Timbre appender spec" v))
:else (c/error `timbre-spec->appender "invalid Timbre appender spec" v)))
(defn output-fn
"Timbre output function, adapted to our needs"
([data] (output-fn nil data))
([{:keys [no-stacktrace?] :as opts} data]
(let [{:keys [level ?err_ vargs_ msg_ ?ns-str hostname_
timestamp_ ?line]} data
origin-info
(cond-> (or ?ns-str "?") ;; we never have line number, for which Timbre would print a ?
;; append a task or service name, and domain if we have one (e.g. log4j logs don't have one):
(:component timbre/*context*)
(str ", " (:component timbre/*context*)
(if-let [domain (:domain timbre/*context*)]
(str " [" domain "]")
"")))]
(str
@timestamp_ " "
@hostname_ " "
(string/upper-case (name level)) " "
"[" origin-info "] - "
(force msg_)
(when-not no-stacktrace?
(when-let [err (force ?err_)]
(str "\n" (with-out-str (pr-exception-stacktrace err)))))))))
(defn fixed-properties-timbre-middleware [hostname application]
;; A timbre middleware, that sets log event properties that should
;; always be set to a specific value; not matter who logs
;; something (including foreign libs)
(let [hostname_ (delay hostname)]
(fn [data]
(-> data
(assoc-in [:hostname_] hostname_)
(assoc-in [:context :application] application)))))
(defn configure-events-logging
"Returns an object that can be fed to
[[set-global-log-events-config!]]."
[timbre-config]
(make-timbre-config
{:level (config/access timbre-config timbre-level-setting)
:appenders (into {}
(map (fn [[k v]]
[k (timbre-spec->appender v)])
(config/access timbre-config timbre-appenders-setting)))
:ns-whitelist (config/access timbre-config timbre-ns-whitelist-setting)
:ns-blacklist (config/access timbre-config timbre-ns-blacklist-setting)
:middleware (conj (config/access timbre-config timbre-middleware-setting)
(fixed-properties-timbre-middleware (config/access timbre-config timbre-hostname-setting)
(config/access timbre-config timbre-application-setting)))
:output-fn output-fn
:timestamp-opts (let [tso (config/access timbre-config timbre-timestamp-opts-setting)]
{:pattern (get tso :pattern)
:locale (let [l (get tso :locale)]
(if (string? l)
(java.util.Locale/forLanguageTag l)
l))
:timezone (let [t (get tso :timezone)]
(if (string? t)
(java.util.TimeZone/getTimeZone ^String t)
t))})}))