johnnywang has asked for the wisdom of the Perl Monks concerning the following question:

The following situation has been bugging me for a long time, would like to seek your insights. Logging and Configuration are two things most of my modules/scripts use (I use Log::Log4perl and AppConfig.) This is ok for standalone scripts, where I simply do something like the following in the beginning:
use strict; use AppConfig; use Log::Log4perl qw(get_logger); my $config = AppConfig->new()->file("app.properties"); Log::Log4perl->init($config->get("log4perl")); # init log my $logger = get_logger(__PACKAGE__); # or some other name
in later part of the code, I can simply call $config and $logger.

The problem is when I try to do similar things to modules, who should construct/init the $config and $logger? It seems to be too much to write the above code to all the module, even too much as a base class. Should the caller of the module always pass in the $config and $logger? There is a pattern called "Inversion of Control" which, among other things, advocates that the creator is responsible for providing all environments, including configuration and logging. Does that make sense here? what do you do?

Replies are listed 'Best First'.
Re: Inversion of control: logging and configuration
by phaylon (Curate) on Feb 22, 2005 at 22:15 UTC
    Well, I handle it this way:
    - A separate config class (with mass export ->export(@list))
    - A separate logging class

    How I use them depends on the module. If it's an internal module fixed into a project, I tend to keep the Config-Information by the package, not by an object, so that every part of the project can (transparently) access the configuration just by use'ing the Config-Module.

    If /it is/ a standalone module, I mostly initialize them at creation time, e.g.
    my $object = new ModuleName( option1 => value1, option2 => value2, );
    and handle the configuration isolated in the module. Maybe it get's additional cget/cset-methods.
    hth,p

    Ordinary morality is for ordinary people. -- Aleister Crowley
Re: Inversion of control: logging and configuration
by stvn (Monsignor) on Feb 22, 2005 at 23:24 UTC

    I wrote the IOC module for just such things. Basically, IOC (once properly configured) constructs a IOC::Registry singleton object from which you can then retrieve all your object instances fully wired together. I wrote an article on it in the Jan. issue of The Perl Journal as well.

    -stvn
Re: Inversion of control: logging and configuration
by Fendaria (Beadle) on Feb 22, 2005 at 23:25 UTC

    I just did almost this exact same thing for a large set of scripts I wrote.

    With regards to Log::Log4perl (which I really like btw) you only need to configure the module it once. Your 'Main' calls Log::Log4perl->init once and then any other package called just needs you to use the module and set up the logging variable. If your doing an OO module, you can even write a base class that sets up your logging variable automatically (I prefer class type instead of __PACKAGE__ for inheritance in OO logging).

    sub new { my $self = shift; my $class = ref($self) || $self; my $self->{logger} = get_logger($class); ... } #following allows $object->debug("debug statement"); #may need log4perl up-stack-call(?) for traceback sub debug { my ($self,$message) = @_; $self->{logger}->debug($message); return $self; }

    If you forget to set up your logger in a script using the module log4perl is should print out a warning that you didn't init it. Log4perl even has an 'easy' mode where it will do the init for you (but I haven't found the defaults to my liking for a production script).

    Another thought is to have your program initialization done in a BEGIN block. Then have your modules check in an INIT block that the logger/config variables they require exist. I don't like using a BEGIN for this type of a solution but it may fit your needs.

    A last option is to define a generic 'startup module' with a 'initialization' routine that is called once. Each module that needs logging/configuration would use the package and call this function in a BEGIN or INIT block. I like this idea except that I generally like per-program config files and changing config/logging information via Getopts::Long which this idea makes hard to do.

    The large problem I struggled with and couldn't solve was that I wanted configuration parameters to control the logging but then I wanted to log the loading of configuration parameters (chicken and egg)...which is something I still haven't solved.

    Fendaria
Re: Inversion of control: logging and configuration
by ikegami (Patriarch) on Feb 22, 2005 at 22:18 UTC
    Wouldn't it be better if the module's caller (the script) configured the module, rather than having the module use AppConfig? If the caller wants to use AppConfig and pass what it read to the module, so be it.
Re: Inversion of control: logging and configuration
by mpeters (Chaplain) on Feb 22, 2005 at 22:22 UTC
    I usually use a configuration object with a configuration file and provide the path to that configuration file in an $ENV var. Other modules which use that data (database connections, logging levels, etc) pull those values from the configuration object. Then however the project is invoked (ie, mod_perl from startup.pl, or scripts, or whatever) it just sets that $ENV var.
Re: Inversion of control: logging and configuration
by saintmike (Vicar) on Feb 23, 2005 at 18:01 UTC
    Couple of Log::Log4perl's best practices:
    • Call Log::Log4perl->init once, in the main script, not in any module you're using.
    • I'm not sure why you're using AppConfig on top of that, this way, you're giving up on useful functionality like init_and_watch. The most common usage of init() is passing it the name of a Log4perl configuration file.
    • In your modules, use
      # Foo.pm package Foo; use Log::Log4perl qw(:easy); sub foo { DEBUG "Fooing the database"; }
    • In your Log4perl config file (read in by the main script, not the module, via ->init($cfg_file)), you're telling which modules you want logging enabled in:
      # l4p.conf # Where should logging be enabled? log4perl.category.Foo = DEBUG, Logfile log4perl.category.Bar = DEBUG, Logfile # Appenders log4perl.appender.Logfile = Log::Log4perl::Appender::File log4perl.appender.Logfile.filename = test.log log4perl.appender.Logfile.layout = Log::Log4perl::Layout::Simple +Layout
    • If the main program doesn't call init(), the Log4perl statements in Foo.pm will be dormant.
    • If the main script calls init(), logging will be active. This doesn't necessarily mean Foo will be generating output, from the central config file, you will be able to control the amount of logging for each individual module separately (or alltogether, if you like to).
Re: Inversion of control: logging and configuration
by Thilosophy (Curate) on Feb 23, 2005 at 00:16 UTC
    I hate to admit it, but the Java folks are better off here. They have the Jakarta Commons Logging package, which is a very light-weight wrapper around the various logging frameworks. An application can be written using Commons Logging and will work with any of the supported implementations. Configuration is done by the user at deploy-time and transparent to the application code. Even if no configuration is done, it provides reasonable defaults.

    I think Perl should have something like this.

    Actually, we have something like this, it is called warn, but it is obviously quite limited (no logging categories or log levels). Are there plans for Perl6 to have an extended version of warn which can act as an interface to pluggable logging implementations?