Sign In/My Account | View Cart  
advertisement


Listen Print

Retire your debugger, log smartly with Log::Log4perl!
by Michael Schilli | Pages: 1, 2, 3, 4

Logger Categories

In Log::Log4perl, every logger has a category assigned to it. Logger Categories are a way of identifying loggers in different parts of the system in order to change their behavior from a central point, typically in the system startup section or a configuration file.

Every logger has has its place in the logger hierarchy. Typically, this hierarchy resembles the class hierarchy of the system. So if your system defines a class hierarchy Groceries, Groceries::Food and Groceries::Drinks, then chances are that your loggers follow the same scheme.

To obtain a logger that belongs to a certain part of the hierarchy, just call get_logger with a string specifying the category:

    ######### System initialization section ###
    use Log::Log4perl qw(get_logger :levels);
    
    my $food_logger = get_logger("Groceries::Food");
    $food_logger->level($INFO); 

This snippet is from the initialization section of the system. It defines the logger for the category Groceries::Food and sets its priority to INFO with the level() method.

Without the :easy target, we have to pass the arguments get_logger and :levels to use Log::Log4perl in order to get the get_logger function and the level scalars ($DEBUG, $INFO, etc.) imported to our program.

Later, most likely inside functions or methods in a package called Groceries::Food, you'll want to obtain the logger instance and send messages to it. Here's two methods, new() and consume(), that both grab the (yes, one) instance of the Groceries::Food logger in order to let the user know what's going on:

    ######### Application section #############
    package Groceries::Food;
    
    use Log::Log4perl qw(get_logger);
    
    sub new {
        my($class, $what) = @_;
        
        my $logger = get_logger("Groceries::Food");
        
        if(defined $what) {
            $logger->debug("New food: $what");
            return bless { what => $what }, $class;
        }
    
        $logger->error("No food defined");
        return undef;
    }
    
    sub consume {
        my($self) = @_;
        
        my $logger = get_logger("Groceries::Food");
        $logger->info("Eating $self->{what}");
    }

Since we've defined the Groceries::Food logger earlier to carry priority $INFO, all messages of priority INFO and higher are going to be logged, but DEBUG messages won't make it through -- at least not in the Groceries::Food part of the system.

So do you have to initialize loggers for all possible classes of your system? Fortunately, Log::Log4perl uses inheritance to make it easy to specify the behavior of entire armies of loggers. In the above case, we could have just said:

    ######### System initialization section ###
    use Log::Log4perl qw(get_logger :levels);
    
    my $food_logger = get_logger("Groceries");
    $food_logger->level($INFO); 

and not only the logger defined with category Groceries would carry the priority INFO, but also all of its descendants -- loggers defined with categories Groceries::Food, Groceries::Drinks::Beer and all of their subloggers will inherit the level setting from the Groceries parent logger (see figure 1).

Figure 1
Figure 1: Explicitely set vs. inherited priorities

Of course, any child logger can choose to override the parent's level() setting -- in this case the child's setting takes priority. We'll talk about typical use cases shortly.

At the top of the logger hierarchy sits the so-called root logger, which doesn't have a name. This is what we've used earlier with the :easy target: It initializes the root logger that we will retrieve later via get_logger() (without arguments). By the way, nobody forces you to name your logger categories after your system's class hierarchy. But if you're developing a system in object-oriented style, then using the class hierarchy is usually the best choice. Think about the people taking over your code one day: The class hierarchy is probably what they know up front, so it's easy for them to tune the logging to their needs.

Let's summarize: Every logger belongs to a category, which is either the root category or one of its direct or indirect descendants. A category can have several children but only one parent, except the root category, which doesn't have a parent. In the system's initialization section, loggers can define their priority using the level() method and one of the scalars $DEBUG, $INFO, etc. which can be imported from Log::Log4perl using the :levels target.

While loggers must be assigned to a category, they may choose not to set a level. If their actual level isn't set, then they inherit the level of the first parent or ancestor with a defined level. This will be their effective priority. At the top of the category hierarchy resides the root logger, which always carries a default priority of DEBUG. If no one else defines a priority, then all unprioritized loggers inherit their priority from the root logger.

Categories allow you to modify the effective priorities of all your loggers in the system from a central location. With a few commands in the system initialization section (or, as we will see soon, in a Log::Log4perl configuration file), you can remote-control low-level debugging in a small system component without changing any code. Category inheritance enables you to modify larger parts of the system with just a few keystrokes.

Appenders

Related Reading

Programming Perl
By Larry Wall, Tom Christiansen, Jon Orwant

But just a logger with a priority assigned to it won't log your message anywhere. This is what appenders are for. Every logger (including the root logger) can have one or more appenders attached to them, objects, that take care of sending messages without further ado to output devices like the screen, files or the syslog daemon. Once a logger has decided to fire off a message because the incident's effective priority is higher or equal than the logger level, all appenders attached to this logger will receive the message -- in order to forward it to each appender's area of expertise.

Moreover, and this is very important, Log::Log4perl will walk up the hierarchy and forward the message to every appender attached to one of the logger's parents or ancestors.

Log::Log4perl makes use of all appenders defined in the Log::Dispatch namespace, a separate set of modules, created by Dave Rolsky and others, all freely available on CPAN. There's appenders to write to the screen (Log::Dispatch::Screen), to a file (Log::Dispatch::File), to a database (Log::Dispatch::DBI), to send messages via e-mail (Log::Dispatch::Email), and many more.

New appenders are defined using the Log::Log4perl::Appender class. The exact number and types of parameters required depends on the type of appender used, here's the syntax for one of the most common ones, the logfile appender, which appends its messages to a log file:

        # Appenders
    my $appender = Log::Log4perl::Appender->new(
        "Log::Dispatch::File",
        filename => "test.log",
        mode     => "append",
    );
    
    $food_logger->add_appender($appender);

This will create a new appender of the class Log::Dispatch::File, which will append messages to the file test.log. If we had left out the mode => "append" pair, then it would just overwrite the file each time at system startup.

The wrapper class Log::Log4perl::Appender provides the necessary glue around Log::Dispatch modules to make them usable by Log::Log4perl as appenders. This tutorial shows only the most common ones: Log::Dispatch::Screen to write messages to STDOUT/STDERR and Log::Dispatch::File, to print to a log file. However, you can use any Log::Dispatch-Module with Log::Log4perl. To find out what's available and how to their respective parameter settings are, please refer to the detailed Log::Dispatch documentation. Using add_appender(), you can attach as many appenders to any logger as you like.

After passing the newly created appender to the logger's add_appender() method like in

    $food_logger->add_appender($appender);

it is attached to the logger and will handle its messages if the logger decides to fire. Also, it will handle messages percolating up the hierarchy if a logger further down decides to fire.

This will cause our Log::Dispatch::File appender to add the following line

    INFO - Eating Sushi

to the logfile test.log. But wait -- where did the nice formatting with date, time, source file name, line number and function go we saw earlier on in :easy mode? By simply specifying an appender without defining its layout, Log::Log4perl just assumed we wanted the no-frills log message layout SimpleLayout, which just logs the incident priority and the message, separated by a dash.

Pages: 1, 2, 3, 4

Next Pagearrow