-
Notifications
You must be signed in to change notification settings - Fork 175
/
log.clj
296 lines (267 loc) · 11.1 KB
/
log.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
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
(ns cider.nrepl.middleware.log
"Capture, debug, inspect and view log events emitted by Java logging frameworks."
{:author "r0man"
:added "0.32.0"}
(:require [cider.nrepl.middleware.inspect :as middleware.inspect]
[cider.nrepl.middleware.util.error-handling :refer [with-safe-transport]]
[haystack.analyzer :as analyzer]
[haystack.parser.clojure.throwable :as throwable]
[logjam.event :as event]
[logjam.framework :as framework]
[nrepl.middleware.print :as print]
[nrepl.misc :refer [response-for]]
[nrepl.transport :as transport]
[orchard.inspect :as orchard.inspect])
(:import (java.io StringWriter)
(java.util UUID)))
(defn- select-consumer
"Return the log `consumer` in a Bencode compatible format."
[consumer]
(-> (select-keys consumer [:id :filters])
(update :id str)))
(defn- select-appender
"Return the log `appender` in a Bencode compatible format."
[appender]
(-> (select-keys appender [:filters :logger :id :size :threshold])
(assoc :consumers (map select-consumer (vals @(:consumers appender))))))
(defn- select-framework
"Return the log `frameowrk` in a Bencode compatible format."
[framework]
(-> (select-keys framework [:id :javadoc-url :name :root-logger :website-url])
(assoc :appenders (map (comp select-appender deref)
(framework/appenders framework)))
(assoc :levels (map #(select-keys % [:name :category :weight]) (:levels framework)))))
(defn- select-exception
"Return the `exception` in a Bencode compatible format."
[exception]
(let [exception-map (throwable/Throwable->map exception)
strip-cause #(dissoc % :data :trace)]
(cond-> (strip-cause exception-map)
(seq (:via exception-map))
(update :via #(map strip-cause %)))))
(defn- select-event
"Return the log `event` in a Bencode compatible format."
[{:keys [exception id] :as event}]
(cond-> (select-keys event [:exception :level :logger :message :id :thread :timestamp])
(uuid? id)
(update :id str)
(instance? Throwable exception)
(update :exception select-exception)))
;; TODO: Double check this! Sometimes inspecting a log event works only after
;; inspecting something else with the Cider inspector.
(defn- inspect-value
"Show `value` in the Cider inspector"
[{:keys [page-size max-atom-length max-coll-size] :as msg} value]
(let [inspector (middleware.inspect/swap-inspector!
msg #(-> (assoc % :page-size (or page-size 32)
:indentation 0
:max-atom-length max-atom-length
:max-coll-size max-coll-size)
(orchard.inspect/start value)))]
(#'middleware.inspect/inspector-response msg inspector)))
(defn- framework
"Lookup the framework from the :framework key of the nREPL message."
[{:keys [session framework]}]
(or (get-in (meta session) [::frameworks framework])
(throw (ex-info "Log framework not found"
{:error :log-framework-not-found
:framework framework}))))
(defn- filters
"Extract the filters from an nREPL dictinary."
[{:keys [end-time exceptions level loggers pattern start-time threads]}]
(cond-> {}
(nat-int? end-time)
(assoc :end-time end-time)
(and (seq exceptions) (every? string? exceptions))
(assoc :exceptions exceptions)
(or (string? level) (keyword? level))
(assoc :level (keyword level))
(and (seq loggers) (every? string? loggers))
(assoc :loggers loggers)
(string? pattern)
(assoc :pattern pattern)
(nat-int? start-time)
(assoc :start-time start-time)
(and (seq threads) (every? string? threads))
(assoc :threads threads)))
(defn- appender
"Make an appender map from the :appender, :filters, :size
and :threshold keys of the nREPL message."
[{:keys [appender logger size threshold] :as msg}]
(when (string? appender)
(cond-> {:id appender}
(map? (:filters msg))
(assoc :filters (filters (:filters msg)))
(string? logger)
(assoc :logger logger)
(pos-int? size)
(assoc :size size)
(nat-int? threshold)
(assoc :threshold threshold))))
(defn- consumer
"Make a consumer map from the :consumer and :filters keys of the nREPL message."
[{:keys [consumer] :as msg}]
(when (string? consumer)
(cond-> {:id consumer}
(map? (:filters msg))
(assoc :filters (filters (:filters msg))))))
(defn- event
"Lookup the log event from the :framework, :appender and :event
keys of the nREPL `msg`."
[{:keys [event] :as msg}]
(or (framework/event (framework msg) (appender msg) (UUID/fromString event))
(throw (ex-info "Log event not found"
{:error :log-event-not-found
:framework (:framework msg)
:appender (:appender msg)
:event event}))))
(defn swap-framework!
"Swap the framework bound in the session by applying `f` with `args`."
[msg f & args]
(if-let [framework (framework msg)]
(-> (:session msg)
(alter-meta! update-in [::frameworks (:id framework)] #(apply f % args))
(get-in [::frameworks (:id framework)]))
(throw (ex-info "Log framework not found"
{:type :log-framework-not-found
:framework (:framework msg)}))))
(defn add-appender-reply
"Add an appender to a log framework."
[msg]
(let [appender (appender msg)]
{:cider/log-add-appender
(-> (swap-framework! msg framework/add-appender appender)
(framework/appender appender)
(deref)
(select-appender))}))
(defn add-consumer-reply
"Add a consumer to an appender of a log framework."
[{:keys [consumer filters transport] :as msg}]
(let [appender (appender msg)
consumer {:id (or consumer (str (UUID/randomUUID)))
:filters (or filters {})
:callback (fn [consumer event]
(->> (response-for msg
:cider/log-consumer (str (:id consumer))
:cider/log-event (select-event event)
:status :cider/log-event)
(transport/send transport)))}]
{:cider/log-add-consumer
(-> (swap-framework! msg framework/add-consumer appender consumer)
(framework/consumer appender consumer)
(select-consumer))}))
(defn clear-appender-reply
"Clear all events of a log appender."
[msg]
(let [appender (appender msg)]
{:cider/log-clear-appender
(-> (swap-framework! msg framework/clear-appender appender)
(framework/appender appender)
(deref)
(select-appender))}))
(defn analyze-stacktrace-reply
"Show the stacktrace of a log event in the debugger."
[{:keys [transport ::print/print-fn] :as msg}]
(let [event (event msg)]
(if-let [exception (:exception event)]
(do (doseq [cause (analyzer/analyze exception print-fn)]
(transport/send transport (response-for msg cause)))
(transport/send transport (response-for msg :status :done)))
(transport/send transport (response-for msg :status :no-error)))))
(defn exceptions-reply
"Return the exceptions and their frequencies for the given framework and appender."
[msg]
{:cider/log-exceptions (->> (framework/events (framework msg) (appender msg))
(event/exception-frequencies))})
(defn frameworks-reply
"Return the available log frameworks."
[{:keys [session]}]
{:cider/log-frameworks (->> (meta session)
::frameworks vals
(map select-framework))})
(defn format-event-reply
"Format a log event."
[{:keys [::print/print-fn] :as msg}]
(let [event (event msg)
writer (StringWriter.)]
(print-fn event writer)
{:cider/log-format-event (str writer)}))
(defn inspect-event-reply
"Inspect a log event."
[msg]
(inspect-value msg (event msg)))
(defn levels-reply
"Return the log levels and their frequencies for the given framework and appender."
[msg]
{:cider/log-levels (->> (framework/events (framework msg) (appender msg))
(event/level-frequencies))})
(defn loggers-reply
"Return the loggers and their frequencies for the given framework and appender."
[msg]
{:cider/log-loggers (->> (framework/events (framework msg) (appender msg))
(event/logger-frequencies))})
(defn remove-appender-reply
"Remove an appender from a log framework."
[msg]
(let [appender (appender msg)]
(swap-framework! msg framework/remove-appender appender)
{:cider/log-remove-appender {:id (str (:id appender))}}))
(defn remove-consumer-reply
"Remove a consumer from the appender of a log framework."
[msg]
(let [appender (appender msg)
consumer (consumer msg)]
(swap-framework! msg framework/remove-consumer appender consumer)
{:cider/log-remove-consumer (select-consumer consumer)}))
(defn update-appender-reply
"Update the appender of a log framework."
[msg]
(let [appender (appender msg)]
{:cider/log-update-appender
(-> (swap-framework! msg framework/update-appender appender)
(framework/appender appender)
(deref)
(select-appender))}))
(defn update-consumer-reply
"Update the consumer of a log appender."
[msg]
(let [appender (appender msg)
consumer (consumer msg)]
{:cider/log-update-consumer
(-> (swap-framework! msg framework/update-consumer appender consumer)
(framework/consumer appender consumer)
(select-consumer))}))
(defn search-reply
"Search the log events of an appender."
[msg]
{:cider/log-search
(->> (select-keys msg [:filters :limit :offset])
(framework/search-events (framework msg) (appender msg))
(map select-event))})
(defn threads-reply
"Return the threads and their frequencies for the given framework and appender."
[msg]
{:cider/log-threads (->> (framework/events (framework msg) (appender msg))
(event/thread-frequencies))})
(defn handle-log
"Handle nREPL log operations."
[handler {:keys [session] :as msg}]
(when-not (contains? (meta session) ::frameworks)
(alter-meta! session assoc ::frameworks (framework/resolve-frameworks)))
(with-safe-transport handler msg
"cider/log-add-appender" add-appender-reply
"cider/log-add-consumer" add-consumer-reply
"cider/log-analyze-stacktrace" analyze-stacktrace-reply
"cider/log-clear-appender" clear-appender-reply
"cider/log-exceptions" exceptions-reply
"cider/log-format-event" format-event-reply
"cider/log-frameworks" frameworks-reply
"cider/log-inspect-event" inspect-event-reply
"cider/log-levels" levels-reply
"cider/log-loggers" loggers-reply
"cider/log-remove-appender" remove-appender-reply
"cider/log-remove-consumer" remove-consumer-reply
"cider/log-search" search-reply
"cider/log-update-appender" update-appender-reply
"cider/log-update-consumer" update-consumer-reply
"cider/log-threads" threads-reply))