(eval-when (:compile-toplevel :load-toplevel :execute) (defconstant +dribble+ 0) (defconstant +debug+ 1)[...] (defconstant +info+ 2) (defconstant +warn+ 3) (defconstant +error+ 4) (defconstant +fatal+ 5) (deflookup-table logger))
(defclass log-category () ((ancestors :initform '() :accessor ancestors :initarg :ancestors) (childer :initform '() :Accessor childer :initarg :childer)[...] (appenders :initform '() :accessor appenders :initarg :appenders) (level :initform +debug+ :initarg :level :accessor level) (name :initarg :name :accessor name)))
(defmethod shared-initialize :after ((l log-category) slot-names &key ancestors &allow-other-keys) (declare (ignore slot-names)) (dolist (anc ancestors) (pushnew l (childer anc) :test (lambda (a b) (eql (name a) (name b))))))
(defmethod enabled-p ((cat log-category) level) (>= level (log.level cat)))
(defmethod log.level ((cat log-category)) (with-slots (level) cat (or level[...] (if (ancestors cat) (loop for ancestor in (ancestors cat) minimize (log.level ancestor)) (error "Can't determine level for ~S" cat)))))
(defmethod (setf log.level) (new-level (cat log-category) &optional (recursive t)) "Change the log level of CAT to NEW-LEVEL. If RECUSIVE is T the setting is also applied to the sub categories of CAT."[...] (setf (slot-value cat 'level) new-level) (when recursive (dolist (child (childer cat)) (setf (log.level child) new-level))))
(defgeneric handle (category message level))
(defmethod handle ((cat log-category) message level) (if (appenders cat) ;; if we have any appenders send them the message[...] (dolist (appender (appenders cat)) (append-message cat appender message level)) ;; send the message to our ancestors (dolist (ancestor (ancestors cat)) (handle ancestor message level))))
(defgeneric append-message (category log-appender message level))
(defclass stream-log-appender () ((stream :initarg :stream :accessor log-stream)) (:documentation "Human readable to the console logger."))
(defmethod append-message ((category log-category) (s stream-log-appender) message level) (multiple-value-bind (second minute hour date month year day) (decode-universal-time (get-universal-time))[...] (declare (ignore date)) (format (log-stream s) "~4,'0D-~2,'0D-~2,'0DT~2,'0D:~2,'0D.~2,'0D ~S ~S: " year month day hour minute second level (name category)) (format (log-stream s) "~A~%" message)))
(defun make-stream-log-appender (&optional (stream *debug-io*)) (make-instance 'stream-log-appender :stream stream))
(defclass file-log-appender (stream-log-appender) ((log-file :initarg :log-file :accessor log-file)) (:documentation "Logs to a file. the output of the file logger is not meant to be read directly by a human."))
(defmethod append-message ((category log-category) (appender file-log-appender) message level) (with-output-to-file (log-file (log-file appender) :if-exists :append[...] :if-does-not-exist :create) (let ((*package* (find-package :it.bese.arnesi))) (format log-file "(~S ~D ~S ~S)~%" level (get-universal-time) (name category) message))))
(defun make-file-log-appender (file-name) (make-instance 'file-log-appender :log-file file-name))
(defmacro deflogger (name ancestors &key level appender appenders documentation) (declare (ignore documentation)) (when appender[...] (setf appenders (append appenders (list appender)))) (let ((ancestors (mapcar (lambda (ancestor-name) `(or (get-logger ',ancestor-name) (error "Attempt to define a sub logger of the undefined logger ~S." ',ancestor-name))) ancestors))) (flet ((make-log-helper (suffix level) `(defmacro ,(intern (strcat name "." suffix)) (message-control &rest message-args) `(when (enabled-p (get-logger ',',name) ,',level) ,(if message-args `(handle (get-logger ',',name) (format nil ,message-control ,@message-args) ',',level) `(handle (get-logger ',',name) ,message-control ',',level)))))) `(progn (setf (get-logger ',name) (make-instance 'log-category :name ',name :level ,level :appenders (list ,@appenders) :ancestors (list ,@ancestors))) ,(make-log-helper '#:dribble '+dribble+) ,(make-log-helper '#:info '+info+) ,(make-log-helper '#:warn '+warn+) ,(make-log-helper '#:error '+error+) ,(make-log-helper '#:fatal '+fatal+)))))
-*- lisp -*-
(in-package :it.bese.arnesi)