Wiki
Download
Manual
Eggs
API
Tests
Bugs
show
edit
You can edit this page using
wiki syntax
for markup.
Article contents:
[[tags: egg]] == logger Simple structured logging for CHICKEN Scheme with per-module level control. [[toc:]] === Description A lightweight logging library that provides: * Four log levels: {{debug}}, {{info}}, {{warn}}, {{error}} * Per-module log level control * Module-local logging functions via {{logger/install}} macro * Text and JSON output formats * Structured fields (alists) merged into JSON records * Configurable output port * ISO 8601 timestamps * Optional hook for warn/error messages * Atomic record writes, with an optional advisory file lock for multi-process logging * Works on CHICKEN 5 and CHICKEN 6 from the same source === Author Rolando Abarca === Repository [[https://forgejo.rolando.cl/cpm/logger-egg]] === Requirements * [[srfi-1]] * [[srfi-180]] === Documentation ==== Quick Start <enscript highlight="scheme"> (import logger) ;; Basic logging (uses GLOBAL module name) (logger/d "debug message") (logger/i "info message") (logger/w "warning message") (logger/e "error message") ;; Messages can be concatenated (logger/i "user " user-id " logged in") </enscript> Output: 2026-01-18T19:07:51Z [INFO] [GLOBAL] info message ==== Per-Module Logging Use {{logger/install}} inside a module to create local {{d}}, {{i}}, {{w}}, {{e}} functions that automatically tag logs with the module name: <enscript highlight="scheme"> (module my-app (do-stuff) (import scheme chicken.base logger) (logger/install my-app) (define (do-stuff) (i "doing stuff") ;; tagged as [my-app] (d "details..."))) </enscript> Output: 2026-01-18T19:07:51Z [INFO] [my-app] doing stuff 2026-01-18T19:07:51Z [DEBUG] [my-app] details... ==== Structured Fields Any logging function accepts one final alist argument as structured fields. In JSON format, the fields are merged into the record: <enscript highlight="scheme"> (logger/format 'json) (logger/i "user logged in" '((user-id . 123) (ip . "127.0.0.1"))) </enscript> Output: {"ts":1737226071,"level":"info","module":"GLOBAL","message":"user logged in","user-id":123,"ip":"127.0.0.1"} The record is written with {{json-write}} from [[srfi-180]], so field values must be valid srfi-180 JSON values. In particular, JSON arrays must be '''vectors''', not lists: <enscript highlight="scheme"> (logger/i "batch done" `((ids . ,(vector 1 2 3)))) </enscript> The rest argument is treated as structured fields only when there is exactly one extra argument and it is shaped like an alist, so normal message concatenation still works: <enscript highlight="scheme"> (logger/i "user " user-id " logged in") </enscript> In text format, the fields are written after the message, separated by a space: <enscript highlight="scheme"> (logger/format 'text) (logger/i "user logged in" '((user-id . 123) (ip . "127.0.0.1"))) ;; 2026-01-18T19:07:51Z [INFO] [GLOBAL] user logged in ((user-id . 123) (ip . 127.0.0.1)) </enscript> ==== Concurrency Each logging call builds the whole record, including the trailing newline, as a single string and writes it with one {{display}} followed by {{flush-output}}. On Linux and macOS local filesystems, when the destination file is opened in append mode, this keeps short records intact even with concurrent writers. For multi-process safety with longer lines, set {{logger/lock}} to an acquire/release pair. {{logger/make-flock-lock}} builds one from a shared lockfile path: <enscript highlight="scheme"> (import logger chicken.file.posix chicken.bitwise) (let* ((fd (file-open "app.log" (bitwise-ior open/wronly open/creat open/append) #o644)) (out (open-output-file* fd))) (logger/output out) (logger/lock (logger/make-flock-lock "app.log.lock")) (logger/i "this line is safe across processes")) </enscript> Notes: * All participating processes must use the '''same''' lockfile path. * POSIX file locks are advisory: only writers that go through this egg are serialized. Foreign writers ({{echo >> app.log}}, log-rotation tools) can still interleave. * Open the destination file with {{O_APPEND}} for best results. Without it, two processes can race on the file offset. * Locking adds two extra syscalls per record, so leave {{logger/lock}} at {{#f}} (the default) for single-process programs. ==== API Reference ===== Parameters <parameter>logger/level</parameter> Global log level. Messages below this priority are suppressed. Default: {{'debug}}. Valid values: {{'debug}}, {{'info}}, {{'warn}}, {{'error}}, {{'none}} <enscript highlight="scheme"> ;; Set global level to info (hides debug messages) (logger/level 'info) ;; Get current level (logger/level) ; => 'info </enscript> <parameter>logger/output</parameter> Output port for log messages. Default: {{(current-output-port)}}. <enscript highlight="scheme"> (import chicken.file.posix) ;; Log to a file (define log-port (open-output-file "app.log")) (logger/output log-port) ;; Log to stderr (import chicken.port) (logger/output (current-error-port)) </enscript> <parameter>logger/format</parameter> Output format. Default: {{'text}}. ; {{'text}} : Human-readable format: {{TIMESTAMP [LEVEL] [MODULE] message}} ; {{'json}} : JSON format: {{{"ts":N,"level":"...","module":"...","message":"..."}}} <enscript highlight="scheme"> ;; Text format (default) (logger/format 'text) ;; Output: 2026-01-18T19:07:51Z [INFO] [GLOBAL] message ;; JSON format (logger/format 'json) ;; Output: {"ts":1737226071,"level":"info","module":"GLOBAL","message":"message"} </enscript> <parameter>logger/module-levels</parameter> Alist of per-module log levels. Use {{logger/set-module-level!}} to modify. <parameter>logger/error-hook</parameter> Optional procedure called after every {{warn}} or {{error}} record is written. Default: {{#f}} (disabled). The hook is called as {{(hook message level)}}, where {{message}} is a string of the form {{"[MODULE] message"}} (structured fields are not included) and {{level}} is {{1}} for warnings and {{2}} for errors. It is only called for messages that pass the level filter. <enscript highlight="scheme"> (logger/error-hook (lambda (message level) (send-alert (if (= level 2) 'error 'warning) message))) </enscript> <parameter>logger/lock</parameter> Optional cross-process lock used around each write. Default: {{#f}} (no locking). When set, it must be a pair {{(acquire-thunk . release-thunk)}}. The thunks are called around each write with {{dynamic-wind}}. See [[#Concurrency|Concurrency]] and {{logger/make-flock-lock}}. ===== Procedures <procedure>(logger/log module-name level msg . rest)</procedure> Core logging function. Logs a message if {{level}} meets or exceeds the effective level for {{module-name}}. ; {{module-name}} : Symbol identifying the module. ; {{level}} : Log level symbol: {{'debug}}, {{'info}}, {{'warn}}, or {{'error}}. ; {{msg}} : Message string or value. ; {{rest}} : Additional values to concatenate to the message, or a single alist of structured fields (see [[#Structured Fields|Structured Fields]]). <enscript highlight="scheme"> (logger/log 'my-module 'info "Processing item " item-id) (logger/log 'my-module 'info "Processed item" `((item-id . ,item-id))) </enscript> <procedure>(logger/d msg . rest)</procedure> <procedure>(logger/i msg . rest)</procedure> <procedure>(logger/w msg . rest)</procedure> <procedure>(logger/e msg . rest)</procedure> Convenience functions for logging at debug, info, warn, and error levels respectively. Uses {{'GLOBAL}} as the module name. <enscript highlight="scheme"> (logger/d "debug details") (logger/i "informational message") (logger/w "warning!") (logger/e "error occurred: " error-msg) </enscript> <procedure>(logger/set-module-level! module-name level)</procedure> Set the log level for a specific module. This overrides the global {{logger/level}} for that module. <enscript highlight="scheme"> ;; Only show warnings and errors from noisy-module (logger/set-module-level! 'noisy-module 'warn) ;; Show all messages from important-module even if global level is higher (logger/set-module-level! 'important-module 'debug) </enscript> <procedure>(logger/disable-module! module-name)</procedure> Disable all logging for a specific module. Equivalent to {{(logger/set-module-level! module-name 'none)}}. <enscript highlight="scheme"> (logger/disable-module! 'noisy-module) </enscript> <procedure>(logger/make-flock-lock path)</procedure> Returns an {{(acquire-thunk . release-thunk)}} pair, suitable for {{logger/lock}}, that takes an advisory POSIX lock on the file at {{path}}. It uses {{chicken.file.posix}}, which is {{fcntl}}-based on CHICKEN 5 and {{flock}}-based on CHICKEN 6. The lock is released by the OS if the process dies. The lockfile is created if needed, opened on the first acquire, and kept open for the life of the process. <enscript highlight="scheme"> (logger/lock (logger/make-flock-lock "app.log.lock")) </enscript> ===== Macros <syntax>(logger/install module-name)</syntax> Installs module-local logging functions {{d}}, {{i}}, {{w}}, and {{e}} that automatically tag messages with {{module-name}}. Must be called at the top level of a module, after imports. <enscript highlight="scheme"> (module my-component (initialize shutdown) (import scheme chicken.base logger) (logger/install my-component) (define (initialize) (i "initializing...") (d "loading config")) (define (shutdown) (i "shutting down"))) </enscript> ==== Log Levels Levels from lowest to highest priority: ; {{debug}} : Detailed information for debugging. Priority: 0 ; {{info}} : General informational messages. Priority: 1 ; {{warn}} : Warning conditions. Priority: 2 ; {{error}} : Error conditions. Priority: 3 ; {{none}} : Disables logging entirely. Priority: 999 Messages are logged only if their level priority is greater than or equal to the effective level for the module. === Examples ==== Application with Multiple Modules <enscript highlight="scheme"> ;;; main.scm (import scheme chicken.base logger) (import my-db my-http) ;; Set global level to info (logger/level 'info) ;; But show debug messages from database module (logger/set-module-level! 'my-db 'debug) ;; Suppress http module unless errors (logger/set-module-level! 'my-http 'error) (db-connect "localhost") (http-serve 8080) </enscript> <enscript highlight="scheme"> ;;; my-db.scm (module my-db (db-connect) (import scheme chicken.base logger) (logger/install my-db) (define (db-connect host) (i "connecting to " host) (d "using default port 5432") ;; ... (i "connected"))) </enscript> <enscript highlight="scheme"> ;;; my-http.scm (module my-http (http-serve) (import scheme chicken.base logger) (logger/install my-http) (define (http-serve port) (i "starting server on port " port) ;; suppressed (d "debug info") ;; suppressed ;; ... )) </enscript> ==== JSON Logging for Production <enscript highlight="scheme"> (import logger chicken.file.posix) ;; Configure for production (logger/format 'json) (logger/level 'info) (logger/output (open-output-file "/var/log/myapp.log")) (logger/i "application started") ;; {"ts":1737226071,"level":"info","module":"GLOBAL","message":"application started"} </enscript> === License BSD-3-Clause === Version History ; 0.0.7 : CHICKEN 6 support (the same source builds on CHICKEN 5 and 6). Replaced [[medea]] with [[srfi-180]] for JSON output: list-valued structured fields must now be vectors. {{logger/make-flock-lock}} uses {{flock}} on CHICKEN 6 ({{fcntl}} on CHICKEN 5). ; 0.0.6 : Atomic per-record writes; added {{logger/lock}} and {{logger/make-flock-lock}} for multi-process logging. ; 0.0.5 : Structured fields are rendered in text output. ; 0.0.4 : Structured fields (alist) in JSON output. ; 0.0.3 : Added {{logger/error-hook}}. ; 0.0.2 : Initial release.
Description of your changes:
I would like to authenticate
Authentication
Username:
Password:
Spam control
What do you get when you add 24 to 7?