I'd like to have modules managing their logging at runtime, but without having everything referring to a single monolithic config file. When dealing with processes running under different permissions, I really don't want to deal with each process needing to be able to access every log on the system when they're only writing to a subset of them.
However, I'm not finding much documentation in the Log4perl manual on how to initialize additional appenders from a configuration file at runtime. http://metacpan.org/pod/Log::Log4perl::Appender references an add_appender method, but that works on instantiated appender objects instead of conf files. It also doesn't define the logger objects and the logger->appender relations.
I tried having each package init from its own conf, but that simply clobbers the existing config each time it's initalized. What I'd like to do is something along the lines of:
my $foo = Foo->new() ## Checks Log::Log4perl::initialized(), sees that it
## hasn't been initalized yet, inits Log4perl from foo.conf
my $bar = Bar->new() ## Checks Log::Log4perl::initialized(), sees that it
## has been initalized. Adds appenders and loggers defined
## in bar.conf into the initialized configuration
How can I parse and add the configuration into the current config?
Edit: Probalem with using a package variable is that this is just a Moose role being consumed by various classes, pretty much just a MooseX::Role::Parameterized version of Ether's answer in Making self-logging modules with Log::Log4perl. Thus, my logger is getting composed into the library consuming it, and I don't have a global variable I can work on each time I use it.
Though..
If I declare a global variable outside of the MooseX::Role::Parameterized role block, would each and every class that consumes the role be using that same conf variable?
You can remember what config files was already loaded (%log_configs
hash in code below). When new class arrives, you can reread all configs, merge it together and init Log::Log4perl
again using string reference parameter to init
.
I generally prefer having a single log configuration per application, because of easier maintenance and reload capability.
package Logger;
use Moose::Role;
use Log::Log4perl;
our %log_configs = ();
around BUILDARGS => sub {
my $orig = shift;
my $class = shift;
my $config_name = lc($class) . '.conf';
# if the config is not integrated yet
if(! defined $log_configs{$config_name}) {
$log_configs{$config_name} = 1;
# reload all configs including new one
my $config_text = '';
for my $file (sort keys %log_configs) {
$config_text .= "\n" . do {
local $/; # slurp
unless(open my $fh, "<", $file) {
warn "$file could not be open\n";
'';
}
else {
<$fh>
}
};
}
# refresh config
Log::Log4perl::init(\$config_text);
}
return $class->$orig(@_);
};
package Foo;
use Moose;
with 'Logger';
use Log::Log4perl ':easy';
sub BUILD {
ERROR 'Foo reporting';
}
package Bar;
use Moose;
with 'Logger';
use Log::Log4perl ':easy';
sub BUILD {
INFO 'Bar reporting';
}
package main;
my $foo = Foo->new;
my $bar = Bar->new;
While I was hoping to avoid it, if I parse the config files myself I can then access the configuration in perl via the API documented in http://search.cpan.org/perldoc?Log::Log4perl. Namely,
########################
# Initialization section
########################
use Log::Log4perl;
use Log::Log4perl::Layout;
use Log::Log4perl::Level;
# Define a category logger
my $log = Log::Log4perl->get_logger("Foo::Bar");
# Define a layout
my $layout = Log::Log4perl::Layout::PatternLayout->new("[%r] %F %L %m%n");
# Define a file appender
my $file_appender = Log::Log4perl::Appender->new(
"Log::Log4perl::Appender::File",
name => "filelog",
filename => "/tmp/my.log");
# Define a stdout appender
my $stdout_appender = Log::Log4perl::Appender->new(
"Log::Log4perl::Appender::Screen",
name => "screenlog",
stderr => 0);
# Have both appenders use the same layout (could be different)
$stdout_appender->layout($layout);
$file_appender->layout($layout);
$log->add_appender($stdout_appender);
$log->add_appender($file_appender);
$log->level($INFO);
While the other method works, there's too many caveats for me to be comfortable using it (Gee I used this library, why'd my logging stop?) -- it's just too surprising for my tastes.
Instead, I think I'm going to see if I can't get from config file to Log::Log4perl state by looking through how to use Log::Log4perl::Config::PropertyConfigurator, which is delegated to by ->init when parsing a config file is needed. If I go over the data structure that returns, i can compare changes to the initialization on a logger-by-logger and appender-by-appender basis and modify the initialized state appropriately, handle namespace collisions properly, etc.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With