Revision history for Log-Abstraction
0.39 Tue Oct 6 08:17:42 PM EDT 2026
[ Bug Fixes ]
- Remove recursion issue when running mutant tests
0.38 Tue Oct 6 02:15:21 PM EDT 2026
[ Enhancements ]
- sendmail digest: with digest => 1, messages that min_interval holds
back are sent, oldest first, with the next email instead of being
dropped (at most digest_max, default 100; the rest are counted).
New flush() method sends them at once; destroying the logger calls
it. A failed send keeps them for the next email.
- New redact option: a qr//, a string or a list of them. Each match
is replaced with [REDACTED] before the message reaches the history
or any backend, in structured-field values too, and in the text
given to carp/croak. Strings make it usable from config files, and
\K keeps a prefix: redact => qr/password=\K\S+/. The Log::Any
adapter passes it on.
- Require Sub::Private 0.06. Before it, the private subs were not
protected when Log::Abstraction was loaded at run time (require,
string eval, Log::Any::Adapter->set), and "Too late to run CHECK
block" was printed.
[ Bug Fixes ]
- The Log::Any adapter ignored timestamp_format, timestamp_precision,
utc, rotate_size, rotate_interval and rotate_keep; it now passes
them to Log::Abstraction->new().
[ Tests ]
- New t/redact.t and t/sendmail_digest.t.
- New t/private_runtime.t checks that private subs can't be called
from outside when the module is loaded at run time.
- t/timestamp.t failed on long-double perls (x86_64-linux-ld): its
mocked clock stepped by 0.001, which isn't exact in binary and
truncated to .000. It now steps by 0.125:
https://www.cpantesters.org/cpan/report/0332021c-c0f8-11f1-80bb-2ae0940ca041
0.37 Sun Oct 4 09:43:19 PM EDT 2026
[ Bug Fixes ]
- journald: a datagram bigger than the system's Unix-socket send
buffer failed with "Message too long" (EMSGSIZE) and the message
was lost. The send buffer is now raised where the kernel allows,
and on EMSGSIZE the message is truncated to half the size and the
send retried, down to 4,096 bytes. Found by a CPAN smoker whose
buffer was under 200,000 bytes, failing t/regressions.t:
https://matrix.perl-magpie.org/results/3720262e-c005-11f1-a083-8730a1ea2842
- Logging no longer changes the caller's $@ or $!, so code such as
eval { ... }; if($@) { $log->debug('failed'); die $@ } rethrows the
real error rather than an empty one. The same applies to the
Log::Any adapter and to DESTROY.
- A file path ending in a newline (e.g. "app.log\n") is now rejected
like any other control character; before, the newline was silently
dropped and a different file written.
[ Tests ]
- t/function.t covers every internal helper directly, and checks for
memory cycles and that logging leaves $_, $@ and $! alone.
- New t/journald_emsgsize.t fakes a small send buffer by overriding
send(). The override records the datagram rather than sending it,
so the test doesn't hit the system's own limit (it failed on macOS,
which caps Unix datagrams at 2KB, with ENOBUFS).
0.36 Sat Oct 3 09:35:44 PM EDT 2026
[ Enhancements ]
- Structured fields: every logging method accepts a hashref of
fields after the message, e.g. $log->info('login', { user_id => 42 }).
They are kept as data in the history, array and CODE-ref backends,
as a nested "fields" object in format => 'json', and as journal
fields in journald; other backends get them appended as logfmt
key=value text.
- The Log::Any adapter implements structured(), so a trailing hashref
and the Log::Any context reach Log::Abstraction as fields.
- New critical(), alert() and emergency() methods, logged at their own
levels (syslog crit/alert/emerg, journald PRIORITY 2/1/0). They
behave like error(): croak_on_error, or having no backend, croaks.
An object logger without the method, such as Log::Log4perl, is
called with fatal(), or error() if it has no fatal().
- New is_trace, is_info, is_notice, is_warn, is_error, is_critical,
is_alert and is_emergency methods alongside is_debug. The Log::Any
adapter's detection methods now call them.
- New timestamp options: timestamp_format (a strftime pattern, or
'iso8601'/'rfc3339'), utc, and timestamp_precision (0-9 digits of
fractional seconds, from Time::HiRes). Patterns may also use %N,
%3N etc., and %z/%:z, which now give a numeric offset on every
platform. They apply to %timestamp% and the JSON timestamp. The
time is now read once per message, so all backends show the same
time. Time::HiRes and Time::Local (both core) are new dependencies.
- File rotation for the path backends: rotate_size (bytes, or with
K/M/G), rotate_interval (hourly, daily, weekly, monthly) and
rotate_keep (default 5). Files are rotated to FILE.1, FILE.2 ...
before the write that is due. Since the file is opened for every
message, logrotate's default rename-and-create works without
copytruncate or SIGHUP; this is now documented and tested.
- Every backend can have its own 'level' and 'format'. syslog,
journald and sendmail take them as keys; file, fd and array (in a
logger hash or at the top level) may be given as a hash, e.g.
file => { file => $path, level => 'info', format => 'json' }. A
format replaces the message for array, syslog, journald and email.
Invalid values croak in new().
[ Incompatible Changes ]
- The Log::Any adapter no longer collapses critical, alert and
emergency into error; they go to the new methods, so backends now
see the levels 'critical', 'alert' and 'emergency' (CODE callbacks
testing for 'error' miss them, and syslog priorities change), and
is_critical, is_alert and is_emergency have their own thresholds.
- A hashref as the last of two or more arguments to a logging method
is now taken as structured fields. It used to be joined into the
message as "HASH(0x...)".
- When a call has structured fields, an object logger (such as the
default Log::Log4perl) is passed them as an extra " key=value ..."
argument after the message.
- Log::Abstraction no longer imports Carp, POSIX, Scalar::Util,
Sys::Syslog or Email::Sender::Simple functions, so they can no longer
be called as methods on a logger ($log->carp, $log->syslog,
$log->strftime ...). Code mocking Log::Abstraction::openlog or
::setlogsock should mock Sys::Syslog::openlog/setlogsock instead.
- 'level' and 'format' in the journald hash are now that backend's
level and format; they were sent to the journal as LEVEL and FORMAT
fields.
[ Bug Fixes ]
- A top-level 'fd' now counts as a backend in new(), as 'file' and
'array' do: Log::Abstraction->new(fd => $fh) no longer also sets up
Log::Log4perl and sends every message there too.
- The release dates in this file are now in ISO-8601 (W3CDTF) form,
as CPAN::Changes expects.
- t/journald.t no longer hangs if a journald send fails: its receiver
now reads with MSG_DONTWAIT, so the test fails instead. This was the
hang in RT#181461.
0.35 2026-09-30T21:44:46-04:00
[ Enhancements ]
- New max_messages option caps the in-memory message history.
[ Security ]
- Format tokens inside a log message are no longer expanded. Tokens
were substituted one at a time with %message% first, so a message
containing "%env_FOO%" leaked $ENV{FOO} into file/fd logs. All
tokens are now expanded in a single pass.
- Line breaks in a message are followed by a tab in text formats, so a
message can no longer forge extra log entries.
- syslog messages are passed through a '%s' format, so '%m' and other
'%' sequences in a message are logged literally.
[ Bug Fixes ]
- A failed sendmail delivery no longer stops the message reaching the
syslog, journald, fd and top-level file/fd backends.
- level() setter is now case-insensitive, as documented.
- The syslog sub-hash 'level' may now be a name as well as a number;
a name used to warn "isn't numeric" and drop every message. The
sendmail 'level' may likewise be a number, and is case-insensitive.
- Invalid syslog/sendmail levels, a sendmail hash without 'to', and
invalid journald field names now croak in new() rather than
silently dropping messages at log time.
- script_name is now auto-detected for logger => { syslog => {...} },
not only for a top-level syslog key.
- warn()/error() called as a class method on a subclass no longer die
with "Can't use string as a HASH ref".
- carp_on_warn (and the no-backend carp) no longer fires for a warning
below the level threshold.
- A top-level file or fd now counts as a backend, so error() no longer
croaks (and warn() no longer carps) on a file-only logger.
- syslog and sendmail now join message parts without a separator, like
every other backend.
- Unicode messages are written as UTF-8 by the file/fd backends without
"Wide character" warnings; an fd with an encoding layer is not
double-encoded. JSON output keys are now sorted.
- journald: values are sent as UTF-8 with correct binary-framing
lengths; a message too big for one datagram is truncated; a missing
journald socket carps once rather than on every message.
- Destroying one instance no longer closes the process-wide syslog
connection while other instances still use it.
- Log::Any adapter: file/line now point at the code calling Log::Any;
a non-Log::Abstraction 'instance' croaks instead of being silently
replaced; carp_on_warn, croak_on_error, config_file and max_messages
are forwarded; a croak while logging is turned into a carp.
[ Documentation ]
- Documented that trace shares debug's threshold, the syslog sub-hash
keys, Unicode handling, and the full list of accepted level names.
- Formal specification brought in line with the code.
[ Housekeeping ]
- Removed the unused JSON::MaybeXS test dependency.
- journald binary framing no longer uses pack('Q<'), which dies on
perls built without 64-bit integers.
- Minimum Perl version is now 5.014 (the code needs 5.010, and
Readonly::Values::Syslog needs 5.014).
- Log::Any and the Email::* modules used by the sendmail backend are
now proper runtime 'recommends' in META; Log::Any was previously
hidden as an unrecognised resource.
- Documented the Log::Any adapter's generated logging and is_*
methods, and fixed its SYNOPSIS.
- autodie is now loaded with :default rather than :all, since the
module never calls system() or exec(); autodie is now declared.
IPC::System::Simple is declared as a runtime requirement because
Params::Get, loaded via Config::Abstraction, needs it but doesn't
declare it.
- Data::Dumper is now a test dependency only; the tests that use it now
load it themselves. Removed the unused File::Slurp, File::Glob
and File::stat test dependencies. Declared the
core modules Socket and File::Basename, and the author-test modules
as develop prerequisites.
- Documented the messages raised while logging (invalid file name,
SMTP host/port, unusable logger, delivery failures), and the
Log::Abstraction::new() function-call form.
- t/10-compile.t no longer fails when Log::Any is not installed.
Log::Any is only a recommended dependency, so the compile check of
Log::Any::Adapter::Abstraction is now skipped when
Log::Any::Adapter::Base cannot be loaded.
0.34 2026-09-26T20:47:01-04:00
[ Bug Fixes ]
- Bump minimum versions
0.33 2026-07-10T11:03:49-04:00
[ Architecture ]
- Added Sub::Private (enforce mode) to mark _log, _high_priority,
_validate_file_path, _format_message, _journald_send, and
_sanitize_email_header as private via the :Private attribute.
Enforcement is active in production; HARNESS_ACTIVE bypass keeps
white-box tests functional without special setup.
- Converted _journald_send from a bare package function to a proper
OOP method ($self->_journald_send), consistent with all other
private helpers in the class.
- Fixed caller-depth resolution for warn/error/fatal: file and line
reported to CODE-ref callbacks and all file/fd format backends now
correctly resolve to the caller's source location. Previously the
extra _high_priority stack frame caused them to point to the module's
internal dispatch code. _format_message now accepts pre-computed
caller_file/caller_line parameters rather than calling caller(2)
internally; _log computes the correct depth (1 for trace/debug/info/
notice; 2 for warn/error) and passes the values down.
- Removed unreachable UNIVERSAL::isa call in _log's private-method
guard; replaced with the idiomatic method form (caller)[0]->isa().
- Removed spurious File::Basename->import() guard; require + fully-
qualified call is sufficient.
- Added =head1 LIMITATIONS section to Log::Abstraction documenting
syslog hash mutation, no-structured-fields, single-threaded email
throttle, and OpenTelemetry status.
- Added =head1 LIMITATIONS section to Log::Any::Adapter::Abstraction
documenting nine-to-six level collapse, no-structured-fields, and
the recommended-not-required Log::Any dependency.
[ Bug Fixes ]
- Fixed warn()/error()/fatal() calls: file and line now correctly
identify the caller's source location in all backends (CODE-ref,
file, fd, hash-file, hash-fd). Previously these resolved to
Abstraction.pm's internal _high_priority frame.
[ Tests ]
- Added t/caller_depth.t: verifies that file/line in CODE-ref
callbacks correctly identify the caller's source location for all
six log levels (trace, debug, info, notice, warn, error, fatal).
[ Enhancements ]
- Added Log::Any::Adapter::Abstraction: route Log::Any-using CPAN modules
through Log::Abstraction with a single Log::Any::Adapter->set() call.
Covers all nine Log::Any levels (critical/alert/emergency collapse to error).
- Added format => 'json' magic value: file and fd backends now emit compact
JSON objects (timestamp/level/message/file/line, plus class for subclasses)
compatible with journald, Loki, Elasticsearch, and Splunk.
- Added journald sub-backend to the HASH logger: sends structured fields
(MESSAGE, PRIORITY, SYSLOG_IDENTIFIER, plus any caller-supplied extras)
as a Unix-domain datagram using the systemd native journal protocol.
Extra config keys are forwarded as uppercase journald fields.
Delivery failures are silent (carp only); the app is never crashed.
- Added JSON::PP as a runtime dependency (core since 5.014; used by json format).
- Added Log::Any as a recommended (not required) runtime dependency.
[ Tests ]
- Added t/log_any_adapter.t: routing, level mapping, is_* detection, threshold
filtering, and adapter-built-from-constructor-args subtests.
- Added t/json_format.t: JSON line validity, field types (line as integer),
class suppression for base class, class inclusion for subclass, all backend
variants (scalar-path, fd, hash-file).
- Added t/journald.t: uses a temporary Unix-domain datagram socket as a fake
journald receiver to verify field encoding (text and binary framing),
PRIORITY mapping, extra fields, coexistence with other sub-backends, and
silent failure on an unreachable socket path.
0.32 2026-06-09T19:34:45-04:00
[ Bug Fixes ]
- Fixed SMTP host/port validation in sendmail backend: croak calls were
inside the eval{} that catches delivery failures, so bad config was
silently swallowed instead of propagating. Validation now runs before
the eval so misconfigured host/port croaks immediately to the caller.
0.31 2026-06-08T20:55:28-04:00
[ Architecture ]
- Added use autodie qw(:all); file writes wrapped in eval{} to keep
logging failures silent (app must not crash when log file is unavailable)
- Extracted _format_message($self, $level, $str, $use_class) helper,
eliminating 4+ copies of the format-token substitution block
- Extracted _validate_file_path($self, $path) helper, unifying path
validation for hash-backend file, scalar-path, and self->{'file'} under
one implementation
- Added Readonly constants for all magic strings and numbers (SMTP defaults,
port range, syslog defaults, format strings, validation regexes)
- Collapsed _high_priority duplicate if/else blocks into one
- All public void methods now return $self to allow method chaining;
level() setter returns undef on invalid input to signal the error
- Fixed syslog priority mapping: all levels (trace→debug, debug→debug,
info→info, notice→notice, warn→warning, error→err) now correctly mapped
via %LEVEL_TO_SYSLOG_PRIORITY hash instead of a binary error/warning test
- Moved Readonly from test-only to runtime dependency (Makefile.PL, cpanfile)
[ Documentation ]
- Added Z-notation FORMAL SPECIFICATION to every public method POD
- Added API Specification (Params::Validate::Strict input and Return::Set
output schemas) to every public method POD
- Added MESSAGES table to every public method POD
- Added internal-routine comment blocks (purpose, entry, exit, side
effects, notes) for _log, _high_priority, _format_message,
_validate_file_path, _sanitize_email_header, DESTROY
- Suppressed %class% for the base Log::Abstraction class (appears empty,
as it added no useful information when not subclassed)
[ Tests ]
- Added t/locales.t: covers geographic (Locale::Country ISO-code sanity
with BAIL_OUT on drift, case-insensitive codes, concurrent instances) and
POSIX system-locale (LC_ALL en_US.UTF-8/de_DE.UTF-8/ja_JP.UTF-8 error
paths using local $! = ENOENT; my $msg = "$!" pattern)
- Updated edge_cases.t and function.t to use unified "Invalid file name"
error message (standardised via _validate_file_path)
[ Security ]
- Fixed path traversal in HASH-backend file logger: path regex now blocks
'..' sequences, preventing writes outside the intended directory
- Fixed open() in HASH-backend file logger to use the untainted $file
variable rather than the original $logger->{'file'} value
- Added path validation (same allowlist + '..' check) to the scalar-string
logger path, which previously called open() with no validation at all
- Validated SMTP host in sendmail backend: only [a-zA-Z0-9.-] characters
accepted, preventing header/protocol injection via a crafted hostname
- Validated SMTP port in sendmail backend: must be an integer 1-65535,
preventing SSRF port-scanning via an attacker-controlled config file
- Fixed %env_*% format expansion in HASH file backend to use /ge with
defined-or fallback, consistent with the fd and scalar-path backends
[ Bug Fixes ]
- Fixed use Readonly::Values::Syslog version in source to 0.04, matching
Makefile.PL and cpanfile (was still 0.03 after the 0.30 bump)
[ Documentation ]
- Fixed EXAMPLES POD: sample output class field was "main"; corrected to
"Log::Abstraction" (blessed class of the logger object)
- Fixed EXAMPLES POD: sample output warn-row level field was "warning";
corrected to "warn" to match what the module actually passes to callbacks
- Added note explaining that file/line resolve to the module's internal
dispatch for warn/error calls due to the extra _high_priority stack frame
- Replaced broken combined file+sendmail example (file backend writes
native text format, not CSV) with a correct sendmail-only example; added
level => 'warn' key to restrict emails to warn-and-above; noted that
combining CSV output with email requires both in a single code-ref
[ Tests ]
- Simplified $parse_line regex in integration.t: removed dead |"$ alternation
subsumed by "(?:,|$)
0.30 2026-05-27T13:09:35-04:00
[ Enhancements ]
- Added EXAMPLES POD section: CSV file logging for BI import (code-ref
backend writing timestamp/level/class/file/line/message rows, with a
combined sendmail+min_interval variant for real-time alerting)
[ Bug Fixes]
- Bump minimum Readonly::Values::Syslog to 0.04 to fix is_debug() returning
false at trace level on older installations
Fixes https://www.cpantesters.org/cpan/report/32b653da-59c4-11f1-ba50-8ade6d8775ea
0.29 2026-05-26T20:36:13-04:00
[ Enhancements ]
- Added min_interval throttle to sendmail backend: at most one email per
configured interval (seconds); cooldown state stored in _last_email_sent
on the object and inherited by clones
- Bump minimum Test::Mockingbird version to fix https://www.cpantesters.org/cpan/report/f148a3e6-50a2-11f1-8224-a122dd379578
0.28 2026-05-15T20:28:07-04:00
[ Enhancements ]
- Added context (ctx) to code ref logger callbacks
[ Bug Fixes]
- Fixed carp_on_warn and croak_on_error when logging to an array backend
- Fixed cloning to a different level (level string now converted to integer)
- Fixed DESTROY to call Sys::Syslog::closelog() fully-qualified for correct mock interception
- Fixed warn(undef) and warn(warning => undef) to be silent no-ops
- Fixed warn(warning => [undef, ...]) to filter undef elements before joining
- Fixed odd-count plain list in warn/error (_high_priority now wraps get_params in eval)
- Fixed autovivification of sendmail key via exists() guards on sendmail hash access
- Fixed %env_foo% format token to expand missing env vars to empty string without warning
- Fixed ::new() to always construct normally regardless of arguments
- Removed spurious Carp::croak/carp fallback when array backend is defined
0.27 2026-01-11T09:52:54-05:00
Easier to read tests
Added fatal as a synonym for error
0.26 2025-10-15T17:03:58-04:00
Added testing dashboard on GitHub Pages
Added croak_on_error
0.25 2025-08-17T20:41:38-04:00
Added is_debug for consistency with Log::Any
Use Return::Set to assert return values within specification
Sanitize the e-mail headers
Started to add format argument to new()
Don't close fd that were passed in
Only load the mail modules when needed
0.24 2025-07-19T16:52:41-04:00
Added the ability to send an email for higher priority messages
Only load Log::Log4perl when needed
Deep-clone the messages array during cloning
Map notice to info for Log::Log4perl
Syslog facility is now configurable (default is still local0)
Connections to the syslog are now persistent
0.23 2025-07-17T14:45:54-04:00
Better error message when we don't know what to do
Allow the syslog config to say 'server' instead of 'host' for consistency with CHI
0.22 2025-07-17T08:22:16-04:00
Error() now sends to syslog like warn()
Ensure undef isn't passed in the messages array to coderefs
0.21 2025-07-16T16:21:25-04:00
Added error()
0.20 2025-06-15T21:19:03-04:00
Fix GitHub#2
0.19 2025-06-10T08:32:28-04:00
Bump minimum version of Sys::Syslog
More untaint checking
0.18 2025-06-06T15:52:27-04:00
Fix level testing
0.17 2025-05-22T14:21:14-04:00
Added the messages() method
0.16 2025-05-22T07:40:24-04:00
Handle upper case levels, such as 'INFO'
0.15 2025-05-20T21:10:23-04:00
No need to create a Log::Log4perl when sending output to a file
0.14 2025-05-20T07:44:19-04:00
Adjusted minimum version of Config::Abstraction.
It should be 0.19 in terms of testing but it's best to use the fixes in 0.25
RT#165420 - ANDK
Added 'array' to the logger hash
Introduced 'level' - minimum level to log at, defaults to 'warn'
Fixed where to log bugs
Check and untaint the filename
0.13 2025-05-14T08:30:53-04:00
Use '>' after level rather than ':' to files, like lower level loggers
Try not to put the name of this package in the logfiles,
it adds nothing apart from disc usage
Block setting logger => Log::Abstraction in new()
Croak if we don't know how to handle a message
0.12 2025-05-09T07:42:35-04:00
Added the file parameter, so that both file and syslog can now be given,
and file can be read in from a configuration file
Added the fd descriptor - a file descriptor to log to
Added separators between fields in file output
0.11 2025-05-07T15:37:37-04:00
Fixed calling can() on an unblessed variable
Honour carp_on_warn when syslog is set
0.10 2025-05-06T20:43:48-04:00
Added carp_on_warn
0.09 2025-05-06T08:15:36-04:00
If no logger is given, use Log4perl
0.08 2025-05-05T11:39:58-04:00
Use Config::Abstraction instead of Config::Auto
Guess the value if script_name if it's not given
0.07 2025-03-24T08:36:01-04:00
Handle "Socket operation on non-socket" on Solaris
Calling new on an object now returns a clone rather than setting the defaults in the new object
0.06 2025-03-12T13:23:24-04:00
Don't put spaces between elements of an array
0.05 2025-03-11T14:10:10-04:00
Handle being passed a reference to an array
0.04 2025-03-10T09:39:46-04:00
Renamed from Log-YetAnother to Log-Abstraction
Added config_file argument to new()
0.03 2025-03-08T08:40:00-05:00
Fix passing an array to warn()
0.02 2025-03-06T17:09:32-05:00
Improved argument checking to new()
0.01 2025-03-06T15:42:04-05:00
First draft
Keyboard Shortcuts
Global
s
Focus search bar
?
Bring up this help dialog
GitHub
gp
Go to pull requests
gi
Go to GitHub issues (only if GitHub is preferred repository)