Guile Library

(logging logger)

Overview

This is a logging subsystem similar to the one in the python standard library. There are two main concepts to understand when working with the logging modules. These are loggers and log handlers.

Loggers

Loggers are the front end interfaces for program logging. They can be registered by name so that no part of a program needs to be concerned with passing around loggers. In addition, a default logger can be designated so that, for most applications, the program does not need to be concerned with logger instances at all beyond the initial setup.

Log messages all flow through a logger. Messages carry with them a level (for example: 'WARNING, 'ERROR, 'CRITICAL), and loggers can filter out messages on a level basis at runtime. This way, the amount of logging can be turned up during development and bug investigation, but turned back down on stable releases.

Loggers depend on Log Handlers to actually get text to the log's destination (for example, a disk file). A single Logger can send messages through multiple Log Handlers, effectively multicasting logs to multiple destinations.

Log Handlers

Log Handlers actually route text to a destination. One or more handlers must be attached to a logger for any text to actually appear in a log.

Handlers apply a configurable transformation to the text so that it is formatted properly for the destination (for instance: syslogs, or a text file). Like the loggers, they can filter out messages based on log levels. By using filters on both the Logger and the Handlers, precise controls can be put on which log messages go where, even within a single logger.

Example use of logger

Here is an example program that sets up a logger with two handlers. One handler sends the log messages to a text log that rotates its logs. The other handler sends logs to standard error, and has its levels set so that INFO and WARN-level logs don't get through.

(use-modules (logging logger)
             (logging rotating-log)
             (logging port-log)
             (scheme documentation)
             (oop goops))

 ----------------------------------------------------------------------
 Support functions
 ----------------------------------------------------------------------
(define (setup-logging)
  (let ((lgr       (make <logger>))
        (rotating  (make <rotating-log>
                     #:num-files 3
                     #:size-limit 1024
                     #:file-name "test-log-file"))
        (err       (make <port-log> #:port (current-error-port))))

    ;; don't want to see warnings or info on the screen!!
    (disable-log-level! err 'WARN)
    (disable-log-level! err 'INFO)
    
    ;; add the handlers to our logger
    (add-handler! lgr rotating)
    (add-handler! lgr err)
    
    ;; make this the application's default logger
    (set-default-logger! lgr)
    (open-log! lgr)))


(define (shutdown-logging)
  (flush-log)   ;; since no args, it uses the default
  (close-log!)  ;; since no args, it uses the default
  (set-default-logger! #f))

 ----------------------------------------------------------------------
 Main code
 ----------------------------------------------------------------------
(setup-logging)

 Due to log levels, this will get to file, 
 but not to stderr
(log-msg 'WARN "This is a warning.")

 This will get to file AND stderr
(log-msg 'CRITICAL "ERROR message!!!")

(shutdown-logging)

Usage

<log-handler>
[Class]

This is the base class for all of the log handlers, and encompasses the basic functionality that all handlers are expected to have. Keyword arguments recognized by the <log-handler> at creation time are:

#:formatter

This optional parameter must be a function that takes three arguments: the log level, the time (as from current-time), and the log string itself. The function must return a string representing the formatted log.

Here is an example invokation of the default formatter, and what it's output looks like:

 (default-log-formatter 'CRITICAL 
                       (current-time) 
                       "The servers are melting!")
==> "2003/12/29 14:53:02 (CRITICAL): The servers are melting!"
emit-log
[Generic]

emit-log handler str. This method should be implemented for all the handlers. This sends a string to their output media. All level checking and formatting has already been done by accept-log.

accept-log
[Generic]

accept-log handler lvl time str. If lvl is enabled for handler, then str will be formatted and sent to the log via the emit-log method. Formatting is done via the formatting function given at handler's creation time, or by the default if none was given.

This method should not normally need to be overridden by subclasses. This method should not normally be called by users of the logging system. It is only exported so that writers of log handlers can override this behavior.

accept-log (self <log-handler>) (level <top>) (time <top>) (str <top>)
[Method]
<logger>
[Class]

This is the class that aggregates and manages log handlers. It also maintains the global information about which levels of log messages are enabled, and which have been suppressed. Keyword arguments accepted on creation are:

#:handlers

This optional parameter must be a list of objects derived from <log-handler>. Handlers can always be added later via add-handler! calls.

add-handler!
[Generic]

add-handler! lgr handler. Adds handler to lgr's list of handlers. All subsequent logs will be sent through the new handler, as well as any previously registered handlers.

add-handler! (lgr <logger>) (handler <log-handler>)
[Method]
log-msg
[Generic]

log-msg [lgr] lvl arg1 arg2 .... Send a log message made up of the display'ed representation of the given arguments. The log is generated at level lvl, which should be a symbol. If the lvl is disabled, the log message is not generated. Generated log messages are sent through each of lgr's handlers.

If the lgr parameter is omitted, then the default logger is used, if one is set.

As the args are display'ed, a large string is built up. Then, the string is split at newlines and sent through the log handlers as independent log messages. The reason for this behavior is to make output nicer for log handlers that prepend information like pid and timestamps to log statements.

;; logging to default logger, level of WARN
 (log-msg 'WARN "Warning! " x " is bigger than " y "!!!")

;; looking up a logger and logging to it
 (let ((l (lookup-logger "main")))
     (log-msg l 'CRITICAL "FAILURE TO COMMUNICATE!")
     (log-msg l 'CRITICAL "ABORTING NOW"))
log-msg (lgr <logger>) (lvl <top>) (objs <top>)...
[Method]
log-msg (lvl <symbol>) (objs <top>)...
[Method]
set-default-logger! lgr
[Function]

Sets the given logger, lgr, as the default for logging methods where a logger is not given. lgr can be an instance of <logger>, a string that has been registered via register-logger!, or #f to remove the default logger.

With this mechanism, most applications will never need to worry about logger registration or lookup.

;; example 1
 (set-default-logger! "main")  ;; look up "main" logger and make it the default

;; example 2
 (define lgr (make  <logger>))
 (add-handler! lgr 
              (make <port-handler>
                    #:port (current-error-port)))
 (set-default-logger! lgr)
 (log-msg 'CRITICAL "This is a message to the default logger!!!")
 (log-msg lgr 'CRITICAL "This is a message to a specific logger!!!")
register-logger! str lgr
[Function]

Makes lgr accessible from other parts of the program by a name given in str. str should be a string, and lgr should be an instance of class <logger>.

 (define main-log  (make <logger>))
 (define corba-log (make <logger>))
 (register-logger! "main" main-log)
 (register-logger! "corba" corba-log)

;; in a completely different part of the program....
 (log-msg (lookup-logger "corba") 'WARNING "This is a corba warning.")
lookup-logger str
[Function]

Looks up an instance of class <logger> by the name given in str. The string should have already been registered via a call to register-logger!.

enable-log-level! lgr lvl
[Function]

Enables a specific logging level given by the symbol lvl, such that messages at that level will be sent to the log handlers. lgr can be of type <logger> or <log-handler>.

Note that any levels that are neither enabled or disabled are treated as enabled by the logging system. This is so that misspelt level names do not cause a logging blackout.

disable-log-level! lgr lvl
[Function]

Disables a specific logging level, such that messages at that level will not be sent to the log handlers. lgr can be of type <logger> or <log-handler>.

Note that any levels that are neither enabled or disabled are treated as enabled by the logging system. This is so that misspelt level names do not cause a logging blackout.

flush-log
[Generic]

flush-log handler. Tells the handler to output any log statements it may have buffered up. Handlers for which a flush operation doesn't make sense can choose not to implement this method. The default implementation just returns #t.

flush-log (lgr <logger>)
[Method]
flush-log
[Method]
flush-log (lh <log-handler>)
[Method]
open-log!
[Generic]

open-log! handler. Tells the handler to open its log. Handlers for which an open operation doesn't make sense can choose not to implement this method. The default implementation just returns #t.

open-log!
[Method]
open-log! (lgr <logger>)
[Method]
open-log! (lh <log-handler>)
[Method]
close-log!
[Generic]

open-log! handler. Tells the handler to close its log. Handlers for which a close operation doesn't make sense can choose not to implement this method. The default implementation just returns #t.

close-log!
[Method]
close-log! (lgr <logger>)
[Method]
close-log! (lh <log-handler>)
[Method]