;;; nsd.el --- Major mode for AOLServer error logs ;; ;; Author: David Lutterkort ;; Created: 2000-12-15 ;; $Id: nsd.el,v 1.6 2000/12/27 23:31:03 lutter Exp $ ;; Purpose: ;; Mode for looking at AOLServer error logs and extracting SQL queries ;; from them. ;;; Commentary: ;; INTRODUCTION: ;; nsd.el lets you tail AOLServer error logs within Emacs. It performs ;; some simplifications on the contents of the log files to increase ;; readability, provides colorful highlighting of the error log and ;; lets you extract SQL queries with one keystroke. ;; ;; The extracted queries are put both into a special buffer and on ;; the kill-ring, so that running a query only requires a yank in ;; your SQL*Plus buffer. ;; INSTALLATION: ;; Put this file somewhere on your load-path and add the following ;; lines to your .emacs file: ;; ;; (require 'nsd) ;; (global-set-key "\C-c\C-t" 'nsd-tail-error) ;; (add-to-list 'nsd-error-log-dirs "some-dir") ;; ;; The directory SOME-DIR is the directory where your AOLServer keeps ;; error logs. ;; ;; Type \C-c\C-t and type the name of an AOLServer at the pompt. For an ;; error log that is kept in SOME-DIR/SERVER-error.log, the server name is ;; SERVER. A buffer *SERVER-error* is created and the error log is tailed ;; into it. To extract a query, put point somewhere before the "SQL()" ;; string and press "\C-c\M-w". You can now yank the query (with bind ;; variables substituted for their values :) into your SQL*Plus buffer. ;; ;; The queries are assembled in buffer *aol-query*, where you can admire ;; them in all their glory. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Required Modules ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Generic mode. ;; In older versions of Emacs, generic.el provides ;; generic-mode, stupidly (condition-case nil (require 'generic) (error (load "generic"))) ;; SQL Mode for making the scratch buffer prettier ;; We don't really need SQL mode. If it's not there ;; alias it to fundamental-mode (condition-case nil (require 'sql) (error (defun sql-mode () (interactive) (fundamental-mode)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Variables ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defvar nsd-error-log-dirs '("/webroot/aol30f/log" "/home/aol32/log" "/var/log/aolserver") "*List of directories that might contain AOLServer error log files.") (defvar nsd-query-buffer-name "*aol-query*" "*Buffer to use for assembling SQL queries that were pulled out of the error log.") (defvar nsd-filter-regexps '(("^\\(\\[.*\\]\\)\\[.*\\]\\[.*\\]" . "\\1") ("bind variable '\\([^']*\\)' = \\('[^']*'\\)" . ":\\1 = \\2")) "List of substitutions to make on the AOLServer error log to make it more readable. Each element of the list is a pair whose car contains the regexp to search for and whose cdr contains the replacement text. See query-replace-regexp for the syntax of those strings. If you change these regexps you probably have to also change nsd-query-regexp and nsd-bindvar-regexp.") (defvar nsd-query-regexp "SQL():\\s *\\([^[]*\\)\\s-*\\[" "Regexp to find a SQL Query in the logfile. The value of \\1 after doing a re-search-forward needs to contain the query. This regexp is matched against the results of nsd-error-filter.") (defvar nsd-bindvar-regexp ":\\([A-Za-z0-9_]+\\) = \\('[^']*'\\)" "Regexp that matches bind variables and their values immediately following a query. This regexp is matched against the results of nsd-error-filter. After matching, \\1 must contain the name of the bind variable and \\2 its value.") ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; The major mode for error logfiles ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define-generic-mode 'nsd-error-mode nil '("Notice" "Error" "Warning") '(("^\\(\\[.*\\]\\)" 1 font-lock-string-face) ("\\(:[A-Za-z_0-9]+\\)" 1 font-lock-variable-name-face) ("SQL():\\([^[]*\\)" 1 font-lock-function-name-face) ("\\(SQL()\\|bind variable\\)" 1 font-lock-comment-face)) nil (list (lambda () (local-set-key "\C-c\M-w" 'nsd-query-extract) (local-set-key "\C-c\C-n" 'nsd-next-error) (local-set-key "\C-c\C-p" 'nsd-prev-error) (local-set-key '[C-down] 'nsd-next-warning) (local-set-key '[C-up] 'nsd-prev-warning) (local-set-key "\C-c\C-f" 'nsd-next-query) (local-set-key "\C-c\C-b" 'nsd-prev-query) (local-set-key "\C-c>" 'nsd-next-start) (local-set-key "\C-c<" 'nsd-prev-start) (local-set-key "\C-c\M-f" 'nsd-error-filter))) "Major mode for AOLServer error logs. Does some replacements on the contents of the error log to tighten the display up and provides a way to extract SQL queries into the kill-ring (via nsd-query-extract). If you turn on font-lock-mode, you also get nice highlighting.") ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Movement in the error log ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun nsd-next-start (n) "Move to the next AOLserver starting message" (interactive "p") (condition-case nil (re-search-forward "AOLServer.*starting$" nil nil n) (error (error "No more server starts")))) (defun nsd-prev-start (n) "Move to the previous AOLServer starting message" (interactive "p") (nsd-next-start (- 1))) (defun nsd-next-log (n kind) "Move cursor to next/prev log message of a given class" (condition-case nil (search-forward (concat kind ":") nil nil n) (error (error (format "No more %ss" kind))))) (defun nsd-next-error (n) "Move cursor to the next error" (interactive "p") (nsd-next-log n "error")) (defun nsd-prev-error (n) "Move cursor to the previous error" (interactive "p") (nsd-next-error (- n))) (defun nsd-next-warning (n) "Move cursor to the next warning" (interactive "p") (nsd-next-log n "warning")) (defun nsd-prev-warning (n) "Move cursor to the previous warning" (interactive "p") (nsd-next-warning (- n))) (defun nsd-next-query (n) "Move cursor to the next SQL query" (interactive "p") (condition-case nil (progn (re-search-forward nsd-query-regexp nil nil n) (goto-char (/ (+ (match-beginning 1) (match-end 1)) 2))) (error (error "No more queries")))) (defun nsd-prev-query (n) "Move cursor to the previous SQL query" (interactive "p") (nsd-next-query (- n))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Functions for the tailing of error logs ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun nsd-error-filter (start end) "Make the replacements specified in nsd-filter-regexps in the current region." (interactive "r") (let ((repls nsd-filter-regexps)) (save-excursion (while repls (let ((pattern (caar repls)) (subst (cdar repls))) (goto-char start) (while (re-search-forward pattern end t) (replace-match subst))) (setq repls (cdr repls)))))) (defun nsd-error-proc-filter (proc string) "Process filter that takes input from nsd-tail-error and runs nsd-error-filter on it." (with-current-buffer (process-buffer proc) (save-excursion (goto-char (process-mark proc)) (insert string) (nsd-error-filter (process-mark proc) (point-max))) (set-marker (process-mark proc) (point-max)))) (defun nsd-tail-error (server) "Tail an AOLServer error log in a buffer. SERVER is the name of the AOLServer. The log will be tailed in a buffer named *SERVER-error*. The logfile must be called SERVER-error.log. The directories nsd-error-log-dirs are sought until one containing a logfile is found. The logfile is processed by nsd-error-filter and tailed in *SERVER-error*. The buffer is put in aolserver-error-mode." (interactive "sServer: ") (let* ((proc-name (concat server "-error")) (buffer-name (concat "*" proc-name "*")) (dir-list nsd-error-log-dirs) (error-dir nil) (errlog "/*")) (when (not (get-buffer buffer-name)) (while (and dir-list (not (file-readable-p errlog))) (setq error-dir (file-name-as-directory (car dir-list))) (setq errlog (concat error-dir proc-name ".log")) (message "Trying %s" errlog) (setq dir-list (cdr dir-list))) (if (not (file-readable-p errlog)) (error "No logfile for %s found" server)) ; found an error log (message "Starting tail on %s" errlog) (start-process proc-name buffer-name "tail" "-f" errlog) (process-kill-without-query (get-process proc-name) nil) (set-process-filter (get-process proc-name) 'nsd-error-proc-filter) (save-excursion (set-buffer buffer-name) (setq default-directory error-dir) (nsd-error-mode))) (set-window-buffer (selected-window) (get-buffer-create buffer-name)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Functions for extracting SQL queries from the error log ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun nsd-set-query-buffer () "Setup the scratch buffer for treating queries and make it the current buffer." (let* ((exists (get-buffer nsd-query-buffer-name)) (buffer (or exists (get-buffer-create nsd-query-buffer-name)))) (set-buffer buffer) (when (not exists) (sql-mode) ; Make _ a word constituent character (modify-syntax-entry ?_ "w")))) (defun nsd-query-extract () "Extract a SQL query from the AOLServer error log. Search forward from point for a match of nsd-query-regexp. Bind variables are found by matching nsd-bindvar-regexp on the lines immediately following the query. The end of the bind variables for a query is recoginized by a line that does not match nsd-bindvar-regexp. The bind variables are substituted into the query and the substituted query is pasted into a buffer called nsd-query-buffer-name" (interactive) (re-search-backward "^\\[" nil t) (when (re-search-forward nsd-query-regexp nil t) (goto-char (match-end 0)) (let ((query (match-string 1)) (line (count-lines (point-min) (match-beginning 1))) (error-log (buffer-name (current-buffer))) (bind-vars '())) ;; Put a mark at the beginning of query (push-mark (match-beginning 1)) ;; Find the values of the bind variables ;; They have to be in consecutive lines immediately ;; following the query (while (re-search-forward nsd-bindvar-regexp (line-end-position) t) (let ((var (match-string 1)) (val (match-string 2))) (if (not (string= var "1")) (setq bind-vars (cons (cons var val) bind-vars)))) (goto-char (line-beginning-position 2))) ;; Go to the scratch buffer and substitute bind variables ;; by their values (nsd-set-query-buffer) (goto-char (point-max)) (insert (format "\n\n-- Query from %s, line %d\n" error-log line)) (let ((start (point))) (insert query "\n") ;; Erase all the whitespace at the end of the buffer ;; Is there a better way to do this ? (goto-char (point-max)) (delete-blank-lines) (backward-char) (fixup-whitespace) (insert ";") (while bind-vars (goto-char start) (insert (format "-- %s = %s\n" (caar bind-vars) (cdar bind-vars))) (setq start (point)) (while (re-search-forward (concat ":\\<" (caar bind-vars) "\\>") nil t) (replace-match (cdar bind-vars))) (setq bind-vars (cdr bind-vars))) (kill-new (buffer-substring start (point-max))))))) (provide 'nsd)