# NAME Log::Abstraction - Logging Abstraction Layer # VERSION 0.31 # SYNOPSIS use Log::Abstraction; my $logger = Log::Abstraction->new(logger => 'logfile.log'); $logger->debug('This is a debug message'); $logger->info('This is an info message'); $logger->notice('This is a notice message'); $logger->trace('This is a trace message'); $logger->warn({ warning => 'This is a warning message' }); # DESCRIPTION The `Log::Abstraction` class provides a flexible logging layer on top of different types of loggers, including code references, arrays, file paths, and objects. It also supports logging to syslog if configured. # METHODS ## new my $logger = Log::Abstraction->new(%args); my $logger = Log::Abstraction->new(\%args); my $logger = Log::Abstraction->new($file_path); # Clone with optional overrides my $clone = $logger->new(level => 'debug'); Creates a new `Log::Abstraction` instance, or clones an existing one when called on an object. ### Arguments - `carp_on_warn` If set to 1, and no `logger` is given, call `Carp::carp` on `warn()`. Also causes `error()` to `carp` if `croak_on_error` is not set. - `croak_on_error` If set to 1, and no `logger` is given, call `Carp::croak` on `error()`. - `config_file` Path to a configuration file (YAML, XML, INI, etc.) whose contents are merged with the constructor arguments. On non-Windows systems the class can also be configured via environment variables prefixed with `"Log::Abstraction::"`. For example: export Log::Abstraction::script_name=foo - `ctx` Arbitrary context value passed through to CODE-ref logger callbacks as `$args->{ctx}`. - `format` Format string for file/fd backends. Tokens expanded at log time: %callstack% caller file and line number %class% blessed class of the logger object %level% upper-cased level name %message% the joined log message %timestamp% YYYY-MM-DD HH:MM:SS (local time) %env_FOO% value of $ENV{FOO}, or empty string if unset **Security note:** because `format` may contain `%env_*%` tokens, avoid granting untrusted sources write access to config files that set this key. - `level` Minimum level at which to emit log entries. Defaults to `"warning"`. Valid values (case-insensitive): `trace`, `debug`, `info`, `notice`, `warn`/`warning`, `error`. - `logger` One of: - A code reference -- called with a hashref `{ class, file, line, level, message, ctx }` - An object -- method matching the level name is called on it - A hash reference -- may contain `file`, `array`, `fd`, `syslog`, and/or `sendmail` keys - An array reference -- `{ level, message }` hashrefs are pushed onto it - A scalar string -- treated as a file path to append to When not supplied, [Log::Log4perl](https://metacpan.org/pod/Log%3A%3ALog4perl) is initialised as the default backend. The `sendmail` sub-hash supports: `host`, `port`, `to`, `from`, `subject`, `level`, `min_interval`. At most one email is sent per `min_interval` seconds per instance. - `script_name` Script name reported to syslog. Auto-detected from `$0` if not supplied. - `verbose` When using the default Log::Log4perl backend, raises the logging level to DEBUG when set to a true value. ### Returns A blessed `Log::Abstraction` object. ### Side Effects Loads `File::Basename` if `syslog` is configured and `script_name` is not supplied. Loads `Log::Log4perl` if no logger backend is specified. ### Example my $logger = Log::Abstraction->new( level => 'debug', logger => \@messages, ); my $clone = $logger->new(level => 'info'); ### API Specification #### Input { carp_on_warn => { type => BOOLEAN, optional => 1 }, config_file => { type => SCALAR, optional => 1 }, croak_on_error => { type => BOOLEAN, optional => 1 }, ctx => { optional => 1 }, format => { type => SCALAR, optional => 1 }, level => { type => SCALAR, regex => qr/^(trace|debug|info|notice|warn(?:ing)?|error)$/i, optional => 1 }, logger => { optional => 1 }, script_name => { type => SCALAR, optional => 1 }, verbose => { type => BOOLEAN, optional => 1 }, } #### Output { type => 'object', class => 'Log::Abstraction' } ### MESSAGES Error Meaning / Action ---------------------------------------- ----------------------------------------- ": : File not readable" config_file path exists but is unreadable. Check file permissions. ": Can't load configuration Config::Abstraction could not parse the from " file. Check syntax and format. ": syslog needs to know the syslog backend requested but script_name script name" could not be determined. Pass it explicitly. ": attempt to encapsulate logger => Log::Abstraction would create Log::Abstraction as a logging class, a needless forwarding loop. Use a that would add a needless indirection" different backend. ": invalid syslog level ''" level value is not a recognised syslog level name. Use trace/debug/info/notice/ warn/warning/error. ## level my $current = $logger->level(); $logger->level('debug'); Get or set the minimum logging level. When setting, returns `$self` to allow method chaining. When getting, returns the current level as an integer (per the syslog numeric scale; lower numbers are higher priority). ### Arguments - `$level` (optional) A level name string: `trace`, `debug`, `info`, `notice`, `warn`/`warning`, or `error`. Case-insensitive. Omit to perform a pure get. ### Returns In getter mode: an integer in the range 0 (emergency) to 7 (debug/trace). In setter mode: `$self` (to allow chaining). ### Side Effects When setting, updates `$self->{level}`. ### Example $logger->level('debug'); my $n = $logger->level(); # e.g. 7 # Method chaining $logger->level('info')->info('Now at info level'); ### API Specification #### Input { level => { type => SCALAR, regex => qr/^(trace|debug|info|notice|warn(?:ing)?|error)$/i, optional => 1 }, } #### Output Getter: { type => 'integer', min => 0, max => 7 } Setter: { type => 'object', class => 'Log::Abstraction' } ### MESSAGES Warning Meaning / Action ---------------------------------------- ------------------------------------------ ": invalid syslog level ''" The supplied level name is not recognised. Use trace/debug/info/notice/warn/error. ## is\_debug if($logger->is_debug()) { ... } Returns a true value when the logger is configured at `debug` level or below (i.e. debug messages will actually be emitted). Provided for compatibility with [Log::Any](https://metacpan.org/pod/Log%3A%3AAny). ### Arguments None. ### Returns `1` if the current level threshold includes debug (or trace) messages; `0` otherwise. ### Example if($logger->is_debug()) { $logger->debug('Expensive diagnostic: ' . Dumper(\%state)); } ### API Specification #### Input {} (no arguments) #### Output { type => 'boolean' } ## messages my $aref = $logger->messages(); Returns a reference to a shallow copy of all messages emitted through this logger since it was created (or since the last clone). ### Arguments None. ### Returns An array reference of hashrefs, each with keys `level` (string) and `message` (string). ### Side Effects None. The returned array is a copy; modifying it does not affect the internal history. ### Example $logger->info('hello'); my $msgs = $logger->messages(); # $msgs->[0] = { level => 'info', message => 'hello' } ### API Specification #### Input {} (no arguments) #### Output { type => 'arrayref', element_type => { level => SCALAR, message => SCALAR } } ## trace $logger->trace(@messages); $logger->trace(\@messages); Logs a message at `trace` level (the most verbose level, below `debug`). The message is dropped silently when the configured level threshold is above `trace`. ### Arguments - `@messages` One or more strings, or a single array reference. All elements are joined without a separator before storage. ### Returns `$self`, to allow method chaining. ### Side Effects Appends to the internal message history and dispatches to configured backends. ### Example $logger->trace('entering sub foo, args=', join(',', @args)); # Chaining $logger->trace('start')->debug('details')->info('summary'); ### API Specification #### Input { messages => { type => ARRAYREF | SCALAR } } #### Output { type => 'object', class => 'Log::Abstraction' } ## debug $logger->debug(@messages); $logger->debug(\@messages); Logs a message at `debug` level. ### Arguments - `@messages` One or more strings, or a single array reference. ### Returns `$self`, to allow method chaining. ### Side Effects Appends to the internal message history and dispatches to configured backends. ### Example $logger->debug('Query took ', $elapsed, 'ms'); ### API Specification #### Input { messages => { type => ARRAYREF | SCALAR } } #### Output { type => 'object', class => 'Log::Abstraction' } ## info $logger->info(@messages); $logger->info(\@messages); Logs a message at `info` level. ### Arguments - `@messages` One or more strings, or a single array reference. ### Returns `$self`, to allow method chaining. ### Side Effects Appends to the internal message history and dispatches to configured backends. ### Example $logger->info('Server started on port ', $port); ### API Specification #### Input { messages => { type => ARRAYREF | SCALAR } } #### Output { type => 'object', class => 'Log::Abstraction' } ## notice $logger->notice(@messages); $logger->notice(\@messages); Logs a message at `notice` level (higher priority than `info`, lower than `warn`). ### Arguments - `@messages` One or more strings, or a single array reference. ### Returns `$self`, to allow method chaining. ### Side Effects Appends to the internal message history and dispatches to configured backends. ### Example $logger->notice('Configuration reloaded'); ### API Specification #### Input { messages => { type => ARRAYREF | SCALAR } } #### Output { type => 'object', class => 'Log::Abstraction' } ## warn $logger->warn(@messages); $logger->warn(\@messages); $logger->warn(warning => $text); $logger->warn({ warning => $text }); $logger->warn(warning => \@parts); Logs a warning message. Also dispatches to syslog and/or email backends when those are configured. Falls back to `Carp::carp` when no logger backend is set. A `warn()` call with an empty or all-undef argument list is a silent no-op. ### Arguments - `@messages` A plain list of strings joined without separator, **or** a named `warning` parameter whose value may be a string or an array reference of strings. ### Returns `$self`, to allow method chaining. ### Side Effects Appends to internal message history. Writes to all configured backends. May call `Carp::carp` if `carp_on_warn` is set or no backend is active. ### Example $logger->warn('Disk usage is high'); $logger->warn(warning => 'Connection reset', ' retrying'); $logger->warn({ warning => ['Part A', 'Part B'] }); ### API Specification #### Input # Named form { warning => { type => SCALAR | ARRAYREF } } # Plain-list form { messages => { type => ARRAYREF } } #### Output { type => 'object', class => 'Log::Abstraction' } ### MESSAGES (no croak/carp messages from this method itself; see _high_priority) ## error $logger->error(@messages); $logger->error(warning => $text); Logs an error-level message. Behaves identically to `warn()` but at the `error` level, which triggers `Carp::croak` if `croak_on_error` is set or no logger backend is active. ### Arguments Same argument forms as `warn()`. ### Returns `$self`, to allow method chaining. Note: if `croak_on_error` is set, the method never returns -- execution unwinds via `Carp::croak`. ### Side Effects Same as `warn()` plus optional `Carp::croak` escalation. ### Example $logger->error('Fatal: database unavailable'); ### API Specification #### Input { warning => { type => SCALAR | ARRAYREF, optional => 1 } } #### Output { type => 'object', class => 'Log::Abstraction' } ### MESSAGES Croak Meaning / Action ---------------------------------------- ------------------------------------------ (the error message text itself) croak_on_error is set, or no backend is active. The call stack is unwound. ## fatal $logger->fatal(@messages); Synonym for `error()`. Provided for compatibility with logging frameworks that use `fatal` as the highest-severity level name. ### Arguments Same as `error()`. ### Returns `$self`. ### Side Effects Same as `error()`. ### Example $logger->fatal('Unrecoverable state; aborting'); ### API Specification #### Input { warning => { type => SCALAR | ARRAYREF, optional => 1 } } #### Output { type => 'object', class => 'Log::Abstraction' } ### MESSAGES Same as `error()`. # EXAMPLES ## CSV file logging for BI import The code-reference backend gives you full control over the output format. The example below writes every message at `trace` level and above as a CSV row to a file, producing output that can be loaded directly into a spreadsheet or BI tool (Tableau, Power BI, Metabase, etc.). Each row contains: `timestamp`, `level`, `class`, `file`, `line`, `message`. use Log::Abstraction; my $csv_file = 'app_events.csv'; # Write the header row once (skip if the file already exists and has data). unless (-s $csv_file) { open my $fh, '>', $csv_file or die "Cannot open $csv_file: $!"; print $fh qq{timestamp,level,class,file,line,message\n}; close $fh; } # Helper: quote a single CSV field (escapes embedded double-quotes). my $csv_field = sub { my $v = defined $_[0] ? $_[0] : ''; $v =~ s/"/""/g; return qq{"$v"}; }; my $logger = Log::Abstraction->new( level => 'trace', # capture everything from trace upwards logger => sub { my $args = $_[0]; my $timestamp = POSIX::strftime('%Y-%m-%dT%H:%M:%SZ', gmtime); my $message = join(' ', @{ $args->{message} // [] }); open my $fh, '>>', $csv_file or return; print $fh join(',', $csv_field->($timestamp), $csv_field->($args->{level}), $csv_field->($args->{class}), $csv_field->($args->{file}), $csv_field->($args->{line}), $csv_field->($message), ), "\n"; close $fh; }, ); $logger->trace('application started'); $logger->info('user logged in', { user => 'alice' }); $logger->warn({ warning => 'disk usage above 80%' }); The resulting `app_events.csv` looks like: timestamp,level,class,file,line,message "2026-05-27T14:00:00Z","trace","Log::Abstraction","app.pl","42","application started" "2026-05-27T14:00:01Z","info","Log::Abstraction","app.pl","43","user logged in" "2026-05-27T14:00:02Z","warn","Log::Abstraction","Log/Abstraction.pm","820","disk usage above 80%" Note: `class` is always `Log::Abstraction` (or the subclass name if you subclass the module). For `trace`, `debug`, `info`, and `notice` calls, `file` and `line` resolve to the caller's source location. For `warn` and `error` calls the extra `_high_priority` stack frame shifts the resolution one level inward, so `file` and `line` point into the module rather than the calling script. For production use, consider replacing the manual `$csv_field` quoting with [Text::CSV](https://metacpan.org/pod/Text%3A%3ACSV) for correct handling of embedded newlines and other edge cases. If you also want real-time alerting on critical events, add the email logic directly inside the code-ref callback -- test `$args->{level}` and call your mailer for `warn` / `error` messages while still writing the CSV row for every message. Alternatively, use the `sendmail` hash-ref backend on its own (without the code-ref) and add a `level` key to restrict emails to warn-and-above: my $logger = Log::Abstraction->new( level => 'warn', logger => { sendmail => { host => 'smtp.example.com', to => 'ops@example.com', from => 'logger@example.com', subject => 'Application alert', level => 'warn', # only email at warn level and above min_interval => 300, # at most one alert email per 5 minutes }, }, ); Note: the `sendmail` backend writes the module's standard text format, not CSV. To produce CSV rows _and_ send email alerts from the same logger, embed both the CSV-write and the mail-send logic inside a single code-ref callback as described above. # AUTHOR Nigel Horne `njh@nigelhorne.com` # SEE ALSO - [Test Dashboard](https://nigelhorne.github.io/Log-Abstraction/coverage/) # SUPPORT This module is provided as-is without any warranty. Please report any bugs or feature requests to `bug-log-abstraction at rt.cpan.org`, or through the web interface at [http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Log-Abstraction](http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Log-Abstraction). I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. You can find documentation for this module with the perldoc command. perldoc Log::Abstraction You can also look for information at: - MetaCPAN [https://metacpan.org/dist/Log-Abstraction](https://metacpan.org/dist/Log-Abstraction) - RT: CPAN's request tracker [https://rt.cpan.org/NoAuth/Bugs.html?Dist=Log-Abstraction](https://rt.cpan.org/NoAuth/Bugs.html?Dist=Log-Abstraction) - CPAN Testers' Matrix [http://matrix.cpantesters.org/?dist=Log-Abstraction](http://matrix.cpantesters.org/?dist=Log-Abstraction) - CPAN Testers Dependencies [http://deps.cpantesters.org/?module=Log::Abstraction](http://deps.cpantesters.org/?module=Log::Abstraction) ## FORMAL SPECIFICATION ### new ┌─ LogState ────────────────────────────────────────────────── │ level : ℤ │ messages : seq { level : STRING; message : STRING } │ logger : LOGGER └───────────────────────────────────────────────────────────── ┌─ New ─────────────────────────────────────────────────────── │ args? : Args │ result! : LogState ├───────────────────────────────────────────────────────────── │ result!.level = syslog_values(args?.level ∨ 'warning') │ result!.messages = ⟨⟩ │ result!.logger = args?.logger └───────────────────────────────────────────────────────────── Clone operation (called on an existing object): ┌─ Clone ───────────────────────────────────────────────────── │ ΔLogState │ overrides? : Args ├───────────────────────────────────────────────────────────── │ result!.level = syslog_values(overrides?.level ∨ level) │ result!.messages = messages {deep copy} │ result!.logger = overrides?.logger ∨ logger └───────────────────────────────────────────────────────────── ### level ┌─ LevelGet ───────────────────────────────────────────────── │ ΞLogState │ result! : ℤ ├───────────────────────────────────────────────────────────── │ result! = level │ 0 ≤ result! ∧ result! ≤ 7 └───────────────────────────────────────────────────────────── ┌─ LevelSet ───────────────────────────────────────────────── │ ΔLogState │ new_level? : STRING ├───────────────────────────────────────────────────────────── │ new_level? ∈ dom(syslog_values) │ level' = syslog_values(new_level?) └───────────────────────────────────────────────────────────── ### is\_debug ┌─ IsDebug ────────────────────────────────────────────────── │ ΞLogState │ result! : BOOLEAN ├───────────────────────────────────────────────────────────── │ result! = (level ≥ syslog_values('debug')) └───────────────────────────────────────────────────────────── ### messages ┌─ Messages ───────────────────────────────────────────────── │ ΞLogState │ result! : seq { level : STRING; message : STRING } ├───────────────────────────────────────────────────────────── │ result! = messages └───────────────────────────────────────────────────────────── ### trace ┌─ Trace ──────────────────────────────────────────────────── │ ΔLogState │ msg? : seq STRING ├───────────────────────────────────────────────────────────── │ msg? ≠ ⟨⟩ │ syslog_values('trace') ≤ level │ messages' = messages ⌢ ⟨{level ↦ 'trace', message ↦ ⊕(msg?)}⟩ └───────────────────────────────────────────────────────────── ### debug ┌─ Debug ──────────────────────────────────────────────────── │ ΔLogState │ msg? : seq STRING ├───────────────────────────────────────────────────────────── │ msg? ≠ ⟨⟩ │ syslog_values('debug') ≤ level │ messages' = messages ⌢ ⟨{level ↦ 'debug', message ↦ ⊕(msg?)}⟩ └───────────────────────────────────────────────────────────── ### info ┌─ Info ───────────────────────────────────────────────────── │ ΔLogState │ msg? : seq STRING ├───────────────────────────────────────────────────────────── │ msg? ≠ ⟨⟩ │ syslog_values('info') ≤ level │ messages' = messages ⌢ ⟨{level ↦ 'info', message ↦ ⊕(msg?)}⟩ └───────────────────────────────────────────────────────────── ### notice ┌─ Notice ─────────────────────────────────────────────────── │ ΔLogState │ msg? : seq STRING ├───────────────────────────────────────────────────────────── │ msg? ≠ ⟨⟩ │ syslog_values('notice') ≤ level │ messages' = messages ⌢ ⟨{level ↦ 'notice', message ↦ ⊕(msg?)}⟩ └───────────────────────────────────────────────────────────── ### warn ┌─ Warn ───────────────────────────────────────────────────── │ ΔLogState │ msg? : seq STRING | { warning : STRING | seq STRING } ├───────────────────────────────────────────────────────────── │ msg? ≠ ∅ ∧ join(msg?) ≠ '' │ syslog_values('warn') ≤ level │ messages' = messages ⌢ ⟨{level ↦ 'warn', message ↦ join(msg?)}⟩ └───────────────────────────────────────────────────────────── ### error ┌─ Error ──────────────────────────────────────────────────── │ ΔLogState │ msg? : seq STRING | { warning : STRING | seq STRING } ├───────────────────────────────────────────────────────────── │ msg? ≠ ∅ ∧ join(msg?) ≠ '' │ syslog_values('error') ≤ level │ messages' = messages ⌢ ⟨{level ↦ 'error', message ↦ join(msg?)}⟩ │ croak_on_error = 1 ⟹ execution_continues = false └───────────────────────────────────────────────────────────── ### fatal fatal ≡ error (identical operation schema) # COPYRIGHT AND LICENSE Copyright (C) 2025-2026 Nigel Horne Usage is subject to the GPL2 licence terms. If you use it, please let me know.