356 lines
10 KiB
Racket
356 lines
10 KiB
Racket
#lang scribble/manual
|
|
|
|
@(require (for-label racket/base
|
|
racket/contract
|
|
simple-log))
|
|
|
|
@title{simple-log}
|
|
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
|
|
|
|
@defmodule[simple-log]
|
|
|
|
A small logging layer on top of Racket's logger system. A log definition
|
|
creates a logger and convenience procedures for the standard log levels.
|
|
Messages are formatted with @racket[format], timestamped, and dispatched
|
|
asynchronously to one or more registered destinations.
|
|
|
|
@section{Quick start}
|
|
|
|
@racketblock[
|
|
(require simple-log)
|
|
|
|
(sl-def-log example)
|
|
(sl-log-to-display)
|
|
|
|
(info-example "Started with ~a items" 3)
|
|
(warn-example "This is only an example")
|
|
]
|
|
|
|
A log line has the following format:
|
|
|
|
@verbatim{example:info:2026-08-03T10:30:00:Started with 3 items}
|
|
|
|
@section{Defining a logger}
|
|
|
|
@defform*[((sl-def-log id)
|
|
(sl-def-log id prefix)
|
|
(sl-def-log id prefix parent))]{
|
|
|
|
Defines a logger with topic @racket['id]. The generated procedure names use
|
|
@racket[id] as their prefix unless an explicit @racket[prefix] is supplied.
|
|
|
|
For example:
|
|
|
|
@racketblock[
|
|
(sl-def-log player)
|
|
]
|
|
|
|
creates @racket[dbg-player], @racket[info-player], @racket[warn-player],
|
|
@racket[err-player], @racket[fatal-player], and @racket[sync-log-player].
|
|
|
|
With an explicit prefix:
|
|
|
|
@racketblock[
|
|
(sl-def-log media-renderer renderer)
|
|
]
|
|
|
|
creates the same procedures with @racket[renderer] in their names, while the
|
|
log topic remains @racket['media-renderer].
|
|
|
|
If @racket[parent] is omitted, @racket[#f] is used as the parent logger.
|
|
}
|
|
|
|
@section{Log destinations}
|
|
|
|
@defform[(sl-log-to name callback)]{
|
|
|
|
Registers @racket[callback] under the symbolic name derived from
|
|
@racket[name]. The name is an identifier, not a runtime value. Registering
|
|
another callback with the same name replaces the previous callback.
|
|
|
|
The callback is invoked as:
|
|
|
|
@racketblock[
|
|
(callback topic level timestamp message)
|
|
]
|
|
|
|
The arguments are a topic symbol, a level symbol, an ISO-like timestamp in
|
|
@litchar{YYYY-MM-DDTHH:MM:SS} form, and the formatted message string.
|
|
|
|
For example:
|
|
|
|
@racketblock[
|
|
(sl-log-to collect-errors
|
|
(lambda (topic level timestamp message)
|
|
(when (memq level '(error fatal))
|
|
(displayln (list timestamp topic message)))))
|
|
]
|
|
}
|
|
|
|
@defproc[(sl-log-to-display) void?]{
|
|
|
|
Registers a destination that writes log lines to the current output port with
|
|
@racket[displayln]. Calling the procedure again replaces the existing display
|
|
destination.
|
|
}
|
|
|
|
@defproc[(sl-log-to-file [filename path-string?]) void?]{
|
|
|
|
Registers a destination that writes log lines to @racket[filename]. The file is
|
|
opened with @racket['replace], and the output is flushed after every line.
|
|
Calling the procedure again replaces the existing file destination.
|
|
}
|
|
|
|
@defproc[(sl-log-to-file&display [filename path-string?]) void?]{
|
|
|
|
Enables both the display and file destinations.
|
|
}
|
|
|
|
@defproc[(sl-log-to-store [max-length exact-nonnegative-integer? 1000]) any/c]{
|
|
|
|
Creates an in-memory log store, registers it as the current store destination,
|
|
and returns it. At most @racket[max-length] entries are retained; older entries
|
|
are removed when the limit is exceeded.
|
|
|
|
Only one destination named @racket[store] is active. A later call to
|
|
@racket[sl-log-to-store] replaces the previous store destination, but does not
|
|
modify the previously returned store.
|
|
|
|
Logging is asynchronous. Use the generated synchronization procedure before
|
|
reading the store when all previously submitted messages must be present.
|
|
|
|
@racketblock[
|
|
(sl-def-log worker)
|
|
(define logs (sl-log-to-store 200))
|
|
|
|
(info-worker "Starting job ~a" 42)
|
|
(warn-worker "Job ~a is slow" 42)
|
|
(sync-log-worker)
|
|
|
|
(sl-store->display logs)
|
|
]
|
|
}
|
|
|
|
@section{Log level}
|
|
|
|
@defproc[(sl-set-log-level
|
|
[level (or/c 'debug 'dbg
|
|
'info
|
|
'warning 'warn
|
|
'error 'err
|
|
'fatal)])
|
|
symbol?]{
|
|
|
|
Sets the module-wide minimum log level and returns its normalized symbol.
|
|
The aliases @racket['dbg], @racket['warn], and @racket['err] are normalized to
|
|
@racket['debug], @racket['warning], and @racket['error]. Other values raise an
|
|
exception.
|
|
}
|
|
|
|
@defproc[(sl-log-level) symbol?]{
|
|
|
|
Returns the current module-wide minimum log level. The default is
|
|
@racket['debug].
|
|
}
|
|
|
|
@section{Synchronization}
|
|
|
|
@defproc[(sl-sync [logger logger?] [topic symbol?]) void?]{
|
|
|
|
Submits a synchronization event to @racket[logger] and waits until the
|
|
simple-log receiver has processed it. Normally this lower-level procedure is
|
|
not needed directly; use the @racket[sync-log-prefix] procedure generated by
|
|
@racket[sl-def-log].
|
|
}
|
|
|
|
Synchronization is useful before inspecting an in-memory store or before a
|
|
program exits immediately after its final log message.
|
|
|
|
@section{In-memory stores}
|
|
|
|
A store contains log entries in chronological order. Every entry consists of:
|
|
|
|
@racketblock[
|
|
(list topic level timestamp message)
|
|
]
|
|
|
|
The filtering procedures return new stores and leave the original store
|
|
unchanged. The new store has the same maximum length as the original.
|
|
|
|
@defproc[(make-sl-store [max-length exact-nonnegative-integer? 1000]) any/c]{
|
|
|
|
Creates an empty store without registering it as a log destination. Most code
|
|
uses @racket[sl-log-to-store] instead.
|
|
}
|
|
|
|
@defproc[(sl-store-enqueue! [store any/c]
|
|
[topic symbol?]
|
|
[level (or/c 'debug 'dbg
|
|
'info
|
|
'warning 'warn
|
|
'error 'err
|
|
'fatal)]
|
|
[timestamp string?]
|
|
[message string?])
|
|
void?]{
|
|
|
|
Adds an entry to @racket[store]. When the maximum length is exceeded, entries
|
|
are removed from the beginning of the store.
|
|
}
|
|
|
|
@defproc[(sl-store-length [store any/c]) exact-nonnegative-integer?]{
|
|
|
|
Returns the current number of entries in @racket[store].
|
|
}
|
|
|
|
@defproc[(sl-store-max-length [store any/c]) exact-nonnegative-integer?]{
|
|
|
|
Returns the configured maximum number of entries in @racket[store].
|
|
}
|
|
|
|
@defproc[(sl-store->list [store any/c]) list?]{
|
|
|
|
Returns the entries as a list in chronological order. Each entry is a list of
|
|
topic, level, timestamp, and message.
|
|
}
|
|
|
|
@defproc[(sl-store->display [store any/c]) void?]{
|
|
|
|
Writes all entries to the current output port in the same colon-separated
|
|
format used by the display and file destinations.
|
|
}
|
|
|
|
@defproc[(sl-store-grep-message [store any/c] [regexp regexp?] [#:invert? invert? boolean? #f]) any/c]{
|
|
|
|
Returns a store containing entries whose message matches @racket[regexp].
|
|
When @racket[invert?] is true, entries matching the filter are excluded instead.
|
|
}
|
|
|
|
@defproc[(sl-store-grep-topic [store any/c]
|
|
[topic-filter
|
|
(or/c symbol?
|
|
regexp?
|
|
(listof (or/c symbol? regexp?)))]
|
|
[#:invert? invert? boolean? #f])
|
|
any/c]{
|
|
|
|
With a symbol, returns entries whose topic is exactly that symbol. With a
|
|
regular expression, matches against the string form of the topic. A list
|
|
selects entries matching any listed symbol or regular expression. When
|
|
@racket[invert?] is true, entries matching the filter are excluded instead.
|
|
|
|
@racketblock[
|
|
(sl-store-grep-topic logs '(webview webview-backend))
|
|
(sl-store-grep-topic logs 'webview-backend #:invert? #t)
|
|
]
|
|
}
|
|
|
|
@defproc[(sl-store-grep-level [store any/c]
|
|
[level-or-regexp
|
|
(or/c regexp?
|
|
'debug 'dbg
|
|
'info
|
|
'warning 'warn
|
|
'error 'err
|
|
'fatal)])
|
|
any/c]{
|
|
|
|
Returns entries at the selected level or a more severe level. A regular
|
|
expression is matched against the available level names and selects the first
|
|
matching level.
|
|
|
|
@racketblock[
|
|
(sl-store-grep-level logs 'warning)
|
|
(sl-store-grep-level logs #rx"err(or)?")
|
|
]
|
|
}
|
|
|
|
@defproc[(sl-store-grep [store any/c]
|
|
[value (or/c symbol? regexp?)]
|
|
[#:invert? invert? boolean? #f])
|
|
any/c]{
|
|
|
|
Performs a combined search.
|
|
|
|
When @racket[value] is a log-level symbol, the result contains entries at that
|
|
level or a more severe level. Any other symbol is treated as an exact topic.
|
|
A regular expression is matched against both topic and message. If that same
|
|
regular expression also identifies a log level, entries below that level are
|
|
excluded. When @racket[invert?] is true, matching entries are excluded, similar
|
|
to @tt{grep -v}.
|
|
}
|
|
|
|
@defproc[(sl-store-tail [store any/c]
|
|
[count exact-nonnegative-integer?])
|
|
any/c]{
|
|
|
|
Returns a store containing the last @racket[count] entries. If
|
|
@racket[count] is greater than the store length, all entries are returned.
|
|
}
|
|
|
|
@section{Store examples}
|
|
|
|
@racketblock[
|
|
(sl-def-log player)
|
|
(define logs (sl-log-to-store 500))
|
|
|
|
(info-player "Opening ~a" "album.flac")
|
|
(warn-player "No duration available")
|
|
(err-player "Renderer returned status ~a" 701)
|
|
(sync-log-player)
|
|
|
|
(define warnings-and-errors
|
|
(sl-store-grep-level logs 'warning))
|
|
|
|
(define renderer-errors
|
|
(sl-store-grep
|
|
(sl-store-grep-topic logs 'player)
|
|
#rx"renderer|status"))
|
|
|
|
(sl-store->display (sl-store-tail warnings-and-errors 20))
|
|
]
|
|
|
|
@section{Generated procedures}
|
|
|
|
For a definition such as:
|
|
|
|
@racketblock[
|
|
(sl-def-log my-module)
|
|
]
|
|
|
|
simple-log creates the following procedures:
|
|
|
|
@defproc[#:link-target? #f
|
|
(dbg-my-module [message string?] [argument any/c] ...) void?]{
|
|
Emits a debug message.
|
|
}
|
|
|
|
@defproc[#:link-target? #f
|
|
(info-my-module [message string?] [argument any/c] ...) void?]{
|
|
Emits an informational message.
|
|
}
|
|
|
|
@defproc[#:link-target? #f
|
|
(warn-my-module [message string?] [argument any/c] ...) void?]{
|
|
Emits a warning message.
|
|
}
|
|
|
|
@defproc[#:link-target? #f
|
|
(err-my-module [message string?] [argument any/c] ...) void?]{
|
|
Emits an error message.
|
|
}
|
|
|
|
@defproc[#:link-target? #f
|
|
(fatal-my-module [message string?] [argument any/c] ...) void?]{
|
|
Emits a fatal message.
|
|
}
|
|
|
|
@defproc[#:link-target? #f
|
|
(sync-log-my-module) void?]{
|
|
Waits until the simple-log receiver has processed all earlier messages from
|
|
this logger.
|
|
}
|
|
|
|
The message procedures pass @racket[message] and @racket[argument] values to
|
|
@racket[format]. Log delivery to the registered destinations is asynchronous.
|