Getting Started¶
Prerequisites
cl-log-kit targets SBCL and requires ASDF
3.3.1 or newer (SBCL includes a recent-enough ASDF). The
shipped cl-log-kit system also depends on three nerima-lisp toolkit
packages — cl-date-kit, cl-concurrent-kit, and cl-host-kit — loaded
at runtime; see Compatibility for what each
is used for.
Installing¶
Pick the path that matches how you use the library.
Clone the repository somewhere ASDF can find it, then load the system:
ASDF finds the system if the clone is on CL_SOURCE_REGISTRY or under a
directory ASDF already searches (e.g. ~/common-lisp/ or
~/.local/share/common-lisp/source/).
Build the packaged ASDF system directly:
The build output's root is the ASDF source tree — result/cl-log-kit.asd
sits at the top level. Reference cl-log-kit from your own flake as an
input and append its store path to CL_SOURCE_REGISTRY:
or, from the command line:
Clone the repository, then work inside the reproducible dev shell, which wires up the test-only dependencies automatically:
git clone https://github.com/nerima-lisp/cl-log-kit.git
cd cl-log-kit
nix develop # SBCL + CL_SOURCE_REGISTRY for cl-weave
# and cl-json-kit
nix run .#test # run the test suite with a real timeout
nix flake check # every CI entrypoint
run-tests.lisp and run-coverage.lisp also work
without Nix — see Development for the direct
CL_SOURCE_REGISTRY invocation. cl-log-kit/test requires cl-weave
1.3.0 or newer and cl-json-kit 1.2.0 or newer; the flake pins both, so
only a hand-managed CL_SOURCE_REGISTRY can point at an older checkout.
Runtime Support¶
cl-log-kit is developed and tested against SBCL, and its runtime
dependencies (cl-date-kit, cl-concurrent-kit, cl-host-kit) are
themselves SBCL-only, so it should load cleanly on any ASDF 3.3.1+
SBCL setup; SBCL is the only implementation the test suite and CI pipeline
exercise. See Compatibility for the platforms
the flake builds and the stability promise attached to the exported surface.
Package and Naming¶
Load the system, then :use the log-kit package:
(defpackage #:my-app
(:use #:cl #:log-kit)
(:shadowing-import-from #:log-kit #:log))
(in-package #:my-app)
log-kit shadows the common-lisp log symbol for its own log macro, the
arbitrary-level general form behind log-debug/log-info/etc. That symbol
is internal, so :use-ing log-kit alone leaves cl:log (the logarithm)
accessible; the :shadowing-import-from line above is what makes log-kit's
macro available under that name instead, and can be dropped if you only use
the fixed-level macros.
Your first logger¶
(defparameter *logger*
(make-logger
:name "api"
:handler (make-instance 'text-handler)
:level +level-info+
:fields '(:service "orders")))
(log-info *logger* "server started" :port 8080)
;; ts=... level=INFO logger="api" msg="server started" field."port"="8080" field."service"="orders"
make-logger accepts a :name, a :handler instance, a minimum :level
(the default is +level-info+), and a base :fields property list shared by
every record the logger emits.
Switching to JSON¶
Swap the handler — nothing else about the call sites changes:
(defparameter *logger*
(make-logger
:name "api"
:handler (make-instance 'json-handler)
:level +level-info+
:fields '(:service "orders")))
(log-info *logger* "server started" :port 8080)
;; {"time":...,"level":"INFO","logger":"api","message":"server started","fields":{"port":8080,"service":"orders"}}
See Handlers for the full text-handler /
json-handler wire formats and how to compose multiple handlers.
Deriving a scoped logger¶
logger-with returns a new logger with additional fields; call-site fields
still override logger fields with the same canonical name:
(defparameter *request-logger*
(logger-with *logger* :request-id "abc123"))
(log-warn *request-logger* "slow response" :duration-ms 842)
See Logger Derivation and Context for derive-logger,
logger-child, and dynamically scoped context via with-log-context.
Using a default logger¶
Explicit-logger and default-logger calls are separate macro families.
log-debug / log-info / log-warn / log-error / log-fatal always
evaluate their first argument as the logger, so (log-info "server started"
:port 8080) signals a type-error rather than falling back to
*default-logger*. Set *default-logger* once, then use the log-default-*
macros anywhere without threading a logger through every call site:
(set-default-logger
(make-logger :handler (make-instance 'text-handler)))
(log-default-error "request failed" :reason "timeout")
;; ts=... level=ERROR logger="root" msg="request failed" field."reason"="timeout"
with-default-logger scopes a different default dynamically, without
mutating the process-wide default:
log-default is the generic form behind log-default-debug /
log-default-info / … — reach for it when the level is itself a variable
instead of a compile-time constant:
Next steps¶
- Levels — severities, thresholds, and level-gated evaluation.
- Fields — structured field rules and resource limits.
- Logging Conditions — logging a caught Lisp condition.
- Log Spans — timing an operation with
with-log-span.