Clausal Prolog — Structured Logging (log module)¶
Overview¶
The log module provides structured logging predicates backed by Python's logging module. It exposes logger creation, leveled log output, handler/formatter configuration, and level management as Clausal Prolog predicates.
Since Python's logging module is the backend, all of Python's handler ecosystem is available — file rotation, syslog, SMTP, JSON formatters, etc.
-import_from(log, [get_logger, info, debug, warning, error, set_level])
main(NAME) <- (
get_logger("myapp", L),
set_level(L, "debug"),
debug(L, f"Starting with name={NAME}"),
info(L, f"Hello, {NAME}!")
)
Or via module import:
Import¶
-import_from(log, [
get_logger, debug, info, warning, error, critical,
set_level, get_level, is_enabled_for, log,
add_handler, remove_handler,
stream_handler, file_handler, set_formatter,
basic_config
])
The module name is log (not logging) to avoid shadowing Python's stdlib logging module.
log levels¶
Levels follow Python's standard hierarchy (ascending severity):
| Level | String | Python constant |
|---|---|---|
| DEBUG | "debug" |
logging.DEBUG (10) |
| INFO | "info" |
logging.INFO (20) |
| WARNING | "warning" (or "warn") |
logging.WARNING (30) |
| ERROR | "error" |
logging.ERROR (40) |
| CRITICAL | "critical" (or "fatal") |
logging.CRITICAL (50) |
Level names are case-insensitive, written as strings (or atoms) when passed to predicates.
Logging predicates¶
All logging predicates always succeed — they are side-effects. A message below the logger's configured level is silently discarded (the predicate still succeeds).
debug/1, debug/2¶
log Msg at DEBUG level. The arity-1 form uses the default "clausal" logger.
info/1, info/2¶
log at INFO level.
warning/1, warning/2¶
log at WARNING level.
error/1, error/2¶
log at ERROR level.
critical/1, critical/2¶
log at CRITICAL level.
log/3¶
log at an arbitrary level. Level is a string ("debug", "info", etc.) or an integer.
Messages and f-strings¶
A message is a string or an atom (an f-string is a string, like "..."). The seam's f-string support means interpolation works naturally:
Logic variables in f-strings are auto-dereferenced at search time.
Logger management¶
get_logger/1, get_logger/2¶
Unify Logger with a Python logging.Logger instance. The arity-1 form returns the default "clausal" logger. Logger objects are opaque — they unify via identity, not structure.
Python's logger hierarchy applies: get_logger("myapp.db", L) creates a child of "myapp". Calling get_logger with the same name always returns the same logger instance.
set_level/2¶
Set the logger's level. Messages below this level will be discarded (but the logging predicate still succeeds). Level is a string or integer.
get_level/2¶
Unify Level with the logger's effective level name, a lowercase atom (e.g. debug, warning) -- the spelling set_level/2 takes. A bound Level may be the atom or the string, in either case ('WARNING', "warning"). A custom level name (logging.addLevelName(5, "TRACE")) is a lowercase atom too (trace); an unnamed numeric level ("Level 15") comes back as a string.
is_enabled_for/2¶
Succeeds if the logger would process a message at Level; fails otherwise. This is the one logging predicate that can fail — useful for guarding expensive message construction:
process(L, DATA) <- (
((is_enabled_for(L, "debug"), debug(L, f"Processing: {DATA}")) or True),
do_work(DATA)
)
Handler management¶
stream_handler/2¶
Create a logging.StreamHandler. StreamName is "stdout" or "stderr".
file_handler/2¶
Create a logging.FileHandler that writes to the given file path.
set_formatter/2¶
Set a logging.Formatter on the handler using Python's format string syntax (e.g. "%(asctime)s [%(levelname)s] %(message)s").
add_handler/2¶
Add a handler to the logger.
remove_handler/2¶
Remove a handler from the logger.
basic_config/1¶
Call logging.basicConfig() with a dict of options ({level: "info"}, keys declared or single-quoted). Supported keys: level, format, datefmt, filename, filemode, stream. Note: basicConfig only takes effect if the root logger has no handlers yet.
Examples
Basic usage¶
-import_from(log, [get_logger, info, warning, set_level])
init(L) <- (
get_logger("myapp", L),
set_level(L, "info"),
info(L, "Application started")
)
process_item(L, ITEM) <- (
ITEM > 0,
info(L, f"Processing item {ITEM}")
)
process_item(L, ITEM) <- (
ITEM =< 0,
warning(L, f"Skipping invalid item {ITEM}")
)
Custom handler and formatter¶
-import_from(log, [
get_logger, info, set_level,
stream_handler, file_handler, set_formatter, add_handler
])
setup_logging(L) <- (
get_logger("myapp", L),
set_level(L, "debug"),
file_handler("/var/log/myapp.log", FH),
set_formatter(FH, "%(asctime)s [%(levelname)s] %(name)s: %(message)s"),
add_handler(L, FH),
stream_handler("stderr", SH),
set_formatter(SH, "%(levelname)s: %(message)s"),
add_handler(L, SH)
)
Logger hierarchy¶
-import_from(log, [get_logger, info, set_level])
Setup <- (
get_logger("myapp", PARENT),
set_level(PARENT, "info"),
get_logger("myapp.db", DBLOG),
set_level(DBLOG, "debug"),
info(DBLOG, "DB logger inherits parent's handlers")
)
Implementation
- Module:
clausal/modules/py/logging.py - Predicates:
ModulePredicatewrappers, the same pattern as the otherclausal/modules/py/modules - Backend: Python's
loggingmodule — all predicates delegate tologging.Loggermethods - Tests:
tests/test_logging_module.py,tests/fixtures/logging_basic.seam
Design decisions
- Logger objects are opaque Python values — passed around via unification, not inspectable as terms.
- Logging predicates always succeed — they are side effects. Level filtering happens inside Python's logging; the predicate succeeds regardless.
is_enabled_for/2is the exception — it succeeds or fails based on level, useful for guarding expensive message construction.- Level names are strings — maps to Python constants internally. Both
"warn"/"warning"and"fatal"/"critical"are accepted. - f-string messages — no special formatting needed; the seam's f-string support handles interpolation with auto-deref of logic variables.
- Module name is
log— avoids shadowing Python'sloggingstdlib module in the import machinery.
See also: I/O — write, writeln, and f-string output · Python Interop — ++() escape for custom logging handlers.