(util logging) - Logging utilities

Library (util logging)

This library provides logging utilities.

(util logger) provides logger and appender style logging utilility. Loggers determine loging level and appenders determin how to write logs. This example shows the concept.

(import (rnrs) (util logging))

;; Appenders
(define console-appender (make-appender "[~w5] ~l ~m"))
(define file-appender
  (make-file-appender "[~w5] ~l ~m" "useful.log"))

;; Logger
(define logger (make-logger +debug-level+ console-appender file-appender))

;; This won't be logged since the logger level is debug.
(trace-log logger "trace log message")
(debug-log logger "debug log message")

;; If the logging requires heavy process, then it's better to
;; check the level
(when (logger-info? logger)
  (let ((message (construct-message)))
    (info-log logger message)))

;; stop logging
(terminate-logger! logger)

Loggers

Loggers contains collection of appenders and level of logging. Once a logger is created, appenders and threshold can't be modified.

Record Type <logger>
Function logger? obj

Basic logger.

Record type <logger> is a base type of loggers.

logger? returns #t if obj is a logger otherwise #f.

make-logger creates a logger whose threshold is threshold and appenders are appenders. The threshold must be one of the followings:

Logging level constants.

The +trace-level+ is the lowest level (0) and +fatal-level+ is the highest level (5). The values are integers so users can extend the level for both side.

Record Type <async-logger>

Asynchronous logger.

Record type <async-logger> is a type of asynchronous loggers. It inherits the <logger>.

async-logger? returns #t if obj is an asynchronous logger otherwise #f.

make-async-logger creates an asynchronous logger. The arguments are passed to parent protocol.

Asynchronous logger logs message asynchronously. Means it creates a background thread and lets it log. It is useful if a logger has a lot of appenders and logging process may take a lot of time.

To stop background thread, terminate-logger! needs to be called. It is users responsibility to do it.

Logging APIs.

_level_-log procedures log message on logger if logger has its threshold lower than the level.

logger-_level_? procedures check if the logger has threshold lower than level.

Generic Function terminate-logger!

Terminates logging of the given logger.

The method calls appender-finish for all appenders of give logger.

If the logger is an asynchronous logger, then it also stops background thread.

Appenders

Appenders are actual logging mechanisms. Each appender must be responsible how to write a log message and resource management such as log file.

Record Type <appender>

Base appender. This appender emits log messages into current-output-port.

appender? returns #f if obj is an appender otherwise #f.

make-appender creates an appender. The log-format argument must be a string and specifying the format of the log line.

The log-format can contains place holders stating with the character #\~. The followings are the defined place holders:

#\w_date-format_

Puts logging date on this location. The date-format specifies format of the log. It must be a character or string which is surrounded by #\{ #\}. The format is passed to the date->string procedure defined in SRFI-19.

#\l

Puts logging level on this location.

#\m

Puts log message on this location.

#\a[n]

Puts _n_th log argument on this location.

#\a

Puts all log arguments on this location.

The following example shows when log-format is "[~w5] ~l ~m"and logging with info level.

(define logger (make-logger +info-level+ (make-appender "[~w5] ~l ~m")))
(info-log logger "message of the log")
;; [2016-09-06T12:32:06] info message of the log
Record Type <file-appender>

File appender. This is a subtype of <appender>. This appender emits log messages to the file named filename.

file-appender? returns #f if obj is a file appender otherwise #f.

make-file-appender creates a file appender. The log-format is passed to parent protocol. The file creation is done with file options of no-fail, no-truncate and append. Thus if the file exists then it would append the log line.

The given filename will be converted to absolute path so changing directory will not affect the log file location.

Generic Function file-appender-filename

Returns log file name.

Rolling file appender. This is a subtype of <file-appender>. This appender emits log message to the file named filename and if the file size is more than rolling-size, then it renames the old file to indexed file and new log file named filename is created.

rolling-file-appender? returns #f if obj is a rolling file appender otherwise #f.

Daily rolling file appender. This is a subtype of <file-appender>. This appender emits log message to the file named filename and if the date string of last file modified time formatted to _date-pattern_is differ from log time, then the appender rolls the old log file to a backup file. The backup file is concatenation of filename and last modified date.

daily-rolling-file-appender? returns #f if obj is a daily rolling file appender otherwise #f.

Creating appenders

Users would soon face that predefined appenders are not enough or don't satisfy the requirement. For that case, appenders can easily be created.

The following example shows how to create an Email appender.

(import (rnrs)
        (rfc smtp)
        (rfc smtp authentications)
        (util logging)
        (clos user))

(define-record-type (<smtp-appender> make-smtp-appender smtp-appender?)
  (parent <appender>)
  (fields (immutable connection smtp-appender-connection)
          (immutable username smtp-appender-username)
          (immutable password smtp-appender-password))
  (protocol (lambda (p)
              (lambda (format host port username password)
                ((p format)
                 (make-smtp-connection host port) username password)))))

(define-method append-log ((appender <smtp-appender>) log)
  (let ((message (format-log appender log))
        (conn (smtp-appender-connection appender)))
    (guard (e (else (report-error e)))
      (smtp-connect! conn)
      (when (smtp-authentication-required? conn)
        (let* ((methods (smtp-connection-authentication-methods conn))
               (username (smtp-appender-username appender))
               (password (smtp-appender-password appender))
               (method (cond ((memq 'PLAIN methods)
                              (list (smtp-plain-authentication username password)))
                             ((memq 'PASSWORD methods)
                              (let-values ((init&next (smtp-login-authentication username password)))
                                init&next))
                             (else #f))))
          (if methods
              (apply smtp-authenticate! conn method)
              (begin
                (smtp-disconnect! conn)
                (error 'append-log "not supported")))))
      (smtp-send! conn (smtp:mail
                        (smtp:from "Takashi Kato" "ktakashi@ymail.com")
                        (smtp:to "Takashi Kato" "ktakashi@ymail.com")
                        (smtp:subject "Logging with email")
                        message))
      (smtp-disconnect! conn))))

The example is not really useful since it sends only the fixed recipient with fixed format. If you need to use it, you need to modify.

Only what users need to do to create an appender is the followings:

  • Creates a record type inherits <appender>.

  • Specialising append-log method with the above record.

Generic Function append-log appender log

Core process of appending logs.

log is an object of <log> or its subtype. This method should emit logs.

Finishing appender if necessary. This method is called when terminate-logger is called.

Implementation should release resource of the appender.

Generic Function format-log appender log

Formats log object. log is an object of <log> or its subtype.

The method must return a string representation of given log.

The default implementation handles the log format described in the make-logger description.

Returns log format of the given appender.

Record Type <log>
Function log? obj

Default log object.

<log> has 4 fields when, level, message and arguments. By the default creation, they are UTC time object, symbol of log level, log message and a vector of extra logging arguments, respectively.

If you want to create own logger which handles log object differently, then you need to specialise the following generic function with the logger.

Generic Function push-log logger log

Pushes log to appenders of the logger.

Singleton logger

Asynchronous logger or other loggers should sometimes be singleton. This type of thing might need to be done in users responsibility however I think it's common idiom. So the very simple one is added to the library.

This macro defines a logger lookup procedure. If the second form is used then, a registration procedure.

The generated procedures have the following signatures

(@var{lookup} @var{name})

Looks up the logger named name

(@var{register} @var{name} @var{logger})

Registers the given logger with name, name

The registration procedure stores the given logger to hidden storage. If the procedure called the same name argument twice, then the latter logger overwrites the previous one.

clause* must the following form:

loggers is an auxiliary syntax. If the loggersclause is specified, then the macro stores the logger logger-name which is created by make-logger thunk as its predefined loggers.