Skip to content

Commit

Permalink
Add CIDER Log Mode
Browse files Browse the repository at this point in the history
  • Loading branch information
r0man committed Jun 29, 2023
1 parent 1367b25 commit 09a8f9a
Show file tree
Hide file tree
Showing 10 changed files with 1,743 additions and 2 deletions.
4 changes: 3 additions & 1 deletion .dir-locals.el
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions cider-eval.el
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1,403 changes: 1,403 additions & 0 deletions cider-log.el

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions cider-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion cider.el
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Binary file added doc/modules/ROOT/assets/images/cider-log.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions doc/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
274 changes: 274 additions & 0 deletions doc/modules/ROOT/pages/debugging/logging.adoc
Original file line number Diff line number Diff line change
@@ -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`.
|===
52 changes: 52 additions & 0 deletions test/cider-log-test.el
Original file line number Diff line number Diff line change
@@ -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)))))

0 comments on commit 09a8f9a

Please sign in to comment.