Log-Log4perl-Layout-JSON-Readable/lib/Log/Log4perl/Layout/JSON/Readable.pm
package Log::Log4perl::Layout::JSON::Readable;
use strict;
use warnings;
use parent 'Log::Log4perl::Layout::JSON';
our $VERSION = '1.0.3'; # VERSION
# ABSTRACT: JSON layout, but some fields always come first
use Class::Tiny +{
first_fields => sub { [qw(time pid level)] },
};
sub BUILDARGS {
my ($class, @etc) = @_;
# the parent class does not have a BUILDARGS, but it may get one
# in the future, let's handle both cases
my $args = $class->maybe::next::method(@etc) || $etc[0];
if (my $first_fields = delete $args->{first_fields}) {
$args->{first_fields} = [
grep { length }
map { my $v = $_; $v =~ s/\s+//g; $v }
split /\s*,\s*/,
$first_fields->{value},
];
}
return $args;
}
# HACK!! the parent class C<warn>s when it sees an argument it doesn't
# expect. To prevent that, we consume it first
sub BUILDALL {
my ($self, $args, @etc) = @_;
if (my $first_fields = delete $args->{first_fields}) {
$self->first_fields($first_fields);
}
return $self->next::method($args,@etc);
}
sub render {
my $self = shift;
my $json = $self->SUPER::render(@_);
if (my $first_fields = $self->first_fields) {
for my $key (reverse @{$first_fields}) {
_move_field_first(\$json, $key);
}
}
return $json;
}
sub _move_field_first {
my ($json_ref, $key) = @_;
# a JSON value starts with double quotes, and ends with a
# non-backslash-escaped double quotes
${$json_ref} =~ s/^{(.+?),("$key":".*?(?<!\\)")/\{$2,$1/;
return;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Log::Log4perl::Layout::JSON::Readable - JSON layout, but some fields always come first
=head1 VERSION
version 1.0.3
=head1 SYNOPSIS
Example configuration:
log4perl.appender.Example.layout = Log::Log4perl::Layout::JSON::Readable
log4perl.appender.Example.layout.field.message = %m{chomp}
log4perl.appender.Example.layout.field.category = %c
log4perl.appender.Example.layout.field.time = %d
log4perl.appender.Example.layout.field.pid = %P
log4perl.appender.Example.layout.field.level = %p
log4perl.appender.Example.layout.canonical = 1
log4perl.appender.Example.layout.first_fields = time, pid, level
=head1 DESCRIPTION
This layout works just like L<< C<Log::Log4perl::Layout::JSON> >>, but
it always prints some fields first, even with C<< canonical => 1 >>.
=for Pod::Coverage first_fields
The fields to print first are set via the C<first_fields> attribute,
which is a comma-separated list of field names (defaults to C<time,
pid, level>, like in the synopsis).
So, instead of:
{"category":"App.Minion.stats","level":"TRACE","message":"Getting metrics","pid":"6689","time":"2018-04-04 13:57:23,990"}
you get:
{"time":"2018-04-04 13:57:23,990","pid":"6689","level":"TRACE","category":"App.Minion.stats","message":"Getting metrics"}
which is more readable (e.g. for the timestamp) and usable (e.g. for
the pid).
=head1 AUTHORS
=over 4
=item *
Johan Lindstrom <Johan.Lindstrom@broadbean.com>
=item *
Gianni Ceccarelli <gianni.ceccarelli@broadbean.com>
=back
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2021 by BroadBean UK, a CareerBuilder Company.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut