NAME

Log::Munger - Extracts structured fields from log records using YAML rule files.

VERSION

Version 0.0.1

SYNOPSIS

use Log::Munger;

my $munger = Log::Munger->new( 'rules' => [ 'sshd', 'postfix' ] );

# a decoded record, such as what journald or syslog-ng JSON output gives you
my $fields = $munger->process_item(
    'item' => {
        'PROGRAM' => 'sshd',
        'MESSAGE' => 'Failed password for root from 203.0.113.7 port 44444 ssh2',
    }
);
# $fields = { ssh_method => 'password', ssh_user => 'root', ssh_src_ip => '203.0.113.7', ... }

# a bare string is matched as the MESSAGE field, so whole log lines work as-is
my $access = $munger->process_item( 'item' => $raw_apache_line );

Each rule file is a YAML document holding a library of named regexps and an ordered list of rules built from them. A record is walked against those rules in load order, and the first one whose gates pass and whose pattern matches returns its named captures. Captured fields can then be broken down further, looked up in a GeoIP database, and coerced to numbers.

This is the same idea as grok for Logstash, minus the Logstash. See Log::Munger::LogProcessor for the matching engine and the log_munger command for the CLI.

METHODS

new

Creates a new munger. Rule files may be supplied up front, added later via "load", or both.

Each name is resolved through Log::Munger::WhichRuleFile, so it may be a bare name such as sshd or a path. A rule file that fails to load or compile is fatal, which means a broken file is caught here rather than silently producing no matches later on.

my $munger = Log::Munger->new( 'rules' => [ 'base', 'postfix' ] );
my $munger = Log::Munger->new( 'rules' => ['postfix'], 'geoip' => '/path/to/GeoLite2-City.mmdb' );

- rules :: Rule files to load. The taken value is an array ref.
    Default :: undef

- geoip :: Path to a MaxMind .mmdb database. When set, rules that flag
    captured fields with a C<geoip:> list have those looked up, with the
    result stored under C<< $result->{geoip}{$field} >>. Needs
    L<IP::Geolocation::MMDB>, which is only loaded when this is used.
    Default :: undef (geoip disabled)

load

Loads an additional rule file and rebuilds the processor. Rules from files loaded later are tried after rules from files loaded earlier, so load order is match priority.

Returns 1. Dies if the file cannot be found, loaded, or compiled.

- file :: The file to load. Required.
    Default :: undef

$munger->load( 'file' => 'sshd' );

process_item

Runs a decoded log record through the loaded rules and returns the named captures of the first matching rule, or undef if nothing matched (or no rules have been loaded).

- item :: The decoded log record (a hash ref), or a bare string. A bare
    string is treated as a raw log line and matched as the C<MESSAGE> field.
    If given, C<item> takes precedence over the field args below.
    Default :: undef

- message :: The raw log message, assembled into C<< { MESSAGE => ... } >>.
    Default :: undef

- program :: Optional C<PROGRAM> field (the usual daemon-rule gate).
    Default :: undef

- priority :: Optional C<PRIORITY> field.
    Default :: undef

- facility :: Optional C<FACILITY> field.
    Default :: undef

my $fields = $munger->process_item( 'item' => $json );
my $fields = $munger->process_item( 'item' => $raw_access_log_line );

# from a syslog reader that already has the fields split out (e.g. baphomet):
my $fields = $munger->process_item(
    'message'  => $message,
    'program'  => $program,
    'priority' => $priority,
    'facility' => $facility,
);

Returns a hash ref of the winning rule's named captures, or undef if nothing matched. Never dies: an exception during matching comes back as undef rather than taking down the stream. That bounds failures, not runtime -- a pattern prone to catastrophic backtracking can still burn CPU on a hostile line.

explain_item

Like "process_item", but reports which rule and pattern fired alongside the fields. Handy when a rule file is not matching what you expected it to. Takes the same args and never dies. See "explain_item" in Log::Munger::LogProcessor.

my $why = $munger->explain_item( 'item' => $json );

# { matched => 0 }
# { matched => 1, rule => 'sshd', pattern => 1, field => 'MESSAGE', fields => { ... } }

AUTHOR

Zane C. Bowers-Hadley, <vvelox at vvelox.net>

SEE ALSO

BUGS

Report bugs and feature requests through GitHub at https://github.com/LilithSec/Log-Munger, or to bug-log-munger at rt.cpan.org.

SUPPORT

perldoc Log::Munger

You can also find this distribution at:

LICENSE AND COPYRIGHT

This software is Copyright (c) 2026 by Zane C. Bowers-Hadley.

This is free software, licensed under:

The GNU Lesser General Public License, Version 3, June 2007