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 contains collection of appenders and level of logging. Once a logger is created, appenders and threshold can't be modified.
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.
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.
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 are actual logging mechanisms. Each appender must be responsible how to write a log message and resource management such as log file.
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
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.
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.
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.
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.
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.
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.
Pushes log to appenders of the 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
Looks up the logger named name
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 (_logger-name_ _make-logger_) ...)
loggers
is an auxiliary syntax. If the loggers
clause is specified, then the macro stores the logger logger-name which
is created by make-logger thunk as its predefined loggers.