Revision history for Log-Abstraction
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)