emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[nongnu] elpa/cider ada5df5bff: Add CIDER Log Mode


From: ELPA Syncer
Subject: [nongnu] elpa/cider ada5df5bff: Add CIDER Log Mode
Date: Wed, 5 Jul 2023 03:59:29 -0400 (EDT)

branch: elpa/cider
commit ada5df5bfff2f43956902e6078688149d702a93d
Author: Roman Scherer <roman@burningswell.com>
Commit: Bozhidar Batsov <bozhidar@batsov.dev>

    Add CIDER Log Mode
---
 .dir-locals.el                                |    4 +-
 CHANGELOG.md                                  |    1 +
 cider-eval.el                                 |    1 +
 cider-log.el                                  | 1403 +++++++++++++++++++++++++
 cider-mode.el                                 |    7 +
 cider.el                                      |    2 +-
 doc/modules/ROOT/assets/images/cider-log.png  |  Bin 0 -> 506394 bytes
 doc/modules/ROOT/nav.adoc                     |    1 +
 doc/modules/ROOT/pages/debugging/logging.adoc |  274 +++++
 test/cider-log-test.el                        |   52 +
 10 files changed, 1743 insertions(+), 2 deletions(-)

diff --git a/.dir-locals.el b/.dir-locals.el
index 292d4dcf53..14b1c7804c 100644
--- a/.dir-locals.el
+++ b/.dir-locals.el
@@ -28,7 +28,9 @@
                               (cl-defun . 2)
                               (with-parsed-tramp-file-name . 2)
                               (thread-first . 0)
-                              (thread-last . 0)))))
+                              (thread-last . 0)
+                              (transient-define-prefix . defmacro)
+                              (transient-define-suffix . defmacro)))))
 
 ;; To use the bug-reference stuff, do:
 ;;     (add-hook 'text-mode-hook #'bug-reference-mode)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 141ed0545c..f4969d6b5a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
 
 ### New features
 
+- [#3352](https://github.com/clojure-emacs/cider/pull/3352) Add CIDER Log 
Mode, a major mode that allows you to capture, debug, inspect and view log 
events emitted by Java logging frameworks.
 - [#3354](https://github.com/clojure-emacs/cider/issues/3354): Add new 
customization variable `cider-reuse-dead-repls` to control how dead REPL 
buffers are reused on new connections.
 
 ### Bugs fixed
diff --git a/cider-eval.el b/cider-eval.el
index eccea291f5..110505bd88 100644
--- a/cider-eval.el
+++ b/cider-eval.el
@@ -320,6 +320,7 @@ If CONNECTION is nil, use `cider-current-repl'."
     "cider.nrepl/wrap-format"
     "cider.nrepl/wrap-info"
     "cider.nrepl/wrap-inspect"
+    "cider.nrepl/wrap-log"
     "cider.nrepl/wrap-macroexpand"
     "cider.nrepl/wrap-ns"
     "cider.nrepl/wrap-out"
diff --git a/cider-log.el b/cider-log.el
new file mode 100644
index 0000000000..80635d5034
--- /dev/null
+++ b/cider-log.el
@@ -0,0 +1,1403 @@
+;;; cider-log.el --- Log inspection functionality for Clojure -*- 
lexical-binding: t -*-
+
+;; Copyright © 2023 Bozhidar Batsov and CIDER contributors
+
+;; Author: r0man <roman@burningswell.com>
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;; This file is not part of GNU Emacs.
+
+;;; Commentary:
+
+;; Log inspection functionality for Clojure.
+
+;;; Code:
+
+(require 'cider-inspector)
+(require 'cider-stacktrace)
+(require 'cl-lib)
+(require 'logview)
+(require 'org)
+(require 'seq)
+(require 'transient)
+
+(defcustom cider-log-framework-name nil
+  "The name of the current log framework."
+  :group 'cider
+  :package-version '(cider . "1.8.0")
+  :safe #'stringp
+  :type 'string)
+
+(defcustom cider-log-appender-id "cider-log"
+  "The name of the default log appender."
+  :group 'cider
+  :package-version '(cider . "1.8.0")
+  :safe #'stringp
+  :type 'string)
+
+(defcustom cider-log-buffer "*cider-log*"
+  "The name of the log buffer."
+  :group 'cider
+  :package-version '(cider . "1.8.0")
+  :safe #'stringp
+  :type 'string)
+
+(defcustom cider-log-event-buffer "*cider-log-event*"
+  "The name of the log event buffer."
+  :group 'cider
+  :package-version '(cider . "1.8.0")
+  :safe #'stringp
+  :type 'string)
+
+(defcustom cider-log-max-message-length 500
+  "The maximum length of the log message to display."
+  :group 'cider
+  :package-version '(cider . "1.8.0")
+  :safe #'integerp
+  :type 'integer)
+
+(defcustom cider-log-pagination-limit 250
+  "The maximum number of log events to return when searching events."
+  :group 'cider
+  :package-version '(cider . "1.8.0")
+  :safe #'integerp
+  :type 'integer)
+
+(defcustom cider-log-pagination-offset 0
+  "The offset from which to return results when searching events."
+  :group 'cider
+  :package-version '(cider . "1.8.0")
+  :safe #'integerp
+  :type 'integer)
+
+(defvar cider-log--initialized-once-p nil
+  "Set to t if log framework and appender have been initialized once.")
+
+(defvar cider-log-framework nil
+  "The current log framework to use.")
+
+(defvar cider-log-appender nil
+  "The current log appender.")
+
+(defvar cider-log-appender-size 100000
+  "The size of the log appender.")
+
+(defvar cider-log-appender-threshold 10
+  "The threshold in percent of the log appender.")
+
+(defvar-local cider-log-consumer nil
+  "The current log consumer.")
+
+;; Filters
+
+(defvar cider-log--end-time-filter nil)
+(defvar cider-log--exceptions-filter nil)
+(defvar cider-log--level-filter nil)
+(defvar cider-log--loggers-filter nil)
+(defvar cider-log--pattern-filter nil)
+(defvar cider-log--start-time-filter nil)
+(defvar cider-log--threads-filter nil)
+
+(defun cider-log--bold (s)
+  "Return S with a bold face."
+  (when s (propertize (format "%s" s) 'face 'bold)))
+
+(defun cider-log-buffer-clear-p (&optional buffer)
+  "Return non-nil if BUFFER is not empty, otherwise nil."
+  (when-let (buffer (get-buffer (or buffer cider-log-buffer)))
+    (> (buffer-size buffer) 0)))
+
+(defun cider-log--description-clear-events-buffer ()
+  "Return the description for the set framework action."
+  (format "Clear %s buffer"
+          (if cider-log-buffer
+              (cider-log--format-value cider-log-buffer)
+            (propertize "n/a" 'face 'font-lock-comment-face))))
+
+(defun cider-log--description-set-framework ()
+  "Return the description for the set framework action."
+  (format "Select framework %s"
+          (if cider-log-framework-name
+              (cider-log--format-value cider-log-framework-name)
+            (propertize "n/a" 'face 'font-lock-comment-face))))
+
+(defun cider-log--description-set-buffer ()
+  "Return the description for the set buffer action."
+  (format "Select buffer %s"
+          (if cider-log-buffer
+              (cider-log--format-value cider-log-buffer)
+            (propertize "n/a" 'face 'font-lock-comment-face))))
+
+(defun cider-log--buffers-in-major-mode (expected)
+  "Return all buffers which are in the EXPECTED major mode."
+  (seq-filter (lambda (buffer)
+                (with-current-buffer buffer
+                  (equal expected major-mode)))
+              (buffer-list)))
+
+(defun cider-log--format-time (time)
+  "Format TIME in ISO8601 format."
+  (format-time-string "%FT%T%z" time))
+
+(defun cider-log--format-value (value)
+  "Format the VALUE for display in a transient menu."
+  (cond ((null value) "")
+        ((or (listp value) (vectorp value))
+         (string-join (seq-map #'cider-log--format-value value)
+                      (propertize ", " 'face 'font-lock-comment-face)))
+        (t (propertize (format "%s" value) 'face 'transient-value))))
+
+(defun cider-log--strip-whitespace (s)
+  "Replace multiple white space characters in S with a single one."
+  (replace-regexp-in-string "[\n ]+" " " s))
+
+;; NREPL
+
+(defun cider-request:log-add-consumer (framework appender consumer &optional 
callback)
+  "Add CONSUMER to the APPENDER of FRAMEWORK and call CALLBACK on log events."
+  (cider-ensure-op-supported "cider/log-add-consumer")
+  (thread-first `("op" "cider/log-add-consumer"
+                  "framework" ,(cider-log-framework-id framework)
+                  "appender" ,(cider-log-appender-id appender)
+                  "filters" ,(cider-log-consumer-filters consumer))
+                (cider-nrepl-send-request callback)))
+
+(defun cider-request:log-analyze-stacktrace (framework appender event 
&optional callback)
+  "Analyze the EVENT stacktrace of the APPENDER of FRAMEWORK and call 
CALLBACK."
+  (cider-ensure-op-supported "cider/log-analyze-stacktrace")
+  (thread-first `("op" "cider/log-analyze-stacktrace"
+                  "framework" ,(cider-log-framework-id framework)
+                  "appender" ,(cider-log-appender-id appender)
+                  "event" ,(cider-log-event-id event))
+                (cider-nrepl-send-request callback)))
+
+(defun cider-sync-request:log-update-consumer (framework appender consumer)
+  "Add CONSUMER to the APPENDER of FRAMEWORK and call CALLBACK on log events."
+  (cider-ensure-op-supported "cider/log-update-consumer")
+  (thread-first `("op" "cider/log-update-consumer"
+                  "framework" ,(cider-log-framework-id framework)
+                  "appender" ,(cider-log-appender-id appender)
+                  "consumer" ,(cider-log-consumer-id consumer)
+                  "filters" ,(cider-log-consumer-filters consumer))
+                (cider-nrepl-send-sync-request)
+                (nrepl-dict-get "cider/log-update-consumer")))
+
+(defun cider-sync-request:log-add-appender (framework appender)
+  "Add the APPENDER to the log FRAMEWORK."
+  (cider-ensure-op-supported "cider/log-add-appender")
+  (thread-first `("op" "cider/log-add-appender"
+                  "framework" ,(cider-log-framework-id framework)
+                  "appender" ,(cider-log-appender-id appender)
+                  "filters" ,(cider-log-appender-filters appender)
+                  "size" ,(cider-log-appender-size appender)
+                  "threshold" ,(cider-log-appender-threshold appender))
+                (cider-nrepl-send-sync-request)
+                (nrepl-dict-get "cider/log-add-appender")))
+
+(defun cider-sync-request:log-update-appender (framework appender)
+  "Update the APPENDER of the log FRAMEWORK."
+  (cider-ensure-op-supported "cider/log-update-appender")
+  (thread-first `("op" "cider/log-update-appender"
+                  "framework" ,(cider-log-framework-id framework)
+                  "appender" ,(cider-log-appender-id appender)
+                  "filters" ,(cider-log-appender-filters appender)
+                  "size" ,(cider-log-appender-size appender)
+                  "threshold" ,(cider-log-appender-threshold appender))
+                (cider-nrepl-send-sync-request)
+                (nrepl-dict-get "cider/log-update-appender")))
+
+(defun cider-sync-request:log-clear (framework appender)
+  "Clear the log events for FRAMEWORK and APPENDER."
+  (cider-ensure-op-supported "cider/log-clear-appender")
+  (thread-first `("op" "cider/log-clear-appender"
+                  "framework" ,(cider-log-framework-id framework)
+                  "appender" ,(cider-log-appender-id appender))
+                (cider-nrepl-send-sync-request)
+                (nrepl-dict-get "cider/log-clear-appender")))
+
+(defun cider-sync-request:log-inspect-event (framework appender event)
+  "Inspect the log event with the ID in the APPENDER of the log FRAMEWORK."
+  (cider-ensure-op-supported "cider/log-inspect-event")
+  (thread-first `("op" "cider/log-inspect-event"
+                  "framework" ,(cider-log-framework-id framework)
+                  "appender" ,(cider-log-appender-id appender)
+                  "event" ,(cider-log-event-id event))
+                (cider-nrepl-send-sync-request)
+                (nrepl-dict-get "value")))
+
+(defun cider-sync-request:log-format-event (framework appender event)
+  "Format the log EVENT from the APPENDER of the log FRAMEWORK."
+  (cider-ensure-op-supported "cider/log-format-event")
+  (thread-first
+    (seq-mapcat #'identity
+                (map-merge 'list
+                           (cider--nrepl-print-request-map fill-column)
+                           `(("op" "cider/log-format-event")
+                             ("framework" ,(cider-log-framework-id framework))
+                             ("appender" ,(cider-log-appender-id appender))
+                             ("event" ,(cider-log-event-id event)))))
+    (cider-nrepl-send-sync-request)
+    (nrepl-dict-get "cider/log-format-event")))
+
+(defun cider-sync-request:log-frameworks ()
+  "Return the available log frameworks."
+  (cider-ensure-op-supported "cider/log-frameworks")
+  (thread-first `("op" "cider/log-frameworks")
+                (cider-nrepl-send-sync-request)
+                (nrepl-dict-get "cider/log-frameworks")))
+
+(cl-defun cider-sync-request:log-search (framework appender &key filters limit 
offset)
+  "Search log events of FRAMEWORK and APPENDER using FILTERS, LIMIT and 
OFFSET."
+  (cider-ensure-op-supported "cider/log-search")
+  (thread-first `("op" "cider/log-search"
+                  "framework" ,(cider-log-framework-id framework)
+                  "appender" ,(cider-log-appender-id appender)
+                  "filters" ,filters
+                  "limit" ,limit
+                  "offset" ,offset)
+                (cider-nrepl-send-sync-request)
+                (nrepl-dict-get "cider/log-search")))
+
+(defun cider-sync-request:log-exceptions (framework appender)
+  "Return the Cider log exceptions for FRAMEWORK and APPENDER."
+  (cider-ensure-op-supported "cider/log-exceptions")
+  (thread-first `("op" "cider/log-exceptions"
+                  "framework" ,(cider-log-framework-id framework)
+                  "appender" ,(cider-log-appender-id appender))
+                (cider-nrepl-send-sync-request)
+                (nrepl-dict-get "cider/log-exceptions")))
+
+(defun cider-sync-request:log-levels (framework appender)
+  "Return the Cider log levels for FRAMEWORK and APPENDER."
+  (cider-ensure-op-supported "cider/log-levels")
+  (thread-first `("op" "cider/log-levels"
+                  "framework" ,(cider-log-framework-id framework)
+                  "appender" ,(cider-log-appender-id appender))
+                (cider-nrepl-send-sync-request)
+                (nrepl-dict-get "cider/log-levels")))
+
+(defun cider-sync-request:log-loggers (framework appender)
+  "Return the Cider loggers for FRAMEWORK and APPENDER."
+  (cider-ensure-op-supported "cider/log-loggers")
+  (thread-first `("op" "cider/log-loggers"
+                  "framework" ,(cider-log-framework-id framework)
+                  "appender" ,(cider-log-appender-id appender))
+                (cider-nrepl-send-sync-request)
+                (nrepl-dict-get "cider/log-loggers")))
+
+(defun cider-sync-request:log-remove-appender (framework appender)
+  "Remove the APPENDER from the log FRAMEWORK."
+  (cider-ensure-op-supported "cider/log-remove-appender")
+  (thread-first `("op" "cider/log-remove-appender"
+                  "framework" ,(cider-log-framework-id framework)
+                  "appender" ,(cider-log-appender-id appender))
+                (cider-nrepl-send-sync-request)
+                (nrepl-dict-get "cider/log-remove-appender")))
+
+(defun cider-sync-request:log-remove-consumer (framework appender consumer)
+  "Remove the CONSUMER from the APPENDER of the log FRAMEWORK."
+  (cider-ensure-op-supported "cider/log-remove-consumer")
+  (thread-first `("op" "cider/log-remove-consumer"
+                  "framework" ,(cider-log-framework-id framework)
+                  "appender" ,(cider-log-appender-id appender)
+                  "consumer" ,(cider-log-consumer-id consumer))
+                (cider-nrepl-send-sync-request)
+                (nrepl-dict-get "cider/log-remove-consumer")))
+
+(defun cider-sync-request:log-threads (framework appender)
+  "Return the threads for FRAMEWORK and APPENDER."
+  (cider-ensure-op-supported "cider/log-threads")
+  (thread-first `("op" "cider/log-threads"
+                  "framework" ,(cider-log-framework-id framework)
+                  "appender" ,(cider-log-appender-id appender))
+                (cider-nrepl-send-sync-request)
+                (nrepl-dict-get "cider/log-threads")))
+
+(defun cider-log--completion-extra-properties (keys &optional separator)
+  "Return the completion properties for NREPL dictionaries.
+
+The KEYS are used to lookup the values and are joined by SEPARATOR."
+  `(:annotation-function
+    ,(lambda (identifier)
+       (when-let (dict (cadr (assoc identifier minibuffer-completion-table)))
+         (let ((annotation (string-join (seq-map (lambda (key) (nrepl-dict-get 
dict key)) keys)
+                                        (or separator " "))))
+           (unless (string-blank-p annotation)
+             (propertize (format " - %s" (cider-log--strip-whitespace 
annotation))
+                         'face 'font-lock-comment-face)))))))
+
+(defun cider-log--read-appender-id (prompt initial-input history)
+  "Read a appender from the minibuffer using PROMPT, INITIAL-INPUT and 
HISTORY."
+  (let ((table (when cider-log-framework
+                 (when-let (framework (cider-log-framework-reload 
cider-log-framework))
+                   (seq-map #'cider-log-appender-id 
(cider-log-framework-appenders framework))))))
+    (completing-read (or prompt "Log appender: ") table nil nil
+                     (or initial-input cider-log-appender-id)
+                     history cider-log-appender-id)))
+
+(defun cider-log--read-buffer (&optional prompt initial-input history)
+  "Read the log buffer name using PROMPT, INITIAL-INPUT and HISTORY."
+  (let ((table (seq-map #'buffer-name (cider-log--buffers-in-major-mode 
'cider-log-mode))))
+    (completing-read (or prompt "Buffer: ") table nil nil
+                     (or initial-input cider-log-buffer)
+                     history cider-log-buffer)))
+
+(defun cider-log--read-exceptions (&optional prompt initial-input history)
+  "Read a list of exceptions using PROMPT, INITIAL-INPUT and HISTORY."
+  (let ((table (when (cider-log-appender-attached-p)
+                 (nrepl-dict-keys (cider-sync-request:log-exceptions
+                                   cider-log-framework cider-log-appender)))))
+    (completing-read-multiple (or prompt "Exceptions: ") table nil nil 
initial-input history)))
+
+(defun cider-log--read-framework-name (&optional prompt initial-input history)
+  "Read a framework name using PROMPT, INITIAL-INPUT and HISTORY."
+  (let ((completion-extra-properties (cider-log--completion-extra-properties 
'("name")))
+        (frameworks (cider-sync-request:log-frameworks)))
+    (completing-read (or prompt "Log framework: ")
+                     (seq-map (lambda (framework)
+                                (list (cider-log-framework-name framework) 
framework))
+                              frameworks)
+                     nil nil initial-input history)))
+
+(defun cider-log--read-level (&optional prompt initial-input history)
+  "Read a log level using PROMPT, INITIAL-INPUT and HISTORY."
+  (let ((table (when cider-log-framework (cider-log-framework-level-names 
cider-log-framework))))
+    (completing-read (or prompt "Level: ") table nil nil initial-input 
history)))
+
+(defun cider-log--read-loggers (&optional prompt initial-input history)
+  "Read a list of loggers using PROMPT, INITIAL-INPUT and HISTORY."
+  (let ((table (when (cider-log-appender-attached-p)
+                 (nrepl-dict-keys (cider-sync-request:log-loggers
+                                   cider-log-framework cider-log-appender)))))
+    (completing-read-multiple (or "Loggers: " prompt) table nil nil 
initial-input history)))
+
+(defun cider-log--read-number-N0 (&optional prompt initial-input history)
+  "Read a natural number (including zero) using PROMPT, INITIAL-INPUT and 
HISTORY."
+  (when-let (value (transient-read-number-N0 (or prompt "Number: ") 
initial-input history))
+    (string-to-number value)))
+
+(defun cider-log--read-number-N+ (&optional prompt initial-input history)
+  "Read a natural number (excluding zero) using PROMPT, INITIAL-INPUT and 
HISTORY."
+  (when-let (value (transient-read-number-N+ (or prompt "Number: ") 
initial-input history))
+    (string-to-number value)))
+
+(defun cider-log--read-threads (&optional prompt initial-input history)
+  "Read a list of threads using PROMPT, INITIAL-INPUT and HISTORY."
+  (let ((table (when (cider-log-appender-attached-p)
+                 (nrepl-dict-keys (cider-sync-request:log-threads
+                                   cider-log-framework cider-log-appender)))))
+    (completing-read-multiple (or prompt "Threads: ") table nil nil 
initial-input history)))
+
+(defun cider-log--read-time (&optional prompt initial-input _)
+  "Read a time from the minibuffer using PROMPT and INITIAL-INPUT."
+  (cider-log--format-time (org-read-date t 'to-time nil prompt nil 
initial-input)))
+
+;; Log Framework
+
+(defun cider-log-framework-appender (framework id)
+  "Return the appender of the log FRAMEWORK with the given ID."
+  (seq-find (lambda (appender) (equal id (cider-log-appender-id appender)))
+            (cider-log-framework-appenders framework)))
+
+(defun cider-log-framework-appenders (framework)
+  "Return the appenders of the log FRAMEWORK."
+  (nrepl-dict-get framework "appenders"))
+
+(defun cider-log-framework-id (framework)
+  "Return the id of the log FRAMEWORK."
+  (nrepl-dict-get framework "id"))
+
+(defun cider-log-framework-javadoc-url (framework)
+  "Return the Javadoc URL of the log FRAMEWORK."
+  (nrepl-dict-get framework "javadoc-url"))
+
+(defun cider-log-framework-name (framework)
+  "Return the name of the log FRAMEWORK."
+  (nrepl-dict-get framework "name"))
+
+(defun cider-log-framework-level-names (framework)
+  "Return the log level names of the log FRAMEWORK."
+  (seq-map (lambda (level) (nrepl-dict-get level "name"))
+           (nrepl-dict-get framework "levels")))
+
+(defun cider-log-framework-website-url (framework)
+  "Return the website URL of the log FRAMEWORK."
+  (nrepl-dict-get framework "website-url"))
+
+(defun cider-log-framework-display-name (framework)
+  "Return the display name of the log FRAMEWORK."
+  (cider-log--bold (cider-log-framework-name framework)))
+
+(defun cider-log-framework-add-appender (framework appender)
+  "Add the APPENDER to the log FRAMEWORK."
+  (cider-sync-request:log-add-appender framework appender))
+
+(defun cider-log-framework-by-id (frameworks id)
+  "Find the log framework in FRAMEWORKS by ID."
+  (seq-find (lambda (framework) (equal id (cider-log-framework-id framework))) 
frameworks))
+
+(defun cider-log-framework-by-name (frameworks name)
+  "Find the log framework in FRAMEWORKS by NAME."
+  (seq-find (lambda (framework) (equal name (cider-log-framework-name 
framework))) frameworks))
+
+(defun cider-log-framework-reload (framework)
+  "Reload the log FRAMEWORK."
+  (cider-log-framework-by-id
+   (cider-sync-request:log-frameworks)
+   (cider-log-framework-id framework)))
+
+;; Log Appender
+
+(defun cider-log-appender-attached-p (&optional framework appender)
+  "Return non-nil if the log APPENDER is attached to FRAMEWORK, otherwise nil."
+  (when-let ((framework (or framework
+                            (cider-log-framework-by-name
+                             (cider-sync-request:log-frameworks)
+                             cider-log-framework-name)))
+             (appender-id (if appender
+                              (cider-log-appender-id appender)
+                            cider-log-appender-id)))
+    (cider-log-framework-appender framework appender-id)))
+
+(defun cider-log-appender-consumers (appender)
+  "Return the consumers of the log APPENDER."
+  (nrepl-dict-get appender "consumers"))
+
+(defun cider-log-appender-id (appender)
+  "Return the id of the log APPENDER."
+  (nrepl-dict-get appender "id"))
+
+(defun cider-log-appender-size (appender)
+  "Return the size of the log APPENDER."
+  (nrepl-dict-get appender "size"))
+
+(defun cider-log-appender-threshold (appender)
+  "Return the threshold of the log APPENDER."
+  (nrepl-dict-get appender "threshold"))
+
+(defun cider-log-appender-filters (appender)
+  "Return the filters of the log APPENDER."
+  (nrepl-dict-get appender "filters"))
+
+(defun cider-log-appender-display-name (appender)
+  "Return the display name of the log APPENDER."
+  (cider-log--bold (cider-log-appender-id appender)))
+
+(defun cider-log-appender-consumer (appender consumer)
+  "Find the consumer in the log APPENDER by the id slot of CONSUMER."
+  (let ((id (cider-log-consumer-id consumer)))
+    (seq-find (lambda (consumer) (equal id (cider-log-consumer-id consumer)))
+              (cider-log-appender-consumers appender))))
+
+(defun cider-log-appender-reload (framework appender)
+  "Reload the APPENDER of the log FRAMEWORK."
+  (when-let (framework (cider-log-framework-reload framework))
+    (cider-log-framework-appender framework (cider-log-appender-id appender))))
+
+;; Log Consumer
+
+(defun cider-log-consumer-attached-p (&optional framework appender consumer)
+  "Return non-nil if the CONSUMER is attached to the APPENDER of FRAMEWORK."
+  (when-let ((framework (or framework cider-log-framework))
+             (appender (or appender cider-log-appender))
+             (consumer (or consumer cider-log-consumer)))
+    (cider-log-consumer-reload framework appender consumer)))
+
+(defun cider-log-consumer-id (consumer)
+  "Return the id of the log CONSUMER."
+  (nrepl-dict-get consumer "id"))
+
+(defun cider-log-consumer-filters (consumer)
+  "Return the filters of the log CONSUMER."
+  (nrepl-dict-get consumer "filters"))
+
+(defun cider-log-consumer-buffers (consumer)
+  "Find all buffers in which `cider-log-consumer' is bound to CONSUMER."
+  (seq-filter (lambda (buffer)
+                (with-current-buffer buffer
+                  (and (nrepl-dict-p cider-log-consumer)
+                       (equal (cider-log-consumer-id consumer)
+                              (cider-log-consumer-id cider-log-consumer)))))
+              (buffer-list)))
+
+(defun cider-log-consumer-display-name (consumer)
+  "Return the display name of the log CONSUMER."
+  (cider-log--bold (cider-log-consumer-id consumer)))
+
+(defun cider-log-consumer-reload (framework appender consumer)
+  "Reload the CONSUMER attached to APPENDER of the log FRAMEWORK."
+  (when-let (appender (cider-log-appender-reload framework appender))
+    (cider-log-appender-consumer appender consumer)))
+
+(defun cider-log--consumer-add (framework appender consumer buffer)
+  "Add the CONSUMER to the APPENDER of FRAMEWORK and write events to BUFFER."
+  (cider-request:log-add-consumer
+   framework appender consumer
+   (lambda (msg)
+     (nrepl-dbind-response msg (cider/log-add-consumer cider/log-consumer 
cider/log-event status)
+       (cond ((member "done" status)
+              (with-current-buffer (get-buffer-create buffer)
+                (setq-local cider-log-framework framework)
+                (setq-local cider-log-appender appender)
+                (setq cider-log-consumer cider/log-add-consumer)
+                (switch-to-buffer buffer)))
+             ((member "cider/log-event" status)
+              (let* ((consumer (nrepl-dict "id" cider/log-consumer))
+                     (buffers (cider-log-consumer-buffers consumer)))
+                (when (seq-empty-p buffers)
+                  (message "WARNING: No buffers found for %s log consumer %s 
of appender %s."
+                           (cider-log-framework-display-name framework)
+                           (cider-log-consumer-display-name consumer)
+                           (cider-log-appender-display-name appender))
+                  (cider-sync-request:log-remove-consumer framework appender 
consumer))
+                (seq-doseq (buffer buffers)
+                  (with-current-buffer buffer
+                    (cider-log--insert-events buffer (list cider/log-event))
+                    (when (not (logview-initialized-p))
+                      (let ((framework cider-log-framework)
+                            (appender cider-log-appender)
+                            (consumer cider-log-consumer))
+                        (logview--guess-submode)
+                        (cider-log-mode)
+                        ;; Restore buffer local vars reset by calling major 
mode.
+                        (setq-local cider-log-framework framework
+                                    cider-log-appender appender
+                                    cider-log-consumer consumer))))))))))))
+
+(defun cider-log--remove-current-buffer-consumer ()
+  "Cleanup the log consumer of the current buffer."
+  (when-let ((framework cider-log-framework)
+             (appender cider-log-appender)
+             (consumer cider-log-consumer))
+    (setq-local cider-log-consumer nil)
+    (when-let (consumer (cider-log-consumer-reload framework appender 
consumer))
+      (cider-sync-request:log-remove-consumer framework appender consumer)
+      consumer)))
+
+;; Event
+
+(defun cider-log-event-id (event)
+  "Return the id of the log EVENT."
+  (nrepl-dict-get event "id"))
+
+(defun cider-log-event-exception (event)
+  "Return the exception of the log EVENT."
+  (nrepl-dict-get event "exception"))
+
+(defun cider-log-event--format-logback (event)
+  "Format the log EVENT in logview's Logback format."
+  (nrepl-dbind-response event (_exception level logger message thread 
timestamp)
+    (propertize (format "%s [%s] %s %s - %s\n"
+                        (if (numberp timestamp)
+                            (format-time-string "%F %T.%3N" (/ timestamp 1000))
+                          (format "%s" timestamp))
+                        thread
+                        (upcase level)
+                        logger
+                        (if (and (stringp message)
+                                 (numberp cider-log-max-message-length))
+                            (substring message 0 (min (length message) 
cider-log-max-message-length))
+                          ""))
+                :cider-log-event event)))
+
+(defun cider-log-event--pretty-print (framework appender event)
+  "Format the log EVENT of FRAMEWORK and APPENDER."
+  (when-let (event (cider-sync-request:log-format-event framework appender 
event))
+    (cider-popup-buffer cider-log-event-buffer cider-auto-select-error-buffer
+                        'clojure-mode 'ancillary)
+    (with-current-buffer cider-log-event-buffer
+      (let ((inhibit-read-only t))
+        (erase-buffer)
+        (insert event)
+        (goto-char (point-min))))))
+
+(defun cider-log-event--inspect (framework appender event)
+  "Inspect the log EVENT of FRAMEWORK and APPENDER."
+  (thread-last (cider-sync-request:log-inspect-event framework appender event)
+               (cider-inspector--render-value)))
+
+(defun cider-log--insert-events (buffer events)
+  "Insert the log EVENTS into BUFFER."
+  (with-current-buffer (get-buffer-create buffer)
+    (let ((windows (seq-filter (lambda (window) (= (window-point window) 
(point-max)))
+                               (get-buffer-window-list buffer))))
+      (save-excursion
+        (let ((inhibit-read-only t))
+          (goto-char (point-max))
+          (seq-doseq (event events)
+            (insert (cider-log-event--format-logback event)))))
+      (seq-doseq (window windows)
+        (set-window-point window (point-max))))))
+
+(defun cider-log-event--show-stacktrace (framework appender event)
+  "Show the stacktrace of the log EVENT of FRAMEWORK and APPENDER."
+  (when (and framework appender event (cider-log-event-exception event))
+    (let ((auto-select-buffer cider-auto-select-error-buffer)
+          (causes nil))
+      (cider-request:log-analyze-stacktrace
+       framework appender event
+       (lambda (response)
+         (nrepl-dbind-response response (class status)
+           (cond (class  (setq causes (cons response causes)))
+                 (status (when causes
+                           (cider-stacktrace-render
+                            (cider-popup-buffer cider-error-buffer
+                                                auto-select-buffer
+                                                #'cider-stacktrace-mode
+                                                'ancillary)
+                            (reverse causes)))))))))))
+
+(defun cider-log-event-next-line (&optional n)
+  "Move N lines forward."
+  (interactive "p")
+  (let ((n (or n 1)))
+    (forward-line n)
+    (beginning-of-line)
+    (when-let ((framework cider-log-framework)
+               (appender cider-log-appender)
+               (event (cider-log-event-at-point)))
+      (let ((cider-auto-select-error-buffer nil))
+        (save-window-excursion
+          (when (get-buffer-window cider-error-buffer)
+            (cider-log-event--show-stacktrace framework appender event))
+          (when (get-buffer-window cider-inspector-buffer)
+            (cider-log-event--inspect framework appender event))
+          (when (get-buffer-window cider-log-event-buffer)
+            (cider-log-event--pretty-print framework appender event)))))))
+
+(defun cider-log-event-previous-line (&optional n)
+  "Move N lines backward."
+  (interactive "p")
+  (cider-log-event-next-line (- (or n 1))))
+
+(defun cider-log--set-filters (filters)
+  "Set the filter variables from the NREPL dict FILTERS."
+  (when filters
+    (setq cider-log--end-time-filter (nrepl-dict-get filters "end-time")
+          cider-log--exceptions-filter (nrepl-dict-get filters "exceptions")
+          cider-log--level-filter (nrepl-dict-get filters "level")
+          cider-log--loggers-filter (nrepl-dict-get filters "loggers")
+          cider-log--pattern-filter (nrepl-dict-get filters "pattern")
+          cider-log--start-time-filter (nrepl-dict-get filters "start-time")
+          cider-log--threads-filter (nrepl-dict-get filters "threads"))))
+
+(defun cider-log--ensure-initialized (framework &optional appender consumer)
+  "Ensure that the given FRAMEWORK, APPENDER and CONSUMER are initialized."
+  (setq cider-log-framework framework
+        cider-log-framework-name (cider-log-framework-name framework))
+  (when appender
+    (setq cider-log-appender appender
+          cider-log-appender-id (cider-log-appender-id appender)
+          cider-log-appender-size (cider-log-appender-size appender)
+          cider-log-appender-threshold (cider-log-appender-threshold appender))
+    (cider-log--set-filters (cider-log-appender-filters appender)))
+  (when consumer
+    (setq cider-log-consumer consumer)
+    (cider-log--set-filters (cider-log-consumer-filters appender)))
+  (when (and appender (not cider-log--initialized-once-p))
+    (unless (cider-log-appender-reload framework appender)
+      (setq cider-log-appender (cider-sync-request:log-add-appender framework 
appender))
+      (setq cider-log--initialized-once-p t))))
+
+(defun cider-log-kill-buffer-hook-handler ()
+  "Called from `kill-buffer-hook' to remove the consumer."
+  (when (eq 'cider-log-mode major-mode)
+    (when-let ((framework cider-log-framework)
+               (appender cider-log-appender)
+               (consumer cider-log-consumer))
+      (cider-log--remove-current-buffer-consumer)
+      (message "Removed %s event consumer %s from appender %s."
+               (cider-log-framework-display-name framework)
+               (cider-log-consumer-display-name consumer)
+               (cider-log-appender-display-name appender)))))
+
+(defun cider-log-select-framework ()
+  "Select the log framework."
+  (let ((frameworks (cider-sync-request:log-frameworks)))
+    (cond ((= 1 (length frameworks))
+           (car frameworks))
+          (t (let ((name (cider-log--read-framework-name)))
+               (cider-log-framework-by-name frameworks name))))))
+
+(defun cider-log--current-framework ()
+  "Return the log framework by the name bound to `cider-log-framework-name'."
+  (when cider-log-framework-name
+    (let ((frameworks (cider-sync-request:log-frameworks)))
+      (cider-log-framework-by-name frameworks cider-log-framework-name))))
+
+(defun cider-log--framework ()
+  "Return the current log framework, or select it."
+  (or (cider-log--current-framework) (cider-log-select-framework)))
+
+(defun cider-log--appender ()
+  "Return the current log appender."
+  (when cider-log-appender-id
+    (nrepl-dict "id" cider-log-appender-id
+                "filters" (cider-log--filters)
+                "size" cider-log-appender-size
+                "threshold" cider-log-appender-threshold)))
+
+(defun cider-log--consumer ()
+  "Return the current log consumer."
+  (let ((consumer (nrepl-dict "filters" (cider-log--filters))))
+    (when cider-log-consumer
+      (nrepl-dict-put consumer "id" (cider-log-consumer-id 
cider-log-consumer)))
+    consumer))
+
+(defun cider-log--event-options ()
+  "Return the current log consumer."
+  (nrepl-dict "filters" (cider-log--filters)
+              "limit" cider-log-pagination-limit
+              "offset" cider-log-pagination-offset))
+
+(defun cider-log--filters ()
+  "Return the log event filters."
+  (nrepl-dict
+   "end-time" cider-log--end-time-filter
+   "exceptions" cider-log--exceptions-filter
+   "level" cider-log--level-filter
+   "loggers" cider-log--loggers-filter
+   "pattern" cider-log--pattern-filter
+   "start-time" cider-log--start-time-filter
+   "threads" cider-log--threads-filter))
+
+(defun cider-log-event-at-point ()
+  "Return the log event at point."
+  (get-text-property (point) :cider-log-event))
+
+(defun cider-log-info ()
+  "Show the current log buffer, framework, appender and consumer."
+  (interactive)
+  (message "%s"
+           (string-join
+            (list (when cider-log-buffer
+                    (format "Buffer: %s" (cider-log--bold cider-log-buffer)))
+                  (when cider-log-framework-name
+                    (format "Framework: %s" (cider-log--bold 
cider-log-framework-name)))
+                  (when cider-log-appender-id
+                    (format "Appender: %s" (cider-log--bold 
cider-log-appender-id)))
+                  (when-let (id (and cider-log-consumer (cider-log-consumer-id 
cider-log-consumer)))
+                    (format "Consumer: %s" (cider-log--bold id))))
+            " ")))
+
+;; Major mode
+
+(defvar cider-log-mode-map
+  (let ((map (make-sparse-keymap)))
+    (set-keymap-parent map logview-mode-map)
+    (define-key map (kbd "C-c l a") #'cider-log-appender)
+    (define-key map (kbd "C-c l c") #'cider-log-consumer)
+    (define-key map (kbd "C-c l e") #'cider-log-event)
+    (define-key map (kbd "C-c l f") #'cider-log-framework)
+    (define-key map (kbd "C-c l i") #'cider-log-info)
+    (define-key map (kbd "C-c l l") #'cider-log)
+    (define-key map (kbd "E") 'cider-log-show-stacktrace)
+    (define-key map (kbd "F") 'cider-log-print-event)
+    (define-key map (kbd "I") 'cider-log-inspect-event)
+    (define-key map (kbd "RET") 'cider-log-inspect-event)
+    (define-key map (kbd "n") 'cider-log-event-next-line)
+    (define-key map (kbd "p") 'cider-log-event-previous-line)
+    map)
+  "The Cider log stream mode key map.")
+
+(define-derived-mode cider-log-mode logview-mode "Cider Log"
+  "Major mode for inspecting Clojure log events.
+
+CIDER Log Mode allows you to capture, debug, inspect and view log events
+emitted by Java logging frameworks.  The captured log events can be
+searched, streamed to the client, pretty printed and are integrated with
+the CIDER Inspector and the CIDER stacktrace mode.
+
+\\{cider-log-mode-map}"
+  (use-local-map cider-log-mode-map)
+  (setq-local electric-indent-chars nil)
+  (setq-local logview-show-ellipses nil)
+  (setq-local sesman-system 'CIDER)
+  (setq-local truncate-lines t)
+  (when (fboundp 'evil-set-initial-state)
+    (evil-set-initial-state 'cider-log-mode 'emacs)))
+
+;; Transient Lisp Variable
+
+(defclass cider-log--lisp-variable (transient-lisp-variable) ())
+
+(cl-defmethod transient-init-value ((obj cider-log--lisp-variable))
+  "Set the initial value of the object OBJ."
+  (let* ((prefix-value (oref transient--prefix value))
+         (value (cdr (assoc (oref obj variable) prefix-value)))
+         (new-value (if (assoc (oref obj variable) prefix-value)
+                        value
+                      (symbol-value (oref obj variable)))))
+    (funcall (oref obj set-value)
+             (oref obj variable)
+             (oset obj value new-value))))
+
+(cl-defmethod transient-infix-set ((obj cider-log--lisp-variable) value)
+  "Set the value of infix object OBJ to VALUE."
+  (funcall (oref obj set-value)
+           (oref obj variable)
+           (oset obj value value)))
+
+(cl-defmethod transient-infix-value ((obj cider-log--lisp-variable))
+  "Return the value of the suffix object OBJ."
+  (cons (oref obj variable) (oref obj value)))
+
+(cl-defmethod transient-format-value ((obj cider-log--lisp-variable))
+  "Format OBJ's value for display and return the result."
+  (propertize (prin1-to-string (oref obj value))
+              'face 'transient-value))
+
+(cl-defmethod transient-format-value ((obj cider-log--lisp-variable))
+  "Format OBJ's value for display and return the result."
+  (cider-log--format-value (oref obj value)))
+
+;; Transient options
+
+(transient-define-infix cider-log--appender-size-option ()
+  :always-read t
+  :argument "--size="
+  :class 'cider-log--lisp-variable
+  :description "Appender size"
+  :key "=s"
+  :prompt "Size: "
+  :reader #'cider-log--read-number-N+
+  :variable 'cider-log-appender-size)
+
+(transient-define-infix cider-log--appender-threshold-option ()
+  :always-read t
+  :argument "--threshold="
+  :class 'cider-log--lisp-variable
+  :description "Appender threshold"
+  :key "=t"
+  :prompt "Threshold: "
+  :reader #'cider-log--read-number-N+
+  :variable 'cider-log-appender-threshold)
+
+(transient-define-infix cider-log--buffer-option ()
+  :always-read t
+  :class 'cider-log--lisp-variable
+  :description "Buffer"
+  :key "=b"
+  :prompt "Log buffer: "
+  :reader #'cider-log--read-buffer
+  :variable 'cider-log-buffer)
+
+(transient-define-infix cider-log--end-time-option ()
+  :argument "--end-time="
+  :class 'cider-log--lisp-variable
+  :description "Filter by end time"
+  :key "-e"
+  :prompt "End time: "
+  :reader #'cider-log--read-time
+  :variable 'cider-log--end-time-filter)
+
+(transient-define-infix cider-log--exceptions-option ()
+  :argument "--exceptions="
+  :class 'cider-log--lisp-variable
+  :description "Filter by exceptions"
+  :key "-E"
+  :multi-value t
+  :prompt "Exceptions: "
+  :reader #'cider-log--read-exceptions
+  :variable 'cider-log--exceptions-filter)
+
+(transient-define-infix cider-log--level-option ()
+  :argument "--level="
+  :class 'cider-log--lisp-variable
+  :description "Filter by level"
+  :key "-l"
+  :prompt "Log Level: "
+  :reader #'cider-log--read-level
+  :variable 'cider-log--level-filter)
+
+(transient-define-infix cider-log--limit-option ()
+  :always-read t
+  :argument "--limit="
+  :class 'cider-log--lisp-variable
+  :description "Limit"
+  :key "=l"
+  :prompt "Limit: "
+  :reader #'cider-log--read-number-N+
+  :variable 'cider-log-pagination-limit)
+
+(transient-define-infix cider-log--logger-option ()
+  :argument "--loggers="
+  :class 'cider-log--lisp-variable
+  :description "Filter by loggers"
+  :key "-L"
+  :multi-value t
+  :prompt "Loggers: "
+  :reader #'cider-log--read-loggers
+  :variable 'cider-log--loggers-filter)
+
+(transient-define-infix cider-log--offset-option ()
+  :always-read t
+  :argument "--offset="
+  :class 'cider-log--lisp-variable
+  :description "Offset"
+  :key "=o"
+  :prompt "Offset: "
+  :reader #'cider-log--read-number-N0
+  :variable 'cider-log-pagination-offset)
+
+(transient-define-infix cider-log--pattern-option ()
+  :argument "--pattern="
+  :class 'cider-log--lisp-variable
+  :description "Filter by regex pattern"
+  :key "-r"
+  :prompt "Regex pattern: "
+  :reader #'read-string
+  :variable 'cider-log--pattern-filter)
+
+(transient-define-infix cider-log--start-time-option ()
+  :argument "--start-time="
+  :class 'cider-log--lisp-variable
+  :description "Filter by start time"
+  :key "-s"
+  :prompt "Start time: "
+  :reader #'cider-log--read-time
+  :variable 'cider-log--start-time-filter)
+
+(transient-define-infix cider-log--threads-option ()
+  :argument "--threads="
+  :class 'cider-log--lisp-variable
+  :description "Filter by threads"
+  :key "-t"
+  :multi-value t
+  :prompt "Threads: "
+  :reader #'cider-log--read-threads
+  :variable 'cider-log--threads-filter)
+
+;; Framework actions
+
+(transient-define-suffix cider-log-browse-javadocs (framework)
+  "Browse the Javadoc of the log FRAMEWORK."
+  :description "Browse Java documentation"
+  (interactive (list (cider-log--framework)))
+  (browse-url (or (cider-log-framework-javadoc-url framework)
+                  (user-error (format "The %s framework does not have 
Javadocs."
+                                      (cider-log-framework-name framework))))))
+
+(transient-define-suffix cider-log-browse-website (framework)
+  "Browse the website of the log FRAMEWORK."
+  :description "Browse website"
+  (interactive (list (cider-log--framework)))
+  (browse-url (or (cider-log-framework-website-url framework)
+                  (user-error (format "The %s framework does not have a 
website."
+                                      (cider-log-framework-name framework))))))
+
+(transient-define-suffix cider-log-set-framework (framework-name)
+  "Set the current log framework to FRAMEWORK-NAME."
+  :description #'cider-log--description-set-framework
+  (interactive (list (cider-log--read-framework-name)))
+  (setq cider-log-framework-name framework-name))
+
+(transient-define-suffix cider-log-set-buffer (buffer)
+  "Set the current log buffer to BUFFER."
+  :description #'cider-log--description-set-buffer
+  (interactive (list (cider-log--read-buffer)))
+  (setq cider-log-buffer buffer))
+
+;; Appender actions
+
+(transient-define-suffix cider-log-clear-appender (framework appender)
+  "Clear the log events of the APPENDER of FRAMEWORK."
+  :description "Clear log appender"
+  :inapt-if-not #'cider-log-appender-attached-p
+  (interactive (list (cider-log--framework) (cider-log--appender)))
+  (cider-sync-request:log-clear framework appender)
+  (message "Cleared the %s log appender of the %s framework."
+           (cider-log-appender-display-name appender)
+           (cider-log-framework-display-name framework)))
+
+(transient-define-suffix cider-log-kill-appender (framework appender)
+  "Remove the log APPENDER from FRAMEWORK."
+  :description "Kill log appender"
+  :inapt-if-not #'cider-log-appender-attached-p
+  (interactive (list (cider-log--framework) (cider-log--appender)))
+  (cider-sync-request:log-remove-appender framework appender)
+  (setq-local cider-log-consumer nil)
+  (message "Log appender %s removed from the %s framework."
+           (cider-log-framework-display-name framework)
+           (cider-log-appender-display-name appender)))
+
+(transient-define-suffix cider-log--do-add-appender (framework appender)
+  "Add the APPENDER to the log FRAMEWORK."
+  :description "Add log appender"
+  :inapt-if #'cider-log-appender-attached-p
+  (interactive (list (cider-log--framework) (cider-log--appender)))
+  (setq cider-log-appender (cider-sync-request:log-add-appender framework 
appender))
+  (message "Log appender %s added to the %s framework."
+           (cider-log-appender-display-name appender)
+           (cider-log-framework-display-name framework)))
+
+(transient-define-suffix cider-log--do-update-appender (framework appender)
+  "Update the APPENDER of the log FRAMEWORK."
+  :description "Update log appender"
+  :inapt-if-not #'cider-log-appender-attached-p
+  (interactive (list (cider-log--framework) (cider-log--appender)))
+  (setq cider-log-appender (cider-sync-request:log-update-appender framework 
appender))
+  (message "Updated log appender %s of the %s framework."
+           (cider-log-appender-display-name appender)
+           (cider-log-framework-display-name framework)))
+
+;; Consumer actions
+
+(transient-define-suffix cider-log--do-add-consumer (framework appender 
consumer buffer)
+  "Add the CONSUMER to the APPENDER of the log FRAMEWORK and write events to 
BUFFER."
+  :description "Add log consumer"
+  :inapt-if #'cider-log-consumer-attached-p
+  (interactive (list (cider-log--framework)
+                     (cider-log--appender)
+                     (cider-log--consumer)
+                     (current-buffer)))
+  (cider-log--consumer-add framework appender consumer buffer))
+
+(transient-define-suffix cider-log-kill-consumer (framework appender consumer)
+  "Remove the CONSUMER listening to the APPENDER of the log FRAMEWORK."
+  :description "Kill log consumer"
+  :inapt-if-not #'cider-log-consumer-attached-p
+  (interactive (list (cider-log--framework) (cider-log--appender) 
(cider-log--consumer)))
+  (cider-sync-request:log-remove-consumer framework appender consumer)
+  (setq-local cider-log-consumer nil)
+  (message "Removed %s log consumer %s for appender %s."
+           (cider-log-framework-display-name framework)
+           (cider-log-consumer-display-name consumer)
+           (cider-log-appender-display-name appender)))
+
+(transient-define-suffix cider-log--do-update-consumer (framework appender 
consumer)
+  "Update the CONSUMER listening to the APPENDER of the log FRAMEWORK."
+  :description "Update log consumer"
+  :inapt-if-not #'cider-log-consumer-attached-p
+  (interactive (list (cider-log--framework)
+                     (cider-log--appender)
+                     (cider-log--consumer)))
+  (setq cider-log-consumer (cider-sync-request:log-update-consumer framework 
appender consumer))
+  (message "Updated %s log consumer %s for appender %s."
+           (cider-log-framework-display-name framework)
+           (cider-log-consumer-display-name consumer)
+           (cider-log-appender-display-name appender)))
+
+;; Event actions
+
+(transient-define-suffix cider-log-show-stacktrace (framework appender event)
+  "Show the stacktrace of the log EVENT of FRAMEWORK and APPENDER."
+  :description "Show log event stacktrace"
+  :if #'cider-log-event-at-point
+  :inapt-if-not (lambda ()
+                  (when-let (event (cider-log-event-at-point))
+                    (cider-log-event-exception event)))
+  (interactive (list (cider-log--framework) (cider-log--appender) 
(cider-log-event-at-point)))
+  (cider-log-event--show-stacktrace framework appender event))
+
+(transient-define-suffix cider-log-print-event (framework appender event)
+  "Format the log EVENT of FRAMEWORK and APPENDER."
+  :description "Pretty print log event at point"
+  :if #'cider-log-event-at-point
+  (interactive (list (cider-log--framework) (cider-log--appender) 
(cider-log-event-at-point)))
+  (if event
+      (cider-log-event--pretty-print framework appender event)
+    (user-error "No log event found at point")))
+
+(transient-define-suffix cider-log-inspect-event (framework appender event)
+  "Inspect the log EVENT of FRAMEWORK and APPENDER."
+  :description "Inspect log event at point"
+  :if #'cider-log-event-at-point
+  (interactive (list (cider-log--framework) (cider-log--appender) 
(cider-log-event-at-point)))
+  (cider-log-event--inspect framework appender event))
+
+(transient-define-suffix cider-log-clear-event-buffer (buffer)
+  "Clear the Cider log events in BUFFER."
+  :description #'cider-log--description-clear-events-buffer
+  :inapt-if-not #'cider-log-buffer-clear-p
+  (interactive (list cider-log-buffer))
+  (when (get-buffer buffer)
+    (with-current-buffer buffer
+      (let ((inhibit-read-only t))
+        (erase-buffer)))))
+
+(transient-define-suffix cider-log-switch-to-buffer (buffer)
+  "Switch to the Cider log event BUFFER."
+  :description "Switch to the log event buffer"
+  (interactive (list cider-log-buffer))
+  (when (get-buffer-create buffer)
+    (switch-to-buffer-other-window buffer)))
+
+(transient-define-suffix cider-log--do-search-events (framework appender 
filters)
+  "Search the log events of FRAMEWORK and APPENDER which match FILTERS."
+  :description "Search log events"
+  :inapt-if-not #'cider-log-appender-attached-p
+  (interactive (list (cider-log--framework) (cider-log--appender) 
(cider-log--filters)))
+  (with-current-buffer (get-buffer-create cider-log-buffer)
+    (let ((consumer (nrepl-dict "filters" (cider-log--filters)))
+          (inhibit-read-only t))
+      (cider-log--remove-current-buffer-consumer)
+      (erase-buffer)
+      (let ((events (cider-sync-request:log-search
+                     framework appender
+                     :filters filters
+                     :limit cider-log-pagination-limit
+                     :offset cider-log-pagination-offset)))
+        (seq-doseq (event (nreverse events))
+          (insert (cider-log-event--format-logback event)))
+        (cider-log-mode)
+        (setq-local cider-log-framework framework)
+        (setq-local cider-log-appender appender)
+        (when (seq-empty-p events)
+          (message "No log events found."))
+        (cider-log--consumer-add framework appender consumer 
(current-buffer))))))
+
+;; Log Framework Transient
+
+;;;###autoload (autoload 'cider-log-framework "cider-log" "Show the Cider log 
framework menu." t)
+(transient-define-prefix cider-log-framework (framework)
+  "Show the Cider log framework menu."
+  [["Cider Log Framework\n\nActions:"
+    ("b" cider-log-set-buffer)
+    ("j" cider-log-browse-javadocs)
+    ("s" cider-log-set-framework)
+    ("w" cider-log-browse-website)]]
+  (interactive (list (cider-log--framework)))
+  (cider-log--ensure-initialized framework)
+  (transient-setup 'cider-log-framework))
+
+;; Log Appender Transients
+
+(defun cider-log--appender-interactive-list ()
+  "Return the interactive arguments for a appender transient."
+  (let ((framework (cider-log--current-framework)))
+    (list framework (cider-log-framework-appender framework 
cider-log-appender-id))))
+
+(transient-define-prefix cider-log-add-appender (framework appender)
+  "Show the menu to add a Cider log appender."
+  :history-key 'cider-log-appender
+  ["Cider Log Appender\n\nSettings:"
+   (cider-log--appender-size-option)
+   (cider-log--appender-threshold-option)]
+  ["Filters:"
+   (cider-log--end-time-option)
+   (cider-log--exceptions-option)
+   (cider-log--level-option)
+   (cider-log--logger-option)
+   (cider-log--pattern-option)
+   (cider-log--start-time-option)
+   (cider-log--threads-option)]
+  ["Actions"
+   ("a" cider-log--do-add-appender)]
+  (interactive (cider-log--appender-interactive-list))
+  (cider-log--ensure-initialized framework appender)
+  (transient-setup 'cider-log-add-appender))
+
+(transient-define-prefix cider-log-update-appender (framework appender)
+  "Show the menu to update a Cider log appender."
+  :history-key 'cider-log-appender
+  ["Cider Log Appender\n\nSettings:"
+   (cider-log--appender-size-option)
+   (cider-log--appender-threshold-option)]
+  ["Filters:"
+   (cider-log--end-time-option)
+   (cider-log--exceptions-option)
+   (cider-log--level-option)
+   (cider-log--logger-option)
+   (cider-log--pattern-option)
+   (cider-log--start-time-option)
+   (cider-log--threads-option)]
+  ["Actions"
+   ("u" cider-log--do-update-appender)]
+  (interactive (cider-log--appender-interactive-list))
+  (cider-log--ensure-initialized framework appender)
+  (transient-setup 'cider-log-update-appender))
+
+;;;###autoload (autoload 'cider-log-appender "cider-log" "Show the Cider log 
appender menu." t)
+(transient-define-prefix cider-log-appender (framework appender)
+  "Show the Cider log appender menu."
+  :history-key 'cider-log-appender
+  ["Cider Log Appender\n\nSettings:"
+   (cider-log--appender-size-option)
+   (cider-log--appender-threshold-option)]
+  ["Filters:"
+   (cider-log--end-time-option)
+   (cider-log--exceptions-option)
+   (cider-log--level-option)
+   (cider-log--logger-option)
+   (cider-log--pattern-option)
+   (cider-log--start-time-option)
+   (cider-log--threads-option)]
+  ["Actions"
+   ("a" cider-log--do-add-appender)
+   ("c" cider-log-clear-appender)
+   ("k" cider-log-kill-appender)
+   ("u" cider-log--do-update-appender)]
+  (interactive (cider-log--appender-interactive-list))
+  (cider-log--ensure-initialized framework appender)
+  (transient-setup 'cider-log-appender))
+
+;; Log Consumer Transient Menu
+
+(defun cider-log--consumer-interactive-list ()
+  "Return the interactive arguments for a consumer transient."
+  (let* ((framework (cider-log--current-framework))
+         (appender (cider-log-framework-appender framework 
cider-log-appender-id)))
+    (list framework appender
+          (if (and appender cider-log-consumer)
+              (seq-find (lambda (consumer)
+                          (equal (cider-log-consumer-id cider-log-consumer)
+                                 (cider-log-consumer-id consumer)))
+                        (cider-log-appender-consumers appender))
+            (nrepl-dict "filters" (cider-log--filters))))))
+
+(transient-define-prefix cider-log-add-consumer (framework appender consumer)
+  "Show the menu to add a Cider log consumer."
+  :history-key 'cider-log-consumer
+  ["Cider Log Consumer\n\nFilters:"
+   (cider-log--end-time-option)
+   (cider-log--exceptions-option)
+   (cider-log--level-option)
+   (cider-log--logger-option)
+   (cider-log--pattern-option)
+   (cider-log--start-time-option)
+   (cider-log--threads-option)]
+  ["Actions"
+   ("a" cider-log--do-add-consumer)]
+  (interactive (cider-log--consumer-interactive-list))
+  (cider-log--ensure-initialized framework appender consumer)
+  (transient-setup 'cider-log-add-consumer))
+
+(transient-define-prefix cider-log-update-consumer (framework appender 
consumer)
+  "Show the menu to update a Cider log consumer."
+  :history-key 'cider-log-consumer
+  ["Cider Log Consumer\n\nFilters:"
+   (cider-log--end-time-option)
+   (cider-log--exceptions-option)
+   (cider-log--level-option)
+   (cider-log--logger-option)
+   (cider-log--pattern-option)
+   (cider-log--start-time-option)
+   (cider-log--threads-option)]
+  ["Actions"
+   ("u" cider-log--do-update-consumer)]
+  (interactive (cider-log--consumer-interactive-list))
+  (cider-log--ensure-initialized framework appender consumer)
+  (transient-setup 'cider-log-update-consumer))
+
+;;;###autoload (autoload 'cider-log-consumer "cider-log" "Show the Cider log 
consumer menu." t)
+(transient-define-prefix cider-log-consumer (framework appender consumer)
+  "Show the Cider log consumer menu."
+  :history-key 'cider-log-consumer
+  ["Cider Log Consumer\n\nFilters:"
+   (cider-log--end-time-option)
+   (cider-log--exceptions-option)
+   (cider-log--level-option)
+   (cider-log--logger-option)
+   (cider-log--pattern-option)
+   (cider-log--start-time-option)
+   (cider-log--threads-option)]
+  ["Actions"
+   ("a" cider-log--do-add-consumer)
+   ("k" cider-log-kill-consumer)
+   ("u" cider-log--do-update-consumer)]
+  (interactive (cider-log--consumer-interactive-list))
+  (cider-log--ensure-initialized framework appender consumer)
+  (transient-setup 'cider-log-consumer))
+
+;; Log Event Transient Menu
+
+(transient-define-prefix cider-log-event-search (framework appender)
+  "Search the search log events menu."
+  :history-key 'cider-log-event
+  ["Cider Log Event\n\nPagination:"
+   (cider-log--limit-option)
+   (cider-log--offset-option)]
+  ["Filters:"
+   (cider-log--end-time-option)
+   (cider-log--exceptions-option)
+   (cider-log--level-option)
+   (cider-log--logger-option)
+   (cider-log--pattern-option)
+   (cider-log--start-time-option)
+   (cider-log--threads-option)]
+  ["Actions"
+   ("s" cider-log--do-search-events)]
+  (interactive (list (cider-log--framework) (cider-log--appender)))
+  (cider-log--ensure-initialized framework appender)
+  (transient-setup 'cider-log-event-search))
+
+;;;###autoload (autoload 'cider-log-event "cider-log" "Show the Cider log 
event menu." t)
+(transient-define-prefix cider-log-event (framework appender)
+  "Show the Cider log event menu."
+  :history-key 'cider-log-event
+  ["Cider Log Event\n\nPagination:"
+   (cider-log--limit-option)
+   (cider-log--offset-option)]
+  ["Filters:"
+   (cider-log--end-time-option)
+   (cider-log--exceptions-option)
+   (cider-log--level-option)
+   (cider-log--logger-option)
+   (cider-log--pattern-option)
+   (cider-log--start-time-option)
+   (cider-log--threads-option)]
+  ["Actions"
+   ("c" cider-log-clear-event-buffer)
+   ("e" cider-log-show-stacktrace)
+   ("i" cider-log-inspect-event)
+   ("p" cider-log-print-event)
+   ("s" cider-log--do-search-events)]
+  (interactive (list (cider-log--framework) (cider-log--appender)))
+  (cider-log--ensure-initialized framework appender)
+  (transient-setup 'cider-log-event))
+
+;; Main Transient Menu
+
+;;;###autoload (autoload 'cider-log "cider-log" "Show the Cider log menu." t)
+(transient-define-prefix cider-log (framework appender)
+  "Show the Cider log menu."
+  [["Framework Actions"
+    ("fs" cider-log-set-framework)
+    ("fb" cider-log-set-buffer)
+    ("fj" cider-log-browse-javadocs)
+    ("fw" cider-log-browse-website)]
+   ["Appender Actions"
+    ("aa" "Add log appender" cider-log-add-appender
+     :inapt-if cider-log-appender-attached-p)
+    ("ac" cider-log-clear-appender)
+    ("ak" cider-log-kill-appender)
+    ("am" "Manage appender" cider-log-appender)
+    ("au" "Update log appender" cider-log-update-appender
+     :inapt-if-not cider-log-appender-attached-p)]
+   ["Consumer Actions"
+    ("ca" "Add log consumer" cider-log-add-consumer
+     :inapt-if cider-log-consumer-attached-p)
+    ("ck" cider-log-kill-consumer)
+    ("cm" "Manage consumer" cider-log-consumer)
+    ("cu" "Update log consumer" cider-log-update-consumer
+     :inapt-if-not cider-log-consumer-attached-p)]
+   ["Event Actions"
+    ("eb" cider-log-switch-to-buffer)
+    ("ec" cider-log-clear-event-buffer)
+    ("ee" cider-log-show-stacktrace)
+    ("ei" cider-log-inspect-event)
+    ("ep" cider-log-print-event)
+    ("es" "Search log events" cider-log-event-search
+     :inapt-if-not cider-log-appender-attached-p)]]
+  (interactive (list (cider-log--framework) (cider-log--appender)))
+  (cider-log--ensure-initialized framework appender)
+  (transient-setup 'cider-log))
+
+(add-hook 'kill-buffer-hook #'cider-log-kill-buffer-hook-handler)
+
+(provide 'cider-log)
+
+;;; cider-log.el ends here
diff --git a/cider-mode.el b/cider-mode.el
index 94a854a8ec..2d7234ec5b 100644
--- a/cider-mode.el
+++ b/cider-mode.el
@@ -33,6 +33,7 @@
 
 (require 'clojure-mode)
 (require 'cider-eval)
+(require 'cider-log)
 (require 'cider-test) ; required only for the menu
 (require 'cider-eldoc)
 (require 'cider-resolve)
@@ -529,6 +530,12 @@ higher precedence."
     (define-key map (kbd "C-c C-? C-d") #'cider-xref-fn-deps-select)
     (define-key map (kbd "C-c C-q") #'cider-quit)
     (define-key map (kbd "C-c M-r") #'cider-restart)
+    (define-key map (kbd "C-c l a") #'cider-log-appender)
+    (define-key map (kbd "C-c l c") #'cider-log-consumer)
+    (define-key map (kbd "C-c l e") #'cider-log-event)
+    (define-key map (kbd "C-c l f") #'cider-log-framework)
+    (define-key map (kbd "C-c l i") #'cider-log-info)
+    (define-key map (kbd "C-c l l") #'cider-log)
     (dolist (variable '(cider-mode-interactions-menu
                         cider-mode-eval-menu
                         cider-mode-menu))
diff --git a/cider.el b/cider.el
index dbcad2f579..476c6cd779 100644
--- a/cider.el
+++ b/cider.el
@@ -12,7 +12,7 @@
 ;; Maintainer: Bozhidar Batsov <bozhidar@batsov.dev>
 ;; URL: http://www.github.com/clojure-emacs/cider
 ;; Version: 1.8.0-snapshot
-;; Package-Requires: ((emacs "26") (clojure-mode "5.16.0") (parseedn "1.0.6") 
(queue "0.2") (spinner "1.7") (seq "2.22") (sesman "0.3.2"))
+;; Package-Requires: ((emacs "26") (clojure-mode "5.16.0") (parseedn "1.0.6") 
(queue "0.2") (spinner "1.7") (seq "2.22") (sesman "0.3.2") (logview "0.16.1") 
(transient "0.4.1"))
 ;; Keywords: languages, clojure, cider
 
 ;; This program is free software: you can redistribute it and/or modify
diff --git a/doc/modules/ROOT/assets/images/cider-log.png 
b/doc/modules/ROOT/assets/images/cider-log.png
new file mode 100644
index 0000000000..6584599c59
Binary files /dev/null and b/doc/modules/ROOT/assets/images/cider-log.png differ
diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc
index d22f943b60..121efcc789 100644
--- a/doc/modules/ROOT/nav.adoc
+++ b/doc/modules/ROOT/nav.adoc
@@ -38,6 +38,7 @@
 ** xref:debugging/debugger.adoc[Debugger]
 ** xref:debugging/enlighten.adoc[Enlighten]
 ** xref:debugging/inspector.adoc[Inspector]
+** xref:debugging/logging.adoc[Logging]
 ** xref:debugging/macroexpansion.adoc[Macroexpansion]
 ** xref:debugging/profiling.adoc[Profiling]
 ** xref:debugging/tracing.adoc[Tracing]
diff --git a/doc/modules/ROOT/pages/debugging/logging.adoc 
b/doc/modules/ROOT/pages/debugging/logging.adoc
new file mode 100644
index 0000000000..3d94b6af0b
--- /dev/null
+++ b/doc/modules/ROOT/pages/debugging/logging.adoc
@@ -0,0 +1,274 @@
+= Logging
+:experimental:
+
+CIDER Log Mode allows you to capture, debug, inspect and view log
+events emitted by Java logging frameworks. The captured log events can
+be searched, streamed to the client, pretty printed and are integrated
+with the CIDER link:inspector.html[Inspector] and
+link:../usage/dealing_with_errors.html[Stacktrace Mode]. Here is a
+screenshot of CIDER Log Mode in action.
+
+image::cider-log.png[CIDER Log]
+
+NOTE: The screenshot displays the list of log events in the
+`+*cider-log*+` buffer on the left. To the right, a log event is
+visible in the `+*cider-inspect*+` buffer, where the exception of the
+event is also displayed in the CIDER Stacktrace Mode. From the
+Stacktrace Mode buffer you can jump to the source of each frame. At
+the bottom the CIDER log menu is shown from which you can perform
+logging related actions.
+
+== Features
+
+- Browse Javadocs and website of log framework.
+- Search log events and show them in buffers.
+- link:../usage/pretty_printing.html[Pretty Print] log events.
+- Show log events in the CIDER link:inspector.html[Inspector]
+- Show log event exceptions in the CIDER 
link:../usage/dealing_with_errors.html[Stacktrace Mode]
+
+== Usage
+
+To use CIDER Log Mode, type kbd:[C-c l l] or kbd:[M-x cider-log] in
+any buffer that has a CIDER https://github.com/vspinu/sesman[Sesman]
+session attached to it. The first time you run the command, it will
+prompt you to select a log framework to use, and then attach a log
+appender to the root logger of the selected framework. After the log
+appender has been attached, the `cider-log` command will show a
+https://www.gnu.org/software/emacs/manual/html_mono/transient.html[Transient]
+menu, from which you can take further actions, like managing the log
+framework, appenders, consumers and events.
+
+To view log events and stream them to your client, type kbd:[es]
+(Search log events) followed by kbd:[s]. This will open the
+`+*cider-log*+` buffer showing any log events captured thus far. It will
+also add a log consumer to this buffer, which receives newly-arriving
+log events.
+
+NOTE: The `+*cider-log*+` buffer might initially be empty, and you may
+see a `No log events found` message. This is because nothing has been
+logged between adding the appender and searching for events. So, now
+would be a good time to run some code that triggers a log event for
+the selected framework.
+
+=== Keybindings
+
+|===
+| Command | Keyboard shortcut | Description
+
+| `cider-log`
+| kbd:[C-c l l]
+| Show the CIDER log menu.
+
+| `cider-log-framework`
+| kbd:[C-c l f]
+| Show the menu to manage a logging framework.
+
+| `cider-log-appender`
+| kbd:[C-c l a]
+| Show the menu to manage appenders of a logging framework.
+
+| `cider-log-consumer`
+| kbd:[C-c l c]
+| Show the menu to manage consumers listening to log events.
+
+| `cider-log-event`
+| kbd:[C-c l e]
+| Show the menu to manage log events.
+|===
+
+== Log framework
+
+CIDER Log Mode supports log frameworks that allow reconfiguration at
+run time. More specifically the framework should support attaching log
+appenders to loggers, in order to capture events.
+
+At the moment the following log frameworks are supported:
+
+- 
https://docs.oracle.com/en/java/javase/19/core/java-logging-overview.html[Java 
Util Logging]
+- https://logback.qos.ch[Logback]
+
+There is some https://github.com/clojure-emacs/logjam/issues/2[work in
+progress] to support https://logging.apache.org/log4j/2.x/[Log4j] as
+well, but there are some
+https://stackoverflow.com/a/17842174/12711900[difficulties] with
+configuration changes made at runtime, which are wiped out by the
+Log4j2 reconfiguration mechanism.
+
+=== Keybindings
+
+|===
+| Command | Keyboard shortcut | Description
+
+| `cider-log-set-framework`
+| kbd:[C-c l f s]
+| Select the log framework to use.
+
+| `cider-log-set-buffer`
+| kbd:[C-c l f b]
+| Select the log buffer to user. Default: `+*cider-log*+`
+
+| `cider-log-browse-javadocs`
+| kbd:[C-c l f j]
+| Browse the Javadocs of the log framework.
+
+| `cider-log-browse-website`
+| kbd:[C-c l f w]
+| Browse the website of the log framework.
+|===
+
+== Log Appender
+
+In order to capture log events, a log appender needs to be attached to
+a logger of a framework. Once an appender is attached to a logger it
+captures the log events emitted by the framework in an in-memory
+atom. A log appender can be configured to have a certain size
+(default: 100000) and a threshold in percentage (default: 10). Log
+events are cleared from the appender when threshold (appender size
+plus threshold) is reached. Additionally an appender can be configured
+to only capture events that match a set of filters.
+
+=== Keybindings
+
+The following keybindings can be used to interact with log appenders.
+
+|===
+| Command | Keyboard shortcut | Description
+
+| `cider-log-appender`
+| kbd:[C-c l a]
+| Show the transient menu to manage log appenders.
+
+| `cider-log-add-appender`
+| kbd:[C-c l a a]
+| Add a log appender to a logger.
+
+| `cider-log-clear-appender`
+| kbd:[C-c l a c]
+| Clear all captured events of a log appender.
+
+| `cider-log-kill-appender`
+| kbd:[C-c l a k]
+| Kill a log appender by removing it from the logger.
+
+| `cider-log-update-appender`
+| kbd:[C-c l a u]
+| Update the filters, size or threshold of a log appender.
+|===
+
+== Log Consumer
+
+Log events can be streamed to a client by attaching a log consumer to
+an appender. Once a log consumer has been attached to an appender, it
+will receive events from the appender. Similar to log appenders,
+consumers can also be configured with a set of filters to only receive
+certain events.
+
+=== Keybindings
+
+The following keybindings can be used to interact with log consumers.
+
+|===
+| Command | Main / Consumer Menu | Keyboard shortcut | Description
+
+| `cider-log-consumer`
+|
+| kbd:[C-c l c]
+| Show the transient menu to manage log consumers.
+
+| `cider-log-add-consumer`
+| kbd:[ca] / kbd:[a]
+| kbd:[C-c l c a]
+| Add a log consumer to a log appender streaming event to the client.
+
+| `cider-log-kill-consumer`
+| kbd:[ck] / kbd:[k]
+| kbd:[C-c l c k]
+| Kill a log consumer and stop streaming events to the client.
+
+| `cider-log-update-consumer`
+| kbd:[cu] / kbd:[u]
+| kbd:[C-c l c u]
+| Update the filters of a log consumer to change which events are streamed to 
the client.
+|===
+
+== Log Event
+
+Log events can be searched, streamed to a client or viewed in CIDER's
+Inspector and Stacktrace Mode. When searching log events the user can
+specify a set of filters. Events that match the filters are shown in
+the `+*cider-log*+` buffer. Additionally a log consumer will be
+attached to the appender to receive log events matching the search
+criteria after the search command has been issued. The log appender
+will be removed automatically once a new search has been submitted or
+when the `+*cider-log*+` buffer gets killed.
+
+=== Keybindings
+
+The following keybindings can be used to interact with log events.
+
+|===
+| Command | Keyboard shortcut | Description
+
+| `cider-log-event`
+| kbd:[C-c l e]
+| Show the transient menu to manage log events.
+
+| `cider-log-clear-event-buffer`
+| kbd:[C-c l e c]
+| Clear all events from the log event buffer.
+
+| `cider-log-show-stacktrace`
+| kbd:[C-c l e e]
+| Show the stacktrace of the log event at point in the CIDER Stacktrace Mode.
+
+| `cider-log-inspect-event`
+| kbd:[C-c l e i]
+| Show the log event in the CIDER Inspector.
+
+| `cider-log-print-event`
+| kbd:[C-c l e p]
+| Pretty print the log event in the `+*cider-log-event*+` buffer.
+
+| `cider-log-event-search`
+| kbd:[C-c l e s]
+| Search log events and show them in the `+*cider-log*+` buffer.
+|===
+
+== Log Filters
+
+Filters for log events can be attached to log appenders and
+consumers. They also take effect when searching events or streaming
+them to clients. If multiple filters are chosen they are combined
+using logical AND condition. The following filters are available:
+
+|===
+| Filter  | Keyboard shortcut | Description
+
+| `end-time`
+| kbd:[-e]
+| Only include log events that were emitted before `end-time`.
+
+| `exceptions`
+| kbd:[-E]
+| Only include log events caused by an exception in the list of `exceptions`.
+
+| `level`
+| kbd:[-l]
+| Only include log events with a log level above `level`.
+
+| `loggers`
+| kbd:[-L]
+| Only include log events that were emitted by a logger in the list of 
`loggers`.
+
+| `pattern`
+| kbd:[-r]
+| Only include log events whose message matcches the regular expression 
`pattern`.
+
+| `start-time`
+| kbd:[-s]
+| Only include log events that were emitted at, or after `start-time`.
+
+| `threads`
+| kbd:[-t]
+| Only include log events that were emitted by a thread in the list of 
`threads`.
+|===
diff --git a/test/cider-log-test.el b/test/cider-log-test.el
new file mode 100644
index 0000000000..662f9fc061
--- /dev/null
+++ b/test/cider-log-test.el
@@ -0,0 +1,52 @@
+;;; cider-log-tests.el  -*- lexical-binding: t; -*-
+
+;; Copyright © 2023 Bozhidar Batsov and CIDER contributors
+
+;; Author: r0man <roman@burningswell.com>
+
+;; This file is NOT part of GNU Emacs.
+
+;; This program is free software: you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License as
+;; published by the Free Software Foundation, either version 3 of the
+;; License, or (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;; General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see `http://www.gnu.org/licenses/'.
+
+;;; Commentary:
+
+;; This file is part of CIDER
+
+;;; Code:
+
+(require 'buttercup)
+(require 'cider-log)
+
+(describe "cider-log"
+  (let ((framework (nrepl-dict "id" "jul" "name" "Java Util Logging"))
+        (appender (nrepl-dict "id" "cider-log")))
+
+    (it "raises user-error when cider is not connected."
+      (spy-on 'cider-connected-p :and-return-value nil)
+      (expect (cider-log framework appender) :to-throw 'user-error))
+
+    (it "doesn't add an appender when initialized."
+      (let ((cider-log--initialized-once-p t))
+        (spy-on 'cider-sync-request:log-frameworks :and-return-value (list 
framework))
+        (spy-on 'transient-setup)
+        (cider-log framework appender)
+        (expect 'transient-setup :to-have-been-called-with 'cider-log)))
+
+    (it "does add an appender when not initialized."
+      (let ((cider-log--initialized-once-p nil))
+        (spy-on 'cider-sync-request:log-frameworks :and-return-value (list 
framework))
+        (spy-on 'cider-sync-request:log-add-appender :and-return-value 
appender)
+        (spy-on 'transient-setup)
+        (cider-log framework appender)
+        (expect 'transient-setup :to-have-been-called-with 'cider-log)))))



reply via email to

[Prev in Thread] Current Thread [Next in Thread]