simple log to store.
This commit is contained in:
@@ -1,3 +1,26 @@
|
||||
# simple-log
|
||||
|
||||
Extends on racket logging to make it more simple
|
||||
`simple-log` is a small convenience layer on top of Racket's logging system.
|
||||
It provides generated procedures for the standard log levels and supports
|
||||
logging to the display, a file, custom callbacks, and a filterable in-memory
|
||||
store.
|
||||
|
||||
```racket
|
||||
#lang racket
|
||||
|
||||
(require simple-log)
|
||||
|
||||
(sl-def-log example)
|
||||
(define logs (sl-log-to-store 1000))
|
||||
(sl-log-to-display)
|
||||
|
||||
(info-example "Started with ~a items" 3)
|
||||
(warn-example "An example warning")
|
||||
(sync-log-example)
|
||||
|
||||
(sl-store->display
|
||||
(sl-store-grep-level logs 'warning))
|
||||
```
|
||||
|
||||
The Scribble manual documents logger definitions, destinations, log levels,
|
||||
synchronization, and all in-memory store operations.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#lang info
|
||||
|
||||
(define pkg-authors '(hnmdijkema))
|
||||
(define version "0.1.2")
|
||||
(define license 'Apache-2.0)
|
||||
(define version "0.2.1")
|
||||
(define license 'MIT)
|
||||
(define collection "simple-log")
|
||||
(define pkg-desc "simple-log - A simple wrapper around the racket logging system")
|
||||
|
||||
|
||||
+1
-1
@@ -82,7 +82,7 @@
|
||||
(if (> (queue-length queue) ml)
|
||||
(begin
|
||||
(dequeue! queue)
|
||||
(set-sl-store-length! store (- sl-store-length store) 1)
|
||||
(set-sl-store-length! store (- sl-store-length store 1))
|
||||
(loop))
|
||||
#t))
|
||||
)
|
||||
|
||||
+277
-93
@@ -1,157 +1,341 @@
|
||||
#lang scribble/manual
|
||||
|
||||
@(require (for-label racket/base))
|
||||
@(require (for-label racket/base
|
||||
racket/contract
|
||||
simple-log))
|
||||
|
||||
@title{simple-log}
|
||||
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
|
||||
|
||||
A small logging layer on top of Racket’s logger system. A log definition
|
||||
creates a logger plus five convenience procedures. Messages are formatted
|
||||
with @racket[format], timestamped, and dispatched asynchronously to the
|
||||
registered callbacks.
|
||||
@defmodule[simple-log]
|
||||
|
||||
@section{API}
|
||||
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.
|
||||
|
||||
@defmodule["simple-log"]
|
||||
|
||||
@defform*[((sl-def-log id)
|
||||
(sl-def-log id name)
|
||||
(sl-def-log id name parent))]{
|
||||
|
||||
Defines a logger with topic @racket['id] and creates:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{@racket[dbg-id]}
|
||||
@item{@racket[info-id]}
|
||||
@item{@racket[warn-id]}
|
||||
@item{@racket[err-id]}
|
||||
@item{@racket[fatal-id]}
|
||||
@item{@racket[sync-id]}
|
||||
]
|
||||
|
||||
Note. If name is given, id @racket[dbg-prefix], etc. will be generated instead of @racket[dbg-id], etc.
|
||||
|
||||
Each procedure has shape:
|
||||
@section{Quick start}
|
||||
|
||||
@racketblock[
|
||||
(proc msg arg ...)
|
||||
(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")
|
||||
]
|
||||
|
||||
The message is formatted via @racket[format] and emitted with a timestamp
|
||||
(@litchar{YYYY-MM-DDTHH:MM:SS}) and topic @racket['id].
|
||||
A log line has the following format:
|
||||
|
||||
If @racket[parent] is omitted, the @racket[#f] is used as "parent logger".
|
||||
@verbatim{example:info:2026-08-03T10:30:00:Started with 3 items}
|
||||
|
||||
A background thread is started that receives log events and forwards them
|
||||
to the registered callbacks.
|
||||
@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].
|
||||
@racket[name]. The name is an identifier, not a runtime value. Registering
|
||||
another callback with the same name replaces the previous callback.
|
||||
|
||||
@racket[name] is an identifier (not a runtime value). It is converted at
|
||||
macro expansion time to a symbol and used as key in the callback registry.
|
||||
|
||||
Invocation shape:
|
||||
The callback is invoked as:
|
||||
|
||||
@racketblock[
|
||||
(callback topic level timestamp message)
|
||||
]
|
||||
|
||||
with:
|
||||
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.
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{@racket[topic] — logger topic (symbol)}
|
||||
@item{@racket[level] — level (symbol)}
|
||||
@item{@racket[timestamp] — @litchar{YYYY-MM-DDTHH:MM:SS}}
|
||||
@item{@racket[message] — formatted string}
|
||||
]
|
||||
|
||||
An existing callback with the same name is replaced.
|
||||
}
|
||||
|
||||
@defproc[(sl-log-to-file [filename path-string?]) void?]{
|
||||
|
||||
Registers a callback that writes log lines to @racket[filename]. The file
|
||||
is opened with @racket['replace].
|
||||
|
||||
Format:
|
||||
For example:
|
||||
|
||||
@racketblock[
|
||||
"<topic>:<level>:<timestamp>:<message>"
|
||||
(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 callback that writes log lines to the current output port
|
||||
using @racket[displayln].
|
||||
Registers a destination that writes log lines to the current output port with
|
||||
@racket[displayln]. Calling the procedure again replaces the existing display
|
||||
destination.
|
||||
}
|
||||
|
||||
Format:
|
||||
@defproc[(sl-log-to-file [filename path-string?]) void?]{
|
||||
|
||||
@racketblock[
|
||||
"<topic>:<level>:<timestamp>:<message>"
|
||||
]
|
||||
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?]{
|
||||
|
||||
Equivalent to combining @racket[sl-log-to-display] and
|
||||
@racket[sl-log-to-file].
|
||||
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
|
||||
[l (or/c 'debug 'dbg
|
||||
[level (or/c 'debug 'dbg
|
||||
'info
|
||||
'warning 'warn
|
||||
'error 'err
|
||||
'fatal)])
|
||||
symbol?]{
|
||||
|
||||
Sets the module-wide log level and returns the normalized symbol.
|
||||
|
||||
Aliases:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{@racket['dbg] → @racket['debug]}
|
||||
@item{@racket['warn] → @racket['warning]}
|
||||
@item{@racket['err] → @racket['error]}
|
||||
]
|
||||
|
||||
Other values raise an exception.
|
||||
|
||||
Note: this value is stored globally. The receiver installed by
|
||||
@racket[sl-def-log] itself operates at level @racket['debug].
|
||||
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 log level (default @racket['debug]).
|
||||
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?]) any/c]{
|
||||
|
||||
Returns a store containing entries whose message matches @racket[regexp].
|
||||
}
|
||||
|
||||
@defproc[(sl-store-grep-topic [store any/c]
|
||||
[topic-or-regexp (or/c symbol? regexp?)])
|
||||
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.
|
||||
}
|
||||
|
||||
@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?)])
|
||||
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.
|
||||
}
|
||||
|
||||
@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}
|
||||
|
||||
A call to @racket[sl-def-log] creates five procedures. For example:
|
||||
For a definition such as:
|
||||
|
||||
@racketblock[
|
||||
(sl-def-log my-module)
|
||||
]
|
||||
|
||||
creates:
|
||||
simple-log creates the following procedures:
|
||||
|
||||
@defproc[#:link-target? #f
|
||||
(dbg-my-module [msg string?] [arg any/c] ...) void?]{Debug log.}
|
||||
@defproc[#:link-target? #f
|
||||
(info-my-module [msg string?] [arg any/c] ...) void?]{Info log.}
|
||||
@defproc[#:link-target? #f
|
||||
(warn-my-module [msg string?] [arg any/c] ...) void?]{Warning log.}
|
||||
@defproc[#:link-target? #f
|
||||
(err-my-module [msg string?] [arg any/c] ...) void?]{Error log.}
|
||||
@defproc[#:link-target? #f
|
||||
(fatal-my-module [msg string?] [arg any/c] ...) void?]{Fatal log.}
|
||||
@defproc[#:link-target? #f
|
||||
(sync-log-my-module) void?]{Puts a sync message to the logger and waits for the receiver until it logs this synchronization event.}
|
||||
(dbg-my-module [message string?] [argument any/c] ...) void?]{
|
||||
Emits a debug message.
|
||||
}
|
||||
|
||||
All, except for @racket[sync-log-my-module] use @racket[format] and emit asynchronously.
|
||||
@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.
|
||||
|
||||
Reference in New Issue
Block a user