aboutsummaryrefslogtreecommitdiffstats
path: root/elisp
diff options
context:
space:
mode:
Diffstat (limited to 'elisp')
-rw-r--r--elisp/journalctl.el226
1 files changed, 226 insertions, 0 deletions
diff --git a/elisp/journalctl.el b/elisp/journalctl.el
new file mode 100644
index 0000000..fd6582c
--- /dev/null
+++ b/elisp/journalctl.el
@@ -0,0 +1,226 @@
+;;; journalctl.el --- Query journalctl -*- lexical-binding: t; -*-
+;;
+;; Copyright (C) 2022 Óscar Nájera
+;;
+;; Author: Óscar Nájera <hi@oscarnajera.com>
+;; Maintainer: Óscar Nájera <hi@oscarnajera.com>
+;; Created: September 07, 2022
+;; Modified: September 07, 2022
+;; Version: 0.0.1
+;; Homepage: https://github.com/titan/journalctl
+;; Package-Requires: ((emacs "27.1"))
+;;
+;; This file is not part of GNU Emacs.
+;;
+;;; Commentary:
+;;
+;; Query journalctl
+;;
+;;; Code:
+(require 'org)
+(require 'cl-extra)
+
+(defvar-local journalctl-current-host nil
+ "Keeps the optetes of the last call of journalctl.")
+
+(defvar-local journalctl-current-opts nil
+ "Keeps the optetes of the last call of journalctl.")
+
+(defcustom journalctl-error-keywords
+ '("Failed" "failed" "Error" "error" "critical" "couldn't" "Can't" "not" "Not" "unreachable")
+ "Keywords that mark errors in journalctl output."
+ :group 'journalctl
+ :type 'string)
+
+(defcustom journalctl-warn-keywords
+ '("Warning" "warn" "debug")
+ "Keywords that mark warnings in journalctl output."
+ :group 'journalctl
+ :type 'string)
+
+(defcustom journalctl-starting-keywords
+ '("Starting" "Activating" "Listening" "Reloading" "connect")
+ "Keywords that mark start of processes or steps in journalctl output."
+ :group 'journalctl
+ :type 'string)
+
+(defcustom journalctl-finished-keywords
+ '("Stopped" "Stopping" "Reached" "Closed" "finished" "Started" "Successfully activated"
+ "Received" "opened" "success" "enabled" "removed" "active" "Created" "loaded" "detected")
+ "Keywords that mark finished processes or steps in journalctl output."
+ :group 'journalctl
+ :type 'string)
+
+;;; faces
+(defface journalctl-error-face
+ '((t :inherit error))
+ "Face to mark errors in journalctl's output."
+ :group 'journalctl)
+
+(defface journalctl-warning-face
+ '((t :inherit warning))
+ "Face to mark warnings in journalctl's output."
+ :group 'journalctl)
+
+(defface journalctl-starting-face
+ '((t :inherit success))
+ "Face to mark starting units in journalctl's output."
+ :group 'journalctl)
+
+(defface journalctl-finished-face
+ '((t :inherit success :bold t))
+ "Face to mark finished units in journalctl's output."
+ :group 'journalctl)
+
+(defface journalctl-timestamp-face
+ '((t :inherit font-lock-type-face))
+ "Face for timestamps in journalctl's output."
+ :group 'journalctl)
+
+(defface journalctl-host-face
+ '((t :inherit font-lock-keyword-face))
+ "Face for hosts in journalctl's output."
+ :group 'journalctl)
+
+(defface journalctl-process-face
+ '((t :inherit font-lock-function-name-face))
+ "Face for hosts in journalctl's output."
+ :group 'journalctl)
+
+(defvar journalctl-font-lock-keywords
+ (let* (
+ ;; generate regex string for each category of keywords
+ (error-keywords-regexp (regexp-opt journalctl-error-keywords 'words))
+ (warn-keywords-regexp (regexp-opt journalctl-warn-keywords 'words))
+ (starting-keywords-regexp (regexp-opt journalctl-starting-keywords 'words))
+ (finished-keywords-regexp (regexp-opt journalctl-finished-keywords 'words)))
+ `(
+ (,warn-keywords-regexp . 'journalctl-warning-face)
+ (,error-keywords-regexp . 'journalctl-error-face)
+ (,starting-keywords-regexp . 'journalctl-starting-face)
+ (,finished-keywords-regexp . 'journalctl-finished-face)
+ ("^\\([A-Z][a-z]+ [0-9]+ [0-9:]+\\)" . (1 'journalctl-timestamp-face))
+ ("^\\([A-Z][a-z]+ [0-9]+ [0-9:]+\\) \\(\\(?:[[:alnum:]]\\|\\.\\)+\\)" . (2 'journalctl-host-face))
+ ("^\\([A-Z][a-z]+ [0-9]+ [0-9:]+\\) \\(\\(?:[[:alnum:]]\\|\\.\\)+\\) \\(.*?:\\)" . (3 'journalctl-process-face))
+
+ ;; note: order above matters, because once colored, that part won't change.
+ ;; in general, put longer words first
+ )))
+(defcustom journalctl-hosts
+ '("/ssh:alina|sudo::"
+ "/ssh:nina|sudo::"
+ "/sudo::")
+ "Valid hosts to connect for journal data."
+ :type (list 'string)
+ :group 'file)
+
+(defconst journalctl-list-of-options
+ '("--unit"
+ "--follow"
+ "--lines"
+ "--reverse"
+ "--grep"
+ "--boot"
+ "--since"
+ "--until"
+ "--priority")
+ "List of possible options to be given to journalctl." )
+
+(defun journalctl-system-units (host-location)
+ "Query HOST-LOCATION (a tramp path) for its systemd units."
+ (interactive (list (completing-read "Tramp host: " journalctl-hosts)))
+ (let ((default-directory host-location))
+ (with-temp-buffer
+ (start-file-process "units" (current-buffer) "systemctl" "list-units" "--all" "--quiet" "--full")
+ (sit-for 0.1)
+ (thread-last (split-string (buffer-string) "\n")
+ (mapcar (lambda (line) (car (split-string line)) ))
+ (delq nil)
+ (completing-read "Unit: ")))))
+
+
+(defun journalctl (host options)
+ "Query the log of HOST given OPTIONS."
+ (interactive
+ (let* ((picked-host (completing-read "Tramp host: " journalctl-hosts))
+ (default-options `(("--unit" ,(journalctl-system-units picked-host)) ("--follow"))))
+ (list picked-host default-options)))
+ (let ((buffer (get-buffer-create "JOURNAL LOG"))
+ (default-directory host))
+ (apply #'start-file-process "Journal" buffer "journalctl" (flatten-tree options))
+ (with-current-buffer buffer
+ (journalctl-mode)
+ (setq-local journalctl-current-host host)
+ (setq-local journalctl-current-opts options)
+ (switch-to-buffer (current-buffer)))))
+
+(defun journalctl-remove-opt (opt)
+ "Remove option from journalctl call.
+
+If OPT is set, remove this option."
+ (interactive (list (completing-read "remove option" (mapcar #'car journalctl-current-opts) nil t)))
+ (let ((new-opts (delq (assoc opt journalctl-current-opts) journalctl-current-opts))
+ (host journalctl-current-host))
+ (kill-buffer (get-buffer "JOURNAL LOG"))
+ (journalctl host new-opts)))
+
+
+(defun journalctl-add-opt (opt)
+ "Remove option from journalctl call.
+
+If OPT is set, remove this option."
+ (interactive (list (completing-read "add option" journalctl-list-of-options nil t)))
+ (let* ((buff (get-buffer "JOURNAL LOG"))
+ (opts (delq (assoc opt journalctl-current-opts) journalctl-current-opts))
+ (host journalctl-current-host))
+ (kill-buffer buff)
+ (thread-last
+ (pcase opt
+ ((or "--since" "--until") (org-read-date t))
+ ((or "--follow" "--reverse"))
+ (_ (read-string (concat opt "= "))))
+ (list opt)
+ (list)
+ (append opts)
+ (journalctl host))))
+
+(defun journalctl-edit-opts ()
+ "Edit the value of 'journalctl-current-opts'."
+ (interactive)
+ (let* ((buff (get-buffer "JOURNAL LOG"))
+ (host journalctl-current-host)
+ (opts (when buff (with-current-buffer buff journalctl-current-opts))))
+ (with-current-buffer (get-buffer-create "Edit Journalctl options")
+ (emacs-lisp-mode)
+ (cl-prettyprint opts)
+ (local-set-key "\C-c\C-c"
+ (lambda ()
+ (interactive)
+ (goto-char (point-min))
+ (kill-buffer buff)
+ (let ((standard-input (current-buffer)))
+ (journalctl host (read)))
+ (kill-buffer)))
+ (switch-to-buffer (current-buffer)))))
+
+
+;;; keymap
+(defvar journalctl-mode-map
+ (let ((map (make-keymap "journalctl")))
+ (define-key map (kbd "n") 'journalctl-next-chunk)
+ (define-key map (kbd "p") 'journalctl-previous-chunk)
+ (define-key map (kbd "+") 'journalctl-add-opt)
+ (define-key map (kbd "-") 'journalctl-remove-opt)
+ (define-key map (kbd "e") 'journalctl-edit-opts)
+ (define-key map (kbd "q") 'kill-current-buffer)
+ map)
+ "Keymap for journalctl mode.")
+
+;;;###autoload
+(define-derived-mode journalctl-mode fundamental-mode "journalctl"
+ "Major mode for viewing journalctl output."
+ ;; code for syntax highlighting
+ (setq font-lock-defaults '((journalctl-font-lock-keywords))))
+
+(provide 'journalctl)
+;;; journalctl.el ends here