是否可以注册一个函数来使用 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 部分。
在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 部分。