3

In Perl, is there any way to tell which .pl script has initialized this instance of a module?

Specifically, I'd like to get the name of the script calling a module, which has a Log4perl object it. That way, I'll know which .log file I want to write to within the module.

Am I doing this wrong? If I define the $logger in my .pl script, will any $logger calls within the module write to the same .log file as the calling script?

I don't have any sample code yet, but have been reading up on Log4perl. Basically, if I set an Appender to a file, caller.log, which is the file appender for my calling script, caller.pl, I'd want any logging defined in the custom imported module, to also write to caller.log (implicitly, if possible -- obviously I could just pass the name of the log name when I initialize the module instance).

Is this possible without passing arguments specifying which File Appender the module should write to? Doesn't Log4perl use just one $logger instance?

Also, let me know if I'm way out, and if there's a different approach I should be considering.

Thank you

EDIT: Sorry, after I posted this, I looked at the Related Links, and I guess my search wording just wasn't correct. It looks like this is a pretty good solution: Self logging Perl modules (without Moose)

If anyone has any other ideas, though, please let me know.

EDIT 2: Finally tested, and got it to work as I had wanted -- was a lot easier than was making it out to be, too!

This is my setup, pretty much:

Module.pm

package Module;

use Log::Log4perl qw(get_logger :levels);
use Data::Dumper;

my $logger = get_logger("Module");

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

    my @caller = caller(0);
    $logger->debug("Creating new Module. Called by " . Dumper(\@caller));

    my $object = { 'name' => $name };

    return bless($object, $class);  
}

caller.pl

use Module;
use Log::Log4perl qw(get_logger :levels);
use Data::Dumper;

my $PATH = "$ENV{'APPS'}/$ENV{'OUTDIR'}";
my $SCRIPT = "caller";

my $logger = get_logger("Module");
$logger->level($DEBUG);

my $file_appender = Log::Log4perl::Appender->new("Log::Dispatch::File", 
                        filename=> "$PATH/$SCRIPT.log", 
                        mode => "append",);
$logger->add_appender($file_appender);

my $layout = Log::Log4perl::Layout::PatternLayout->new("%d %p> %F{1}:%L %M - %m%n");
$file_appender->layout($layout);

my $lib = Module->new('Chris');

$logger->info(Dumper($lib));
Community
  • 1
  • 1
Chris Serra
  • 13,226
  • 3
  • 25
  • 25

3 Answers3

2

You could subclass Log4perl, overriding its constructor. In your custom constructor, use caller() to get the filename that called the constructor and put it in $self.

frezik
  • 2,316
  • 15
  • 13
  • Thanks. Not a bad idea. I may have another module is which a base for the module I've discussed -- and that could at least have a pseudo-constructor for creating the $logger instance. – Chris Serra Aug 23 '11 at 14:31
  • How would you then suggest calling $logger, without extracting it from the instance variable? Make it a global variable (not ideal), or would I just have to do (in caller.pl) my $logger = get_logger("SubModule") and SubModule would have my $logger = get_logger("Module") ? – Chris Serra Aug 23 '11 at 14:32
  • You can use SUPER for calling the superclass version: `$class->SUPER::get_logger( @_ );` – frezik Aug 23 '11 at 14:34
  • OK, so if in my calling script, if I do `my $logger = get_logger("Module")` and I specify a $logger of "Module" in Module.pm, then add a File Appender in caller.pl, all $logger calls will write to the one .log file? – Chris Serra Aug 23 '11 at 14:37
  • If your goal is to have everything go to the same log file, then perhaps a better approach is to setup a Log::Log4perl config where `log4perl.rootLogger=LOGFILE`, and then build an appender `LOGFILE`. As long as the config doesn't have any other loggers defined, everything will go there. – frezik Aug 23 '11 at 14:55
  • Well, the log file name is all script-caller specific. So another process, caller2.pl, may require the same Module caller.pl did, but has "caller2.log" to write to. In that case, I'll want any $logger calls within Module to write to "caller2.log" – Chris Serra Aug 23 '11 at 15:06
2

You can put a subroutine hook into @INC that can run arbitrary code, as documented in perldoc -f require. For example:

# UseLogger.pm
package UseLogger;
sub import { unshift @INC, \&UseLogger::log_use }
sub log_use {
    my ($self, $filename) = @_;
    my @c = caller(0);
    print "Module $filename required in file $c[1] line $c[2]\n";
    return 0;
}
1;

$ perl -MUseLogger my_script.pl
Module feature.pm required in file my_script.pl line 2
Module Encode.pm required in file my_script.pl line 5
Module XSLoader.pm from /usr/lib/perl5/5.14.0/cygwin-thread-multi-64int/Encode.pm line 13
...
mob
  • 117,087
  • 18
  • 149
  • 283
  • Interesting. I think I can get away with the default Log4perl use -- using the logger defined in the Module class, and adding FileAppenders in the calling scripts as needed. – Chris Serra Aug 23 '11 at 16:47
2

$0 contains the path to the script. You can use File::Basename's basename if you want to want the file name component.

ikegami
  • 367,544
  • 15
  • 269
  • 518
  • Duh! I knew that! :p but figured that was for the actual file $0 was in -- didn't know it was related to the calling script (but tested, and sure enough..) – Chris Serra Aug 23 '11 at 20:02
  • @Chris Serra, `__FILE__` is the file that contains `__FILE__`. – ikegami Aug 23 '11 at 20:38
  • @Chris Serra, There's no such thing as a "calling script". A module is *loaded*, and it's not necessarily loaded by a *script*. I'm assuming you want the path to the script Perl is executing. – ikegami Aug 23 '11 at 20:40
  • Good point, ikegami. Only time there'd be a true calling script, is if you ised system() or exec() within one script to run another. But in this case, I am loading a module. – Chris Serra Aug 24 '11 at 07:32