-
Notifications
You must be signed in to change notification settings - Fork 180
Add CIDER log middleware #773
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
c320457
Add CIDER log middleware
r0man 8d7c958
Remove + from install profile
r0man a16f5fd
Prefix NREPL ops with cider
r0man 2687e51
Move java.util.logging config into test profile
r0man d37a305
Clarify default-size and default-threshold doc strings
r0man 9df2647
Fix type hint of exception-name
r0man 1e7dea2
Move test.check to the test profile
r0man 12c820c
Use keep instead of remove nil?
r0man 919cd39
Move log specs to test dir
r0man 09fac63
Instrument in test namespace
r0man 17f9a36
Complete repl namespace specs
r0man e195d14
Remove obsolete disable-warning for deprecation
r0man f7f92bb
Add namespace doc strings and metadata
r0man 874fc48
Add changelog entry
r0man ba09c4f
Use the next cider-nrepl version in metadata
r0man 59f4535
Add cider.nrepl/wrap-log to usage doc
r0man d0bae21
Add cider.nrepl/wrap-log to cider-middleware
r0man f249be0
Add wrap-log to supplied middlewares
r0man a1541ef
Add log ops to supplied middleware
r0man f6ef379
Accept keyword arguments in cider.log.repl
r0man File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
(ns cider.log.appender | ||
"A log appender that captures log events in memory." | ||
{:author "r0man"} | ||
(:require [cider.log.event :as event])) | ||
|
||
(def ^:private default-size | ||
"The default number of events captured by an appender." | ||
100000) | ||
|
||
(def ^:private default-threshold | ||
"The default threshold in percentage after which log events are cleaned up. | ||
|
||
Events of a log appender are cleanup up if the number of events reach the | ||
`default-size` plus the `default-threshold` percentage of | ||
`default-threshold`." | ||
10) | ||
|
||
(defn- garbage-collect? | ||
"Whether to garbage collect events, or not." | ||
[{:keys [event-index size threshold]}] | ||
(> (count event-index) (+ size (* size (/ threshold 100.0))))) | ||
|
||
(defn- garbage-collect-events | ||
"Garbage collect some events of the `appender`." | ||
[{:keys [events event-index size] :as appender}] | ||
(if (garbage-collect? appender) | ||
(assoc appender | ||
:events (take size events) | ||
:event-index (apply dissoc event-index (map :id (drop size events)))) | ||
appender)) | ||
|
||
(defn- add-event? | ||
"Whether the `event` should be added to the appender." | ||
[{:keys [filter-fn]} event] | ||
(or (nil? filter-fn) (filter-fn event))) | ||
|
||
(defn- notify-consumers | ||
[{:keys [consumers] :as appender} event] | ||
(doseq [{:keys [callback filter-fn] :as consumer} (vals consumers) | ||
:when (filter-fn event)] | ||
(callback consumer event)) | ||
appender) | ||
|
||
(defn- enqueue-event | ||
"Enqueue the `event` to the event list of `appender`." | ||
[appender event] | ||
(update appender :events #(cons event %))) | ||
|
||
(defn- index-event | ||
"Add the `event` to the index of `appender`." | ||
[appender event] | ||
(assoc-in appender [:event-index (:id event)] event)) | ||
|
||
(defn add-consumer | ||
"Add the `consumer` to the `appender`." | ||
[appender {:keys [id filters] :as consumer}] | ||
(assert (not (get-in appender [:consumers id])) | ||
(format "Consumer %s already registered" id)) | ||
(assoc-in appender [:consumers id] | ||
(-> (select-keys consumer [:callback :filters :id]) | ||
(assoc :filter-fn (event/search-filter (:levels appender) filters))))) | ||
|
||
(defn add-event | ||
"Add the `event` to the `appender`." | ||
[appender event] | ||
(if (add-event? appender event) | ||
(-> (enqueue-event appender event) | ||
(index-event event) | ||
(notify-consumers event) | ||
(garbage-collect-events)) | ||
appender)) | ||
|
||
(defn clear | ||
"Clear the events from the `appender`." | ||
[appender] | ||
(assoc appender :events [] :event-index {})) | ||
|
||
(defn consumers | ||
"Return the consumers of the `appender`." | ||
[appender] | ||
(vals (:consumers appender))) | ||
|
||
(defn consumer-by-id | ||
"Find the consumer of `appender` by `id`." | ||
[appender id] | ||
(some #(and (= id (:id %)) %) (consumers appender))) | ||
|
||
(defn event | ||
"Lookup the event by `id` from the log `appender`." | ||
[appender id] | ||
(get (:event-index appender) id)) | ||
|
||
(defn events | ||
"Return the events from the `appender`." | ||
[appender] | ||
(take (:size appender) (:events appender))) | ||
|
||
(defn make-appender | ||
"Make a hash map appender." | ||
[{:keys [id filters levels logger size threshold]}] | ||
(cond-> {:consumers {} | ||
:event-index {} | ||
:events nil | ||
:filters (or filters {}) | ||
:id id | ||
:levels levels | ||
:size (or size default-size) | ||
:threshold (or threshold default-threshold)} | ||
(map? filters) | ||
(assoc :filter-fn (event/search-filter levels filters)) | ||
logger | ||
(assoc :logger logger))) | ||
|
||
(defn remove-consumer | ||
"Remove the `consumer` from the `appender`." | ||
[appender consumer] | ||
(update appender :consumers dissoc (:id consumer))) | ||
|
||
(defn update-appender | ||
"Update the log `appender`." | ||
[appender {:keys [filters size threshold]}] | ||
(cond-> appender | ||
(map? filters) | ||
(assoc :filters filters :filter-fn (event/search-filter (:levels appender) filters)) | ||
(pos-int? size) | ||
(assoc :size size) | ||
(nat-int? threshold) | ||
(assoc :threshold threshold))) | ||
|
||
(defn update-consumer | ||
"Update the `consumer` of the `appender`." | ||
[appender {:keys [id filters] :as consumer}] | ||
(update-in appender [:consumers id] | ||
(fn [existing-consumer] | ||
(assert (:id existing-consumer) | ||
(format "Consumer %s not registered" id)) | ||
(-> existing-consumer | ||
(merge (select-keys consumer [:filters])) | ||
(assoc :filter-fn (event/search-filter (:levels appender) filters)))))) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
(ns cider.log.event | ||
"Log event related utilities like searching and calculating frequencies." | ||
{:author "r0man"} | ||
(:require [clojure.string :as str]) | ||
(:import [java.util.regex Pattern])) | ||
|
||
(defn- exception-name | ||
"Return the `exception` class name." | ||
[^Throwable exception] | ||
(some-> exception .getClass .getName)) | ||
|
||
(defn exception-frequencies | ||
"Return the exception name frequencies of `events`." | ||
[events] | ||
(frequencies (keep #(some-> % :exception exception-name) events))) | ||
|
||
(defn logger-frequencies | ||
"Return the logger name frequencies of `events`." | ||
[events] | ||
(frequencies (map :logger events))) | ||
|
||
(defn level-frequencies | ||
"Return the log level frequencies of `events`." | ||
[events] | ||
(frequencies (map :level events))) | ||
|
||
(defn search-filter | ||
"Return a predicate function that computes if a given event matches the search criteria." | ||
[levels {:keys [end-time exceptions level loggers pattern start-time threads]}] | ||
(let [exceptions (set exceptions) | ||
level->weight (into {} (map (juxt :name :weight) levels)) | ||
level-weight (when (or (string? level) (keyword? level)) | ||
(some-> level name str/upper-case keyword level->weight)) | ||
loggers (set loggers) | ||
threads (set threads) | ||
pattern (cond | ||
(string? pattern) | ||
(try (re-pattern pattern) (catch Exception _)) | ||
(instance? Pattern pattern) | ||
pattern)] | ||
(if (or (seq exceptions) (seq loggers) (seq threads) level-weight pattern start-time end-time) | ||
(fn [event] | ||
(and (or (empty? exceptions) | ||
(contains? exceptions (some-> event :exception exception-name))) | ||
(or (nil? level-weight) | ||
(>= (level->weight (:level event)) level-weight)) | ||
(or (empty? loggers) | ||
(contains? loggers (:logger event))) | ||
(or (empty? threads) | ||
(contains? threads (:thread event))) | ||
(or (not pattern) | ||
(some->> event :message (re-matches pattern))) | ||
(or (not (nat-int? start-time)) | ||
(>= (:timestamp event) start-time)) | ||
(or (not (nat-int? end-time)) | ||
(< (:timestamp event) end-time)))) | ||
(constantly true)))) | ||
|
||
(defn search | ||
"Search the log events by `criteria`." | ||
[levels {:keys [filters limit offset] :as _criteria} events] | ||
(cond->> events | ||
(map? filters) | ||
(filter (search-filter levels filters)) | ||
(nat-int? offset) | ||
(drop offset) | ||
true | ||
(take (if (nat-int? limit) limit 500)))) | ||
|
||
(defn thread-frequencies | ||
"Return the thread frequencies of `events`." | ||
[events] | ||
(frequencies (map (comp name :thread) events))) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.