From d3c0b2f8042abf3092363583adf6e21927c7736c Mon Sep 17 00:00:00 2001 From: Nil Geisweiller Date: Wed, 28 Jan 2026 16:38:09 +0200 Subject: [PATCH 1/9] Initial logger library + example --- examples/logger.metta | 2 ++ lib/lib_logger.metta | 4 ++++ lib/lib_logger.pl | 24 ++++++++++++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 examples/logger.metta create mode 100644 lib/lib_logger.metta create mode 100644 lib/lib_logger.pl diff --git a/examples/logger.metta b/examples/logger.metta new file mode 100644 index 0000000..566f3fe --- /dev/null +++ b/examples/logger.metta @@ -0,0 +1,2 @@ +!(import! &self (library lib_logger)) +!(logger-info "Here?") diff --git a/lib/lib_logger.metta b/lib/lib_logger.metta new file mode 100644 index 0000000..c53b1e8 --- /dev/null +++ b/lib/lib_logger.metta @@ -0,0 +1,4 @@ +!(import! &self (library lib_import)) + +!(import_prolog_functions_from_file (library lib_logger.pl) + (logger-info)) diff --git a/lib/lib_logger.pl b/lib/lib_logger.pl new file mode 100644 index 0000000..e4cd506 --- /dev/null +++ b/lib/lib_logger.pl @@ -0,0 +1,24 @@ +% Import log4p +use_module(library(log4p)). + +% PeTTa log handler, append timestamped messages to a petta.log file +% under the current folder +petta_log_handler(Level, Msg) :- + % Get current time + get_time(T), + format_time(atom(FT), '%F %T:%3f', T), + % Format full message as "[DATETIME] [LOGLEVEL] MESSAGE" + string_upper(Level, ULvl), + swritef(FullMsg, "[%w] [%w] %w", [FT, ULvl, Msg]), + % Append full message to petta.log (TODO: should be optimized) + open('petta.log', append, Stream), + write(Stream, FullMsg), + nl(Stream), + close(Stream). + +% Set PeTTa log handler +remove_log_handler(default_log_handler). +add_log_handler(petta_log_handler). + +'logger-info'(Msg, []) :- + log4p:info(Msg). From d6c2ae7157212f3ad21ef9331d86bcf9b3cedbc5 Mon Sep 17 00:00:00 2001 From: Nil Geisweiller Date: Thu, 29 Jan 2026 09:02:08 +0200 Subject: [PATCH 2/9] Add comments to lib_logger.pl --- lib/lib_logger.pl | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/lib_logger.pl b/lib/lib_logger.pl index e4cd506..4d5f4bf 100644 --- a/lib/lib_logger.pl +++ b/lib/lib_logger.pl @@ -1,5 +1,31 @@ +% Logger for MeTTa based on +% +% [log4p](https://www.swi-prolog.org/pack/list?p=log4p) +% +% a SWI-Prolog logger. +% +% By default it logs a message MESSAGE by appending the following line +% to the file petta.log in the current directory +% +% [DATETIME] [LOGLEVEL] MESSAGE +% +% where DATETIME is the current date and time and LOGLEVEL is one the +% following: TRACE, DEBUG, INFO, WARN, ERROR, FATAL. +% +% The logger has a global (across all threads) log level, as well as +% local (per thread) log levels. The default log level (global and +% local) is INFO, meaning that by default all messages logged at the +% INFO, WARN, ERROR or FATAL level will be logged while the messages +% logged at the TRACE or DEBUG level will be ignored. + % Import log4p -use_module(library(log4p)). +:- use_module(library(log4p)). + +% NEXT: support resetting stdout, filepath and timestamp. + +% NEXT: test overhead and see if it possible to minimize it. + +% NEXT: add lots of comments in lib_logger.metta. % PeTTa log handler, append timestamped messages to a petta.log file % under the current folder From d6e20dc0e832f6dbe26c2e0fe18368d74bf91431 Mon Sep 17 00:00:00 2001 From: Nil Geisweiller Date: Thu, 29 Jan 2026 09:02:44 +0200 Subject: [PATCH 3/9] Implement main logging functions --- lib/lib_logger.pl | 92 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 3 deletions(-) diff --git a/lib/lib_logger.pl b/lib/lib_logger.pl index 4d5f4bf..ba3c9a5 100644 --- a/lib/lib_logger.pl +++ b/lib/lib_logger.pl @@ -43,8 +43,94 @@ close(Stream). % Set PeTTa log handler -remove_log_handler(default_log_handler). -add_log_handler(petta_log_handler). +:- remove_log_handler(default_log_handler). +:- add_log_handler(petta_log_handler). -'logger-info'(Msg, []) :- +% Get current log level, one of trace, debug, info, warn, error and +% fatal. +'logger-level'(LogLvl) :- + log4p:get_log_level(LogLvl). + +% Get all possible log levels, which are trace, debug, info, warn, +% error and fatal. +'logger-levels'(LogLvls) :- + log4p:log_levels(LogLvls). + +% Change log level globally (across all threads). Valid log levels +% are trace, debug, info, warn, error and fatal. +'logger-set-global-level'(NewLvl, true) :- + log4p:set_global_log_level(NewLvl). + +% Change log level locally (only the current threads). Valid log +% levels are trace, debug, info, warn, error and fatal. +'logger-set-local-level'(NewLvl, true) :- + log4p:set_local_log_level(NewLvl, true). +'logger-set-level'(NewLvl, true) :- + log4p:set_local_log_level(NewLvl). + +% Log a message at the trace level +'logger-trace'(Msg, true) :- + log4p:trace(Msg). + +% Log a message at the debug level +'logger-debug'(Msg, true) :- + log4p:debug(Msg). + +% Log a message at the info level +'logger-info'(Msg, true) :- log4p:info(Msg). + +% Log a message at the warn level +'logger-warn'(Msg, true) :- + log4p:warn(Msg). + +% Log a message at the error level +'logger-error'(Msg, true) :- + log4p:error(Msg). + +% Log a message at the fatal level. This will NOT halt the process. +% This responsability is left to the programmer. +'logger-fatal'(Msg, true) :- + log4p:fatal(Msg). + +% Log a message at the given level. +'logger-log'(LogLvl, Msg, true) :- + log4p:log(LogLvl, Msg). + +% Log a formatted message at the given level. The format +% specification can be found +% [here](https://www.swi-prolog.org/pldoc/man?predicate=format/3) +'logger-logf'(LogLvl, Msg, Args, true) :- + maplist(repr, Args, Strs), + log4p:logf(LogLvl, Msg, Strs). + +% Log a formatted message at the trace level +'logger-tracef'(Msg, Args, true) :- + maplist(repr, Args, Strs), + log4p:logf(trace, Msg, Strs). + +% Log a message at the debug level +'logger-debugf'(Msg, Args, true) :- + maplist(repr, Args, Strs), + log4p:logf(debug, Msg, Strs). + +% Log a message at the info level +'logger-infof'(Msg, Args, true) :- + maplist(repr, Args, Strs), + log4p:logf(info, Msg, Strs). + +% Log a message at the warn level +'logger-warnf'(Msg, Args, true) :- + maplist(repr, Args, Strs), + log4p:logf(warn, Msg, Strs). + +% Log a message at the error level +'logger-errorf'(Msg, Args, true) :- + maplist(repr, Args, Strs), + log4p:logf(error, Msg, Strs). + +% Log a message at the fatal level. This will NOT halt the process. +% This responsability is left to the programmer. +'logger-fatalf'(Msg, Args, true) :- + maplist(repr, Args, Strs), + log4p:logf(fatal, Msg, Strs). From fdd9d4e75a5d4c775f56d4fbbc73ec0571365dd2 Mon Sep 17 00:00:00 2001 From: Nil Geisweiller Date: Thu, 29 Jan 2026 09:03:13 +0200 Subject: [PATCH 4/9] Export main logging functions to MeTTa --- lib/lib_logger.metta | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/lib_logger.metta b/lib/lib_logger.metta index c53b1e8..406d6af 100644 --- a/lib/lib_logger.metta +++ b/lib/lib_logger.metta @@ -1,4 +1,21 @@ !(import! &self (library lib_import)) - !(import_prolog_functions_from_file (library lib_logger.pl) - (logger-info)) + (logger-level + logger-levels + logger-set-global-level + logger-set-local-level + logger-set-level + logger-trace + logger-debug + logger-info + logger-warn + logger-error + logger-fatal + logger-log + logger-logf + logger-tracef + logger-debugf + logger-infof + logger-warnf + logger-errorf + logger-fatalf)) From a0753e48ab30e60d970a93480c4d2f7e09580ee8 Mon Sep 17 00:00:00 2001 From: Nil Geisweiller Date: Thu, 29 Jan 2026 09:03:26 +0200 Subject: [PATCH 5/9] Add tests of main logging functions --- examples/logger.metta | 72 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/examples/logger.metta b/examples/logger.metta index 566f3fe..43f8159 100644 --- a/examples/logger.metta +++ b/examples/logger.metta @@ -1,2 +1,72 @@ +;; Test logger library + +;; Import logger library !(import! &self (library lib_logger)) -!(logger-info "Here?") + +;; Get current log-level +!(test + (logger-level) + info) + +;; Get all possible log levels +!(test + (logger-levels) + (trace debug info warn error fatal)) + +;; Set log level to debug +!(test + (logger-set-level debug) + True) + +;; Get current log-level +!(test + (logger-level) + debug) + +;; Log message at the trace level (should be ignored) +!(test + (logger-trace "Log message at the trace level") + True) + +;; Log message at the debug level +!(test + (logger-debug "Log message at the debug level") + True) + +;; Log message at the info level +!(test + (logger-info "Log message at the info level") + True) + +;; Log message at the warn level +!(test + (logger-warn "Log message at the warn level") + True) + +;; Log message at the error level +!(test + (logger-error "Log message at the error level") + True) + +;; Log message at the fatal level +!(test + (logger-fatal "Log message at the fatal level") + True) + +;; Log message at the given info level +!(test + (logger-log info "Log message at the given info level") + True) + +;; Log message containing MeTTa terms at the given info level +!(test + (logger-logf info + "Log terms %w, %w and %w at the given info level" + ((+ 21 21) "Hello World!" (R A B))) + True) + +;; Log message containing MeTTa terms at the info level +!(test + (logger-infof "Log terms %w, %w and %w at the info level" + ((+ 21 21) "Hello World!" (R A B))) + True) From 35dd28b555a04680763fd51aefe00615ac911319 Mon Sep 17 00:00:00 2001 From: Nil Geisweiller Date: Mon, 2 Feb 2026 07:23:03 +0200 Subject: [PATCH 6/9] Complete logger library --- lib/lib_logger.metta | 80 ++++++++++++++++++++++++++++- lib/lib_logger.pl | 116 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 180 insertions(+), 16 deletions(-) diff --git a/lib/lib_logger.metta b/lib/lib_logger.metta index 406d6af..dddbf22 100644 --- a/lib/lib_logger.metta +++ b/lib/lib_logger.metta @@ -1,6 +1,84 @@ +;; Logger library to log timestamped messages into a log file. New +;; messages never replace old messages, instead they are appended to +;; the end of the log file. The responsability of deleting a log file +;; is left the user. +;; +;; The logger library contains the following methods: +;; +;; - (logger-filepath) returns a string of the filepath where log +;; messages are written. Default: "petta.log". +;; +;; - (logger-stdout) returns True iff messages are written to stdout. +;; Default: False. +;; +;; - (logger-timestamp) returns True iff messages are prefixed with +;; date and time. Default: True. +;; +;; - (logger-set-filepath FILEPATH) sets the destination of the +;; filepath to FILEPATH, where log messages are written. +;; +;; - (logger-set-stdout FLAG) sets the flag to write log messages to +;; stdout iff FLAG is True. +;; +;; - (logger-set-timestamp FLAG) sets the flag to prefix log messages +;; with date and time iff FLAG is True. +;; +;; - (logger-level) returns the current log level. Only messages are +;; at this level or higher are logged. For instance, if the current +;; log level is info, then messages at levels info, warn, error and +;; fatal will be logged. Default: info. +;; +;; - (logger-levels) returns a tuple with all possible log levels, +;; which are trace, debug, info, warn, error and fatal. +;; +;; - (logger-trace MESSAGE) log MESSAGE at the trace level. +;; +;; - (logger-debug MESSAGE) log MESSAGE at the debug level. +;; +;; - (logger-info MESSAGE) log MESSAGE at the info level. +;; +;; - (logger-warn MESSAGE) log MESSAGE at the warn level. +;; +;; - (logger-error MESSAGE) log MESSAGE at the error level. +;; +;; - (logger-fatal MESSAGE) log MESSAGE at the fatal level. Note that +;; this will not halt the process. This responsability is left to +;; the programmer. +;; +;; - (logger-log LEVEL MESSAGE) log MESSAGE at level LEVEL. +;; +;; - (logger-logf LEVEL MESSAGE ARGUMENTS) log MESSAGE at level LEVEL, +;; formatting the message with ARGUMENTS according +;; https://www.swi-prolog.org/pldoc/man?predicate=format/3. +;; +;; - (logger-tracef MESSAGE ARGUMENTS) like logger-trace but expects a +;; formatted message. +;; +;; - (logger-debugf MESSAGE ARGUMENTS) like logger-debug but expects a +;; formatted message. +;; +;; - (logger-infof MESSAGE ARGUMENTS) like logger-info but expects a +;; formatted message. +;; +;; - (logger-warnf MESSAGE ARGUMENTS) like logger-warn but expects a +;; formatted message. +;; +;; - (logger-errorf MESSAGE ARGUMENTS) like logger-error but expects a +;; formatted message. +;; +;; - (logger-fatalf MESSAGE ARGUMENTS) like logger-fatal but expects a +;; formatted message. + +;; Import prolog logger library !(import! &self (library lib_import)) !(import_prolog_functions_from_file (library lib_logger.pl) - (logger-level + (logger-filepath + logger-stdout + logger-timestamp + logger-set-filepath + logger-set-stdout + logger-set-timestamp + logger-level logger-levels logger-set-global-level logger-set-local-level diff --git a/lib/lib_logger.pl b/lib/lib_logger.pl index ba3c9a5..81f0c82 100644 --- a/lib/lib_logger.pl +++ b/lib/lib_logger.pl @@ -17,35 +17,121 @@ % local) is INFO, meaning that by default all messages logged at the % INFO, WARN, ERROR or FATAL level will be logged while the messages % logged at the TRACE or DEBUG level will be ignored. +% +% The logger always appends messsages to log file and never deletes +% it. It is up to the user to delete the log file. +% +% To install log4p you may need to run the following command +% +% ``` +% swipl pack install log4p +% ``` +% +% See https://www.swi-prolog.org/pldoc/man?section=pack-install for +% more information. % Import log4p :- use_module(library(log4p)). -% NEXT: support resetting stdout, filepath and timestamp. - % NEXT: test overhead and see if it possible to minimize it. % NEXT: add lots of comments in lib_logger.metta. -% PeTTa log handler, append timestamped messages to a petta.log file -% under the current folder +% Declare global variables +:- dynamic filepath/1. % Path where to write +:- dynamic stdout_flag/1. % Toggle writing to stdout +:- dynamic timestamp_flag/1. % Toggle prefixing message with timestamp + +% Set global variable defaults +filepath("petta.log"). +stdout_flag(false). +timestamp_flag(true). + +% Set global variables +set_filepath(NewFP) :- + retractall(filepath(_)), + asserta(filepath(NewFP)). +set_stdout(NewSF) :- + retractall(stdout_flag(_)), + asserta(stdout_flag(NewSF)). +set_timestamp(NewTF) :- + retractall(timestamp_flag(_)), + asserta(timestamp_flag(NewTF)). + +% Format message as +% +% "[DATETIME] [LOGLEVEL] MESSAGE" +% +% or +% +% "[LOGLEVEL] MESSAGE" +format_message(Level, Msg, FmtMsg) :- + % Retrieve timestamp flag + timestamp_flag(TF), + % Format message + (TF + -> (% Get current time + get_time(T), + format_time(atom(FmtT), '%F %T:%3f', T), + % Format full message as "[DATETIME] [LOGLEVEL] MESSAGE" + string_upper(Level, ULvl), + swritef(FmtMsg, "[%w] [%w] %w", [FmtT, ULvl, Msg])) + ; (% Format full message as "[LOGLEVEL] MESSAGE" + string_upper(Level, ULvl), + swritef(FmtMsg, "[%w] %w", [ULvl, Msg]))). + +write_to_logfile(FmtMsg) :- + % Retrieve filepath + filepath(FP), + % Append formatted message to log file. TODO: opening then + % closing is not optimal, should be optimized. + (string_length(FP, 0) + -> true + ; (open(FP, append, Stream), + writeln(Stream, FmtMsg), + close(Stream))). + +write_to_stdout(FmtMsg) :- + % Retrieve stdout_flag + stdout_flag(SF), + % Write formatted message to stdout. + (SF + -> writeln(FmtMsg) + ; true). + +% PeTTa log handler, append formatted messages to log file. petta_log_handler(Level, Msg) :- - % Get current time - get_time(T), - format_time(atom(FT), '%F %T:%3f', T), - % Format full message as "[DATETIME] [LOGLEVEL] MESSAGE" - string_upper(Level, ULvl), - swritef(FullMsg, "[%w] [%w] %w", [FT, ULvl, Msg]), - % Append full message to petta.log (TODO: should be optimized) - open('petta.log', append, Stream), - write(Stream, FullMsg), - nl(Stream), - close(Stream). + % Format message for logging + format_message(Level, Msg, FmtMsg), + % Write formatted message to log file + write_to_logfile(FmtMsg), + % Write formatted message to stdout + write_to_stdout(FmtMsg). % Set PeTTa log handler :- remove_log_handler(default_log_handler). :- add_log_handler(petta_log_handler). +%%%%%%%%%%%%%%% +%% MeTTa API %% +%%%%%%%%%%%%%%% + +% Get global variables +'logger-filepath'(FP) :- + filepath(FP). +'logger-stdout'(SF) :- + stdout_flag(SF). +'logger-timestamp'(TF) :- + timestamp_flag(TF). + +% Set global variables +'logger-set-filepath'(NewFP, true) :- + set_filepath(NewFP). +'logger-set-stdout'(NewSF, true) :- + set_stdout(NewSF). +'logger-set-timestamp'(NewTF, true) :- + set_timestamp(NewTF). + % Get current log level, one of trace, debug, info, warn, error and % fatal. 'logger-level'(LogLvl) :- From 70d9722085a668a7c30097ee6a21cc7168c9b534 Mon Sep 17 00:00:00 2001 From: Nil Geisweiller Date: Mon, 2 Feb 2026 07:23:14 +0200 Subject: [PATCH 7/9] Complete logger library tests --- examples/logger.metta | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/examples/logger.metta b/examples/logger.metta index 43f8159..296d298 100644 --- a/examples/logger.metta +++ b/examples/logger.metta @@ -3,6 +3,21 @@ ;; Import logger library !(import! &self (library lib_logger)) +;; Get current filepath +!(test + (logger-filepath) + "petta.log") + +;; Get current stdout flag +!(test + (logger-stdout) + False) + +;; Get current timestamp flag +!(test + (logger-timestamp) + True) + ;; Get current log-level !(test (logger-level) @@ -70,3 +85,27 @@ (logger-infof "Log terms %w, %w and %w at the info level" ((+ 21 21) "Hello World!" (R A B))) True) + +;; Reset log filepath +!(test + (logger-set-filepath "my-petta.log") + True) +!(test + (logger-info "Log message to \"my-petta.log\" file") + True) + +;; Enable logging to stdout +!(test + (logger-set-stdout True) + True) +!(test + (logger-info "Message to stdout as well") + True) + +;; Disable prefixing the message with timestampe +!(test + (logger-set-timestamp False) + True) +!(test + (logger-info "Message without timestamp") + True) From 996d00bbe14f21cc7cd6d1a45c15373bf499ffc2 Mon Sep 17 00:00:00 2001 From: Nil Geisweiller Date: Mon, 2 Feb 2026 07:37:32 +0200 Subject: [PATCH 8/9] Add comment about installation --- examples/logger.metta | 8 +++++++- lib/lib_logger.metta | 8 ++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/examples/logger.metta b/examples/logger.metta index 296d298..faf919a 100644 --- a/examples/logger.metta +++ b/examples/logger.metta @@ -1,4 +1,10 @@ -;; Test logger library +;; Test logger library. +;; +;; You may need to install log4p by running the following command: +;; +;; ``` +;; swipl pack install log4p +;; ```` ;; Import logger library !(import! &self (library lib_logger)) diff --git a/lib/lib_logger.metta b/lib/lib_logger.metta index dddbf22..86cd10e 100644 --- a/lib/lib_logger.metta +++ b/lib/lib_logger.metta @@ -3,6 +3,14 @@ ;; the end of the log file. The responsability of deleting a log file ;; is left the user. ;; +;; This logger library is based on +;; [log4p](https://www.swi-prolog.org/pack/list?p=log4p). To install +;; log4p you may need to run the following command: +;; +;; ``` +;; swipl pack install log4p +;; ``` +;; ;; The logger library contains the following methods: ;; ;; - (logger-filepath) returns a string of the filepath where log From 859f4bb1b9933df9234fdb90f36fb6f9b4ee597d Mon Sep 17 00:00:00 2001 From: Nil Geisweiller Date: Mon, 2 Feb 2026 07:39:59 +0200 Subject: [PATCH 9/9] Remove useless NEXT message --- lib/lib_logger.pl | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/lib_logger.pl b/lib/lib_logger.pl index 81f0c82..08c588b 100644 --- a/lib/lib_logger.pl +++ b/lib/lib_logger.pl @@ -35,8 +35,6 @@ % NEXT: test overhead and see if it possible to minimize it. -% NEXT: add lots of comments in lib_logger.metta. - % Declare global variables :- dynamic filepath/1. % Path where to write :- dynamic stdout_flag/1. % Toggle writing to stdout