是否可以注册一个函数来使用 Log::Log4perl 预处理日志消息?

Is it possible to register a function to preprocess log messages with Log::Log4perl?

this example中:

$logger->debug({
    filter => \&Data::Dumper::Dumper,
    value  => $ref
});

我可以漂亮地打印我的参考文献而不是 ARRAY(0xFFDFKDJ)。但是每次都敲那么长的代码,太无聊了。我只想:

$logger->preprocessor({
    filter => \&Data::Dumper::Dumper,
    value  => $ref
});

$logger->debug( $ref, $ref2 );
$logger->info( $array );

并且$ref$ref2$array将被Data::Dumper转储。

有办法吗?

UPD
在您的回答的帮助下,我做了 patch

现在你只需:

log4perl.appender.A1.layout=FallbackLayout
log4perl.appender.A1.layout.chain=PatternLayout
log4perl.appender.A1.layout.chain.ConversionPattern=%m%n
log4perl.appender.A1.warp_message = sub { $#_ = 2 if @_ > 3; \
                                       return @_; }
# OR
log4perl.appender.A1.warp_message = main::warp_my_message

sub warp_my_message {
    my( @chunks ) =  @_;

    use Data::Dump qw/ pp /;
    for my $msg ( @chunks ) {
        $msg =  pp $msg   if ref $msg;
    }

    return @chunks;
}

UPD2

或者你可以使用这个小模块

log4perl.appender.SomeAPP.warp_message  = Preprocess::Messages::msg_filter
log4perl.appender.SomeAPP.layout        = Preprocess::Messages

package Preprocess::Messages;

sub msg_filter {
    my @chunks =  @_;

    for my $msg ( @chunks ) {
        $msg =  pp $msg   if ref $msg;
    }

    return @chunks;
};

sub render {
    my $self =  shift;

    my $layout =  Log::Log4perl::Layout::PatternLayout->new(
        '%d %P %p> %c %F:%L %M%n  %m{indent=2}%n%n'
    );

    $_[-1] += 1; # increase level of the caller
    return $layout->render( join $Log::Log4perl::JOIN_MSG_ARRAY_CHAR, @{ shift() }, @_ );
}


sub new {
    my $class = shift;
    $class = ref ($class) || $class;

    return bless {}, $class;
}

1;

是的,当然你可以设置'warp_message = 0'并组合msg_filter并一起渲染。

log4perl.appender.SomeAPP.warp_message  = 0
log4perl.appender.SomeAPP.layout        = Preprocess::Messages

sub render {
    my($self, $message, $category, $priority, $caller_level) = @_;

    my $layout =  Log::Log4perl::Layout::PatternLayout->new(
        '%d %P %p> %c %F:%L %M%n  %m{indent=2}%n%n'
    );

    for my $item ( @{ $message } ) {
        $item =  pp $item   if ref $item;
    }

    $message =  join $Log::Log4perl::JOIN_MSG_ARRAY_CHAR, @$message;
    return $layout->render( $message, $category, $priority, $caller_level+1 );
}

简单的方法:使用warp_message

最简单的方法是创建一个自定义附加程序和 set the warp_message parameter 这样您就可以获得传递给记录器的原始引用:

package DumpAppender;
use strict;
use warnings;

use Data::Dumper;

$Data::Dumper::Indent = 0;
$Data::Dumper::Terse  = 1;

sub new {
    bless {}, $_[0];
}

sub log {
    my($self, %params) = @_;
    print ref($_) ? Dumper($_) : $_ for @{ $params{message} };
    print "\n";
}

package main;   
use strict;
use warnings;

use Log::Log4perl;

Log::Log4perl->init(\q{
    log4perl.rootLogger=DEBUG,Dump
    log4perl.appender.Dump=DumpAppender
    log4perl.appender.Dump.layout=NoopLayout
    log4perl.appender.Dump.warp_message=0
});

my $logger = Log::Log4perl->get_logger;

$logger->debug(
    'This is a string, but this is a reference: ',
    { foo => 'bar' },
);

输出:

This is a string, but this is a reference: {'foo' => 'bar'}

不幸的是,如果您采用这种方法,您将不得不编写自己的代码来处理布局、打开文件等。我不会采用这种方法,除非只需要打印到屏幕的非常简单的项目。

更好的方法:复合appender

更好的方法是编写您自己的 composite appender。复合 appender 在以某种方式操作消息后将消息转发到另一个 appender,例如过滤或缓存它们。使用这种方法,您可以只编写用于转储引用的代码,让现有的 appender 完成繁重的工作。

下面展示了如何编写复合appender。其中一些在 Log::Log4perl::Appender, but I copied much of it from Mike Schilli's Log::Log4perl::Appender::Limit:

的文档中进行了解释
package DumpAppender;   
use strict;
use warnings;

our @ISA = qw(Log::Log4perl::Appender);

use Data::Dumper;

$Data::Dumper::Indent = 0;
$Data::Dumper::Terse  = 1;

sub new {
    my ($class, %options) = @_;

    my $self = {
        appender => undef,
        %options
    };

    # Pass back the appender to be limited as a dependency to the configuration
    # file parser.
    push @{ $options{l4p_depends_on} }, $self->{appender};

    # Run our post_init method in the configurator after all appenders have been
    # defined to make sure the appenders we're connecting to really exist.
    push @{ $options{l4p_post_config_subs} }, sub { $self->post_init() };

    bless $self, $class;
}

sub log {
    my ($self, %params) = @_;

    # Adjust call stack so messages are reported with the correct caller and
    # file
    local $Log::Log4perl::caller_depth = $Log::Log4perl::caller_depth + 2;

    # Dump all references with Data::Dumper
    $_ = ref($_) ? Dumper($_) : $_ for @{ $params{message} };

    $self->{app}->SUPER::log(
        \%params,
        $params{log4p_category},
        $params{log4p_level}
    );
}

sub post_init {
    my ($self) = @_;

    if(! exists $self->{appender}) {
        die "No appender defined for " . __PACKAGE__;
    }

    my $appenders = Log::Log4perl->appenders();
    my $appender = Log::Log4perl->appenders()->{$self->{appender}};

    if(! defined $appender) {
        die "Appender $self->{appender} not defined (yet) when " .
            __PACKAGE__ . " needed it";
    }

    $self->{app} = $appender;
}

package main;

use strict;
use warnings;

use Log::Log4perl;

Log::Log4perl->init(\q{
    log4perl.rootLogger=DEBUG, Dump

    log4perl.appender.Dump=DumpAppender
    log4perl.appender.Dump.appender=SCREEN

    log4perl.appender.SCREEN=Log::Log4perl::Appender::Screen
    log4perl.appender.SCREEN.layout=PatternLayout
    log4perl.appender.SCREEN.layout.ConversionPattern=%d %p %m%n
});

my $logger = Log::Log4perl->get_logger;

$logger->debug(
    'This is a string, but this is a reference: ',
    { foo => 'bar' },
);

输出:

2015/09/14 13:38:47 DEBUG This is a string, but this is a reference: {'foo' => 'bar'}

请注意,如果您通过 API 而不是通过文件初始化 Log::Log4perl,则必须采取一些额外的步骤。这记录在 Log::Log4perl::Appender 文档的 composite appenders 部分。