8

Is there a way to use Log::Log4perl to make a smart self-logging module that logs its operations to a file even in the absence of the calling script not initializing Log4perl? As far as I can tell from the documentation, the only way to use Log4perl is to initialize it in the running script from a configuration, then modules implementing Log4perl calls log themselves based on the caller's Log4perl config.

Instead, I'd like the modules to provide a default initialization config for Log4perl. This would provide the default file appender for the module's category. Then, I could override this behavior by initing Log4perl in the caller with a different config if needed, and everything would hopefully just work.

Is this sort of defensive logging behavior possible or am I going to need to rely on initing Log4perl in every .pl script that calls the module I want logged?

Ether
  • 53,118
  • 13
  • 86
  • 159
Oesor
  • 6,632
  • 2
  • 29
  • 56

2 Answers2

7

I do this in a custom Log role in Moose (irrelevant complicated code removed):

package MyApp::Role::Log;

use Moose::Role;
use Log::Log4perl;

my @methods = qw(
    log trace debug info warn error fatal
    is_trace is_debug is_info is_warn is_error is_fatal
    logexit logwarn error_warn logdie error_die
    logcarp logcluck logcroak logconfess
);

has _logger => (
    is => 'ro',
    isa => 'Log::Log4perl::Logger',
    lazy_build => 1,
    handles => \@methods,
);

around $_ => sub {
    my $orig = shift;
    my $this = shift;

    # one level for this method itself
    # two levels for Class:;MOP::Method::Wrapped (the "around" wrapper)
    # one level for Moose::Meta::Method::Delegation (the "handles" wrapper)
    local $Log::Log4perl::caller_depth;
    $Log::Log4perl::caller_depth += 4;

    my $return = $this->$orig(@_);

    $Log::Log4perl::caller_depth -= 4;
    return $return;

} foreach @methods;

method _build__logger => sub {
    my $this = shift;

    my $loggerName = ref($this);
    Log::Log4perl->easy_init() if not Log::Log4perl::initialized();
    return Log::Log4perl->get_logger($loggerName)
};

As you can see, the log object is self-initializing -- if Log::Log4perl->init has not been called, then easy_init is called. You could easily modify this to allow each module to customize its logger -- I do so with optional role parameters, with ref($this) as the default fallback.

PS. You may also want to look at MooseX::Log::Log4perl, which is where I started before I used the logger role above. Someday when I get around to it I will submit some much-needed patches to that MX module to incorporate some features I have added.

Ether
  • 53,118
  • 13
  • 86
  • 159
  • 1
    Luckily, this'll be logging Moose objects, so it looks like it'll be simple enough to get going. Thanks! – Oesor Jun 10 '10 at 21:58
  • @Oesor: hooray, Moose FTW! :) – Ether Jun 10 '10 at 22:21
  • may I inquire as to what an MX module is? I know PP and XS, is MX some shorthand for Moose? – DVK Jun 10 '10 at 23:19
  • @DVK: MX is just shorthand for MooseX, the namespace commonly used for Moose extensions that are not part of Moose core. – Ether Jun 10 '10 at 23:30
  • @Ether: Can the `log4perl` methods be used within `BUILDARGS` or any other "pre" object instantiation method? (e.g.: `$class->info("mumble");` fails) --- Also for other folks puzzling out the above example: the snippet wants `use Method::Signatures` or `use Method::Signatures::Simple` – ericx May 05 '15 at 21:39
  • @ericx in BUILDARGS, the object hasn't been instantiated yet - there is no `$self`, so there is no `_logger` object defined to call the method on. You'd have to call the l4p sub manually. And there is nothing in the code snippet in this answer that needs Method::Signatures(::*) - what is making you think it's required? – Ether May 06 '15 at 22:46
  • @ether: I was hoping to push some `debug` level logs about the build process out of `BUILDARGS`. I appreciate that the hash isn't blessed yet; but I was hoping there was some clever way to access `_logger` from the partial object. Moo/Moose doesn't seem to be big on the idea of Class methods or Class ivars, but that's where I was trying to go. – ericx May 07 '15 at 18:55
  • @ether: I'm new to `Moose`; and I chose to use `Moo`. `method` throws vague syntax errors (`"my" variable $self masks earlier declaration in same statement`) without `Method::Signatures`. Similar problem using `Mouse`. My mistake assuming it was also a `Moose` problem. – ericx May 07 '15 at 19:02
  • @ericx this is getting pretty complicated now and is hard to explain here.. if you can hop on irc.perl.org #moose, I or someone else can help you out. – Ether May 08 '15 at 19:35
1

The short answer is to call Log::Log4perl::initialized(); at some point and if it is false, set up some default logging.

The tricky part is "some point."

You can't do it in BEGIN {}, because then the main script's will stomp your initialisation even though you created unnessary files. You don't want to do it before each call to get_logger() because that is wasteful. So you should do it during the module's initialisation, say sub new or sub init.

Leolo
  • 1,327
  • 9
  • 14