logviewer-mode of emacs

    近来经常需要查看某些 Log 文件,当用 Emacs 打开这些巨大的 Log 文件的时候,总是需要等待较长的时间,且没有什么语法高亮, 不能让自己感兴趣的区域来吸引眼球。

    Google 了一下,发现没有很合适的 log viewer , 有一个 log4j, 试用了一下,效果不好,还有一个纯粹的 viewer-mode , 也不太好。 于是自己动手写了一个,主要特色有:

  1. Log 语法高亮
  2. 与 VIM 打开 /var/log/messages 时的效果类似。
  3. 打开大文件时自动分割成若干小文件,并逐片打开和显示这个功能在打开大文件时候比较好用,该状态下,可通过 n 和 p 来在分片中切换 。
  4. (未完成)Log 级别过滤该功能尚未添加,主要是想可以对 log 进行过滤,比如只显示 ERROR 以上级别的 log 等, 现在对这个功能的需求不大,等以后再加上吧。

代码如下(使用方法参考代码注释): 


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; logviewer.el --- Simple log viewer.
;;
;; Copyright (C) 2011, Yang, Ying-chao
;;
;; Author:        Yang, Ying-chao <yangyingchao@gmail.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 2
;; 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, write to the Free Software
;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
;;
;; Commentary:
;;
;;   This is a simple log viewer, with syntax highlight.
;;
;;   To use logviewer, you should put logviewer.el into the top of load-path
;; of emacs, the add following lines into your .emacs:
;; (require 'logviewer)
;;
;;   When log files are huge, it will try to split huge logs into small ones
;; to speed up loading. In that case, you can press "n" & "p" to go to next
;; part (or previous part) to the log file. You can custom variable
;; logviewer-split-line to proper number to control the size of the slice of
;; huge file.
;;
;; TODO:
;;   Add support of log filtering, ie, show logs whos level is higher than
;; some specified one.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;; Code
;; custom hooks
(defvar logviewer-mode-hook nil)

;; default mode map, really simple
(defvar logviewer-mode-map
  (let ((logviewer-mode-map (make-keymap)))
    (define-key logviewer-mode-map "n"
      (lambda () (interactive) (logviewer-next-file t)))
    (define-key logviewer-mode-map "p"
      (lambda () (interactive) (logviewer-next-file nil)))
    logviewer-mode-map)
  "Keymap for PS major mode")

(defvar logviewer-indent-width 4)

(defvar logviewer-font-lock-keywords
  `(
    ;; Date & time.
    (,(rx line-start
          (*? not-newline) (+ digit) ":" (+ digit) ":" (+ digit)
          (? "." (+ digit)))
     . font-lock-builtin-face)
    (,(rx symbol-start
          (group (*? not-newline) (+ digit) ":" (+ digit) ":" (+ digit) (? "." (+ digit)))
          (1+ space) (group (1+ (or alnum "-" "_"  blank))) (? "["(* digit) "]")":")
     (1 font-lock-builtin-face) (2 font-lock-variable-name-face))
    (,(rx symbol-start
          (group (or "ERROR" "FATAL" "error" "fatal" )) ":"
          (group (+ (*? not-newline))) line-end)
    (1 font-lock-warning-face) (2 font-lock-comment-face))
    (,(rx symbol-start
          (group (or "info" "INFO" )) ":"
          (group (+ (*? not-newline))) line-end)
     (1 font-lock-function-name-face) (2 font-lock-doc-face))
    (,(rx symbol-start
          (group (or "DEBUG" "debug" )) ":"
          (group (+ (*? not-newline))) line-end)
     (1 font-lock-keyword-face) (2 font-lock-string-face))
    )
  )

(defvar logviewer-mode-syntax-table (make-syntax-table)
  "Syntax table for Logviewer mode")
;; (modify-syntax-entry ?( "()" logviewer-mode-syntax-table)
;;                      (modify-syntax-entry ?) ")("
;;                      logviewer-mode-syntax-table)

(defvar logviewer-imenu-expressions
  '((nil "^\\(?:[fF]unction\\|Add-Class\\)\\s-+\\([-a-z0-9A-Z_^:.]+\\)[^-a-z0-9A-Z_^:.]" 1))
  "alist of regexp identifying the start of logviewer definitions"
  )


(defvar logviewer-split-line 50000 "Lines when trying to split files.")
(defvar Logviewer-current-file nil
  "Log file viewed by logviewer")

(defun logviewer-process-sentinel (process event)
  "description"
  (when (memq (process-status process) '(signal exit))
    (let* ((exit-status       (process-exit-status process))
           (command           (process-command process))
           (source-buffer     (process-buffer process))
           )

      (condition-case err
          (delete-process process)
        (error
         (let ((err-str (format "Error in process sentinel: %s"
                                 (error-message-string err))))
           (message err-str)))))))


;;;; Overrite function provied by Emacs itself.
(defun abort-if-file-too-large (size op-type filename)
  "If file SIZE larger than `large-file-warning-threshold', allow user to abort.
OP-TYPE specifies the file operation being performed (for message to user)."
  (let* ((re-log-str (rx (or "LOG" "log" "Log")))
         (log-cache (expand-file-name "~/.emacs.d/log_cache"))
         (cur-file nil)
         (process nil)
         (filename-base (file-name-sans-extension
                         (file-name-nondirectory filename)))
         (out-file-prefix (format "%s/%s" log-cache filename-base)))
    (if (and  large-file-warning-threshold size
              (> size large-file-warning-threshold))
        (if (string-match re-log-str filename) ;; This is logfile.
            (if (y-or-n-p
                 (format "LogFile %s is large (%dMB), really %s? "
                         (file-name-nondirectory filename)
                         (/ size 1048576) op-type))
                (if (and (string= op-type "open")
                         (executable-find "split"))
                    (progn
                      (if (file-exists-p log-cache)
                          nil
                        (mkdir log-cache t))
                      (setq Logviewer-current-file
                            (format "%s000" out-file-prefix))

                      (message (format "%s*" out-file-prefix))
                      (call-process-shell-command "rm" nil "*Messages*" nil
                                    "-rf"
                                    (format "%s*" out-file-prefix))
                      (setq process
                            (start-process "Split-process" "*Messages*"
                                           "split"  "--suffix-length=3"
                                           "-d" "-l"
                                           (format "%s" logviewer-split-line)
                                           (expand-file-name filename)
                                           out-file-prefix))
                      (set-process-sentinel process
                                            'logviewer-process-sentinel)

                      (while (not (file-exists-p Logviewer-current-file))
                        (sleep-for 0.5))

                      (set-buffer (get-buffer-create filename-base))
                      (toggle-read-only 0)
                      (erase-buffer)
                      (insert-file-contents Logviewer-current-file nil)
                      (switch-to-buffer filename-base)
                      (toggle-read-only 1)
                      (add-to-list 'recentf-list filename)
                      (logviewer-mode)
                      (error "See this instead")
                      )
                  nil
                  (error "Aborted")
                  )
              (when  (not (y-or-n-p
                           (format "YC: File %s is large (%dMB), really %s? "
                                   (file-name-nondirectory filename)
                                   (/ size 1048576) op-type)))
                (error "Aborted"))
              )
          )
      )))

(defun get-next-file (cc)
  "Get next file, or previous file.
if direc = t, it returns next file, or it returns previous file"
  (let ((filename-pre nil)
        (filename-sub nil)
        (filename Logviewer-current-file)
        (filename-sub-num 0)
        (sub-len 0)
        (new-seq 0)
        (fmt "")
        (pre-rx (rx line-start (group (+? not-newline))
                    (group (+ digit)) line-end)))
    (if (string-match pre-rx filename)
        (progn
          (setq filename-pre (match-string 1 filename))
          (setq filename-sub (match-string 2 filename))
          (setq sub-len (length filename-sub))
          (if cc
              (setq new-seq (1+ (string-to-number filename-sub)))
            (setq new-seq (1- (string-to-number filename-sub)))
            )
          (format (format "%%s%%0%dd" sub-len)
                  filename-pre new-seq))
      (error "Failed to parse filename"))))

(defun logviewer-next-file (dir)
  "view next/previous file"
  (let ((next-file nil))
    (if (string-match "log_cache" Logviewer-current-file)
        (progn
          (setq next-file (get-next-file dir))
          (if (file-exists-p next-file)
              (progn
                (message (format "Now viewing: %s" next-file))
                (toggle-read-only 0)
                (erase-buffer)
                (insert-file-contents next-file nil)
                (setq Logviewer-current-file next-file)
                (toggle-read-only 1))
            (progn
              (let ((msg nil))
                (if dir
                  (setq msg (concat "Tail of log reached. File "
                                    next-file " does not exist!" ))
                  (setq msg (concat "Head of log reached. File "
                                    next-file " does not exist!" )))
                (error msg))
              )
            ))
      (error "This is the whole file"))))

(defun logviewer-setup-imenu ()
  "Installs logviewer-imenu-expression."
  (require 'imenu t)
  ;; imenu doc says these 3 are buffer-local by default
  (setq imenu-generic-expression logviewer-imenu-expressions)
  (imenu-add-menubar-index)
  )

(defun logviewer-mode ()
  "Major mode for editing Logviewer files"
  (interactive)
  (kill-all-local-variables)
  (setq major-mode 'logviewer-mode)
  (setq mode-name "logviewer")
  (set-syntax-table logviewer-mode-syntax-table)
  (use-local-map logviewer-mode-map)
  (set (make-local-variable 'font-lock-defaults)
       '(logviewer-font-lock-keywords))
  (toggle-read-only t)
  (if (not Logviewer-current-file)
  (setq Logviewer-current-file (buffer-file-name)))
  (run-hooks 'logviewer-mode-hook))

(add-to-list 'auto-mode-alist '("\\.log\\'" .
                                logviewer-mode))
(provide 'logviewer)

posted @ 2011-09-16 10:36  英超  Views(950)  Comments(0Edit  收藏  举报