simple log to store.
This commit is contained in:
@@ -1,3 +1,26 @@
|
|||||||
# simple-log
|
# 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
|
#lang info
|
||||||
|
|
||||||
(define pkg-authors '(hnmdijkema))
|
(define pkg-authors '(hnmdijkema))
|
||||||
(define version "0.1.2")
|
(define version "0.2.1")
|
||||||
(define license 'Apache-2.0)
|
(define license 'MIT)
|
||||||
(define collection "simple-log")
|
(define collection "simple-log")
|
||||||
(define pkg-desc "simple-log - A simple wrapper around the racket logging system")
|
(define pkg-desc "simple-log - A simple wrapper around the racket logging system")
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -82,7 +82,7 @@
|
|||||||
(if (> (queue-length queue) ml)
|
(if (> (queue-length queue) ml)
|
||||||
(begin
|
(begin
|
||||||
(dequeue! queue)
|
(dequeue! queue)
|
||||||
(set-sl-store-length! store (- sl-store-length store) 1)
|
(set-sl-store-length! store (- sl-store-length store 1))
|
||||||
(loop))
|
(loop))
|
||||||
#t))
|
#t))
|
||||||
)
|
)
|
||||||
|
|||||||
+277
-93
@@ -1,157 +1,341 @@
|
|||||||
#lang scribble/manual
|
#lang scribble/manual
|
||||||
|
|
||||||
@(require (for-label racket/base))
|
@(require (for-label racket/base
|
||||||
|
racket/contract
|
||||||
|
simple-log))
|
||||||
|
|
||||||
@title{simple-log}
|
@title{simple-log}
|
||||||
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
|
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
|
||||||
|
|
||||||
A small logging layer on top of Racket’s logger system. A log definition
|
@defmodule[simple-log]
|
||||||
creates a logger plus five convenience procedures. Messages are formatted
|
|
||||||
with @racket[format], timestamped, and dispatched asynchronously to the
|
|
||||||
registered callbacks.
|
|
||||||
|
|
||||||
@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"]
|
@section{Quick start}
|
||||||
|
|
||||||
@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:
|
|
||||||
|
|
||||||
@racketblock[
|
@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
|
A log line has the following format:
|
||||||
(@litchar{YYYY-MM-DDTHH:MM:SS}) and topic @racket['id].
|
|
||||||
|
|
||||||
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
|
@section{Defining a logger}
|
||||||
to the registered callbacks.
|
|
||||||
|
@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)]{
|
@defform[(sl-log-to name callback)]{
|
||||||
|
|
||||||
Registers @racket[callback] under the symbolic name derived from
|
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
|
The callback is invoked as:
|
||||||
macro expansion time to a symbol and used as key in the callback registry.
|
|
||||||
|
|
||||||
Invocation shape:
|
|
||||||
|
|
||||||
@racketblock[
|
@racketblock[
|
||||||
(callback topic level timestamp message)
|
(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
|
For example:
|
||||||
@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:
|
|
||||||
|
|
||||||
@racketblock[
|
@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?]{
|
@defproc[(sl-log-to-display) void?]{
|
||||||
|
|
||||||
Registers a callback that writes log lines to the current output port
|
Registers a destination that writes log lines to the current output port with
|
||||||
using @racket[displayln].
|
@racket[displayln]. Calling the procedure again replaces the existing display
|
||||||
|
destination.
|
||||||
|
}
|
||||||
|
|
||||||
Format:
|
@defproc[(sl-log-to-file [filename path-string?]) void?]{
|
||||||
|
|
||||||
@racketblock[
|
Registers a destination that writes log lines to @racket[filename]. The file is
|
||||||
"<topic>:<level>:<timestamp>:<message>"
|
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?]{
|
@defproc[(sl-log-to-file&display [filename path-string?]) void?]{
|
||||||
|
|
||||||
Equivalent to combining @racket[sl-log-to-display] and
|
Enables both the display and file destinations.
|
||||||
@racket[sl-log-to-file].
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
@defproc[(sl-set-log-level
|
||||||
[l (or/c 'debug 'dbg
|
[level (or/c 'debug 'dbg
|
||||||
'info
|
'info
|
||||||
'warning 'warn
|
'warning 'warn
|
||||||
'error 'err
|
'error 'err
|
||||||
'fatal)])
|
'fatal)])
|
||||||
symbol?]{
|
symbol?]{
|
||||||
|
|
||||||
Sets the module-wide log level and returns the normalized 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
|
||||||
Aliases:
|
@racket['debug], @racket['warning], and @racket['error]. Other values raise an
|
||||||
|
exception.
|
||||||
@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].
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@defproc[(sl-log-level) symbol?]{
|
@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}
|
@section{Generated procedures}
|
||||||
|
|
||||||
A call to @racket[sl-def-log] creates five procedures. For example:
|
For a definition such as:
|
||||||
|
|
||||||
@racketblock[
|
@racketblock[
|
||||||
(sl-def-log my-module)
|
(sl-def-log my-module)
|
||||||
]
|
]
|
||||||
|
|
||||||
creates:
|
simple-log creates the following procedures:
|
||||||
|
|
||||||
@defproc[#:link-target? #f
|
@defproc[#:link-target? #f
|
||||||
(dbg-my-module [msg string?] [arg any/c] ...) void?]{Debug log.}
|
(dbg-my-module [message string?] [argument any/c] ...) void?]{
|
||||||
@defproc[#:link-target? #f
|
Emits a debug message.
|
||||||
(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.}
|
|
||||||
|
|
||||||
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