Home

Search this site:
 

Horshack’s
very
own site

 

This is Bavaria, Germany!
You will find links to Perl,
bootdisks for system administration,
a lot of Linux,
Windows administration
and some personal belongings.
Log4perl

Log4perl

The module Log::Log4perl from CPAN provides easy logging in Perlprograms.

A fast approach, log to screen

When you simply want to log to screen from a certain level on:
use Log::Log4perl qw(:easy);
# Set priority of root logger to ERROR
Log::Log4perl->easy_init($ERROR);  

# Application Section
my $logger = get_logger();
# Will not be logged:
$logger->info("I do this and that");
# Will be logged:
$logger->error("Bad");
Logging looks like this:

A fast approach, log to screen and file

Log to screen and file. You can build this up to full logging later. Don't fear the lines in $log_conf, simply copy it and change the name of the logfile.

#!/usr/bin/perl
use strict;
use warnings;
use Log::Log4perl;

my $log_conf = q/ 
    log4perl.category = INFO, Logfile, Screen 
     
    log4perl.appender.Logfile = Log::Log4perl::Appender::File 
    log4perl.appender.Logfile.filename = your_logfilename.log 
    log4perl.appender.Logfile.mode = write 
    log4perl.appender.Logfile.layout = Log::Log4perl::Layout::SimpleLayout
     
    log4perl.appender.Screen        = Log::Log4perl::Appender::Screen 
    log4perl.appender.Screen.layout = Log::Log4perl::Layout::SimpleLayout
/;

Log::Log4perl::init( \$log_conf );
my $logger = Log::Log4perl::get_logger();

$logger->info("Starting $0");
$logger->error("Bad thing happened");

A more complex example

This is a more complex example. Do this:

#!/usr/bin/perl
use strict;
use warnings;
use Log::Log4perl;
use Hob::Inventory;
use Hob::GetWishlist;
use Hob::Harvester;

# Logging (mode=write|append)
my $log_conf = q/
	log4perl.category = INFO, Logfile, Screen

	log4perl.appender.Logfile = Log::Log4perl::Appender::File
	log4perl.appender.Logfile.filename = your_logfilename.log
	log4perl.appender.Logfile.mode = write
	log4perl.appender.Logfile.layout = Log::Log4perl::Layout::PatternLayout
	log4perl.appender.Logfile.layout.ConversionPattern = %d %p> %m%n

	log4perl.appender.Screen        = Log::Log4perl::Appender::Screen
	log4perl.appender.Screen.stderr = 0
	log4perl.appender.Screen.layout = Log::Log4perl::Layout::PatternLayout
	log4perl.appender.Screen.layout.ConversionPattern = %d %p> %m%n

	log4perl.logger.main = INFO
	log4perl.logger.Hob.Inventory = INFO
	log4perl.logger.Hob.GetWishlist = INFO
	log4perl.logger.Hob.Harvester = INFO
	log4perl.logger.Hob.Harvester.Anna = INFO
	log4perl.logger.Hob.Harvester.Petra = DEBUG
	log4perl.logger.Hob.Harvester.Ludmilla = INFO
/;
# Create Singleton-Object
Log::Log4perl::init( \$log_conf );
# Logging ready

my $logger = Log::Log4perl::get_logger("main");
$log->info("Starting $0");

my $badthing=1;
if ($badthing) {
	$log->error("bad thing happened");
}
my $fatalthing=1

if ($fatalthing) {
	$log->logdie("fatal thing happened, I die");
}
In every module you use do this:

See it in the code::

package Hob::Inventory;
use strict;
use warnings;
use Log::Log4perl;
sub new {
    my($class,%args) = @_;
    my %defaults = ( 
        verbose => 1, 
	       find_these => [ qw/jpg gif/ ], 
    );
    
    my $self = bless {
        %defaults,
        %args,
        # get Logger-Singleton and get environment for "Hob.Inventory"
        logger => Log::Log4perl->get_logger('Hob.Inventory'),
    }, $class;
    
    $self->check_args;
    return $self;
}

sub _logger { shift->{logger}; }

sub check_args {
    my $self = shift;
    $self->_logger->debug('Checking arguments');
    ...
}

1;

Methods to use in Log::Log4perl

How to switch Loglevel in the program

I don't like these methods:

because I want to determine the exact level:

package Hob::Harvester;
use strict;
use File::Basename;
use Log::Log4perl qw(:levels);
# ...
my $logger = Log::Log4perl->get_logger('Hob.Harvester');
$logger->level($DEBUG);
# Take the fully qualified variable name if you
# don't want to import :levels like this:
# $logger->level($Log::Log4perl::DEBUG);
If you want to switch the runlevel only for a small part in the program do this:
  1. remember the old level in a variable
  2. switch to a new level
  3. do your job
  4. switch back to old level
    my $logger = Log::Log4perl->get_logger('Findus.Oldinicatcher');
    # determine and remember old level (DEBUG)
    my $loglevel_org = $logger->level();
    # set loglevel to ERROR not get flooded with DEBUG-messages
    $logger->level($Log::Log4perl::ERROR);
    # ... do the things with less logging
    # ... do
    # ... do
    # return to old loglevel
    $logger->level($loglevel_org);

Determine name of logfile on runtime

This is made by perl hooks in the configuration (see description in
CPAN of Log4perl for more). This means that you can define a function in the log-definition which is interpreted at runtime (get_log_fn in this example.

#!/usr/bin/perl
use strict;
use warnings;
use Log::Log4perl;

my $log_conf = q/ 
    log4perl.category = INFO, Logfile
     
    log4perl.appender.Logfile = Log::Log4perl::Appender::File 
    log4perl.appender.Logfile.filename = sub { return get_log_fn(); }
    log4perl.appender.Logfile.mode = write 
    log4perl.appender.Logfile.layout = Log::Log4perl::Layout::SimpleLayout
/;

Log::Log4perl::init( \$log_conf );
my $logger = Log::Log4perl::get_logger();

$logger->info("Starting $0");
$logger->error("Bad thing happened");

=head2 get_log_fn

Return Logfilename for this programm. It is the basename of this programm
with a ".log" ending.

=cut

sub get_log_fn {
    use File::Basename;
    return sprintf "%s.log", basename( $0, '.pl' );
}

Use an external config-file for logging

If you want to determine loglevel on runtime you can do the whole configuration in a separate configuration file like this:
# Separate configfile: customer.logconfig
log4perl.category = DEBUG, Screen, Logfile 
 
log4perl.appender.Screen        = Log::Log4perl::Appender::Screen 
log4perl.appender.Screen.layout = Log::Log4perl::Layout::PatternLayout
#     %c Category of the logging event.
#     %C Fully qualified package (or class) name of the caller
#     %d Current date in yyyy/MM/dd hh:mm:ss format
#     %F File where the logging event occurred
#     %H Hostname (if Sys::Hostname is available)
#     %l Fully qualified name of the calling method followed by the
#        callers source the file name and line number between 
#        parentheses.
#     %L Line number within the file where the log statement was issued
#     %m The message to be logged
#     %M Method or function where the logging request was issued
#     %n Newline (OS-independent)
#     %p Priority of the logging event
#     %P pid of the current process 
#     %r Number of milliseconds elapsed from program start to logging 
#        event   
#     %T A stack trace of functions called
#     %x The topmost NDC (see below)
#     %X{key} The entry 'key' of the MDC (see below)
#     %% A literal percent (%) sign
log4perl.appender.Screen.layout.ConversionPattern = %p> %m

log4perl.appender.Logfile = Log::Log4perl::Appender::File
log4perl.appender.Logfile.filename = t.log
log4perl.appender.Logfile.mode = append
log4perl.appender.Logfile.layout = Log::Log4perl::Layout::PatternLayout
log4perl.appender.Logfile.layout.ConversionPattern = %d %p> %m%n

log4perl.logger.main = INFO
log4perl.logger.customers.get = DEBUG
Now tell the program where to find the config-file for logging:
#!/usr/bin/perl
use strict;
use warnings;
use Log::Log4perl;

my $conf_file = 't.logconfig';
# read config file only one time!
# Log::Log4perl->init( $conf_file );
# Re-read config file every 5 seconds
Log::Log4perl->init_and_watch( $conf_file, 5 );

my $logger = Log::Log4perl::get_logger('main');
$logger->debug("Starting $0");

Logrotate

Logfile shall rotate when they reach a given size.
    # you need the perlmodule Log::Dispatch::FileRotate for that!
...
    log4perl.appender.Logfile = Log::Dispatch::FileRotate
    log4perl.appender.Logfile.filename = findus.log 
    log4perl.appender.Logfile.mode = append
    log4perl.appender.Logfile.max = 2
    log4perl.appender.Logfile.size = 10_000_000
    log4perl.appender.Logfile.layout = Log::Log4perl::Layout::PatternLayout
    log4perl.appender.Logfile.layout.ConversionPattern = %d %p: %M> %m%n
...

Use a section in an inifile to initialize Log::Log4perl

When you frequently are using inifiles to determine how your programs are supposed to run, you can also initialize Log::Log4perl with that file. Copy this subroutine into your code:
use strict;
use warnings;
use Config::IniFiles;
use File::Basename;
use Log::Log4perl;

my $ini_obj = Config::IniFiles->new( -file => $ini_fn ) || die;
init_logger_by_section_in_inifile( { ini_obj => $ini_obj } );

my $logger = Log::Log4perl::get_logger();
$logger->info("ok!");

=head2 init_logger_by_section_in_inifile

Initialize Log::Log4perl with an ini-File. You must supply a 
section for Log4perl like this:

    [log4perl]
    log4perl.category = INFO, Logfile, Screen 

    log4perl.appender.Logfile          = Log::Log4perl::Appender::File 
    log4perl.appender.Logfile.filename = your_logfile.log
    log4perl.appender.Logfile.mode     = write 

    log4perl.appender.Logfile.layout = Log::Log4perl::Layout::SimpleLayout
    log4perl.appender.Screen         = Log::Log4perl::Appender::Screen 
    log4perl.appender.Screen.layout  = Log::Log4perl::Layout::SimpleLayout

    [myfiles]
    ...

    [myvalues1]
    ...

Usage:

    init_logger_by_section_in_inifile(
        {
        ini_obj => $ini_obj,    # mandatory
        section => "log4perl",  # optional, default is "log4perl"
        }
    );

    # and then:
    my $logger = Log::Log4perl->get_logger('main');

Returnvalues:

    Returns nothing. 

Errors:

    Dies if ini_obj is not of type Config::IniFiles

=cut

sub init_logger_by_section_in_inifile {
    my $args_rh = shift;
    my $ini_obj = $args_rh->{ini_obj};
    my $section = 'log4perl' if ! exists $args_rh->{section};

    if (! $ini_obj || ref($ini_obj) ne 'Config::IniFiles') {
        die 'You must supply a valid ini_obj';
    }

    my $log_conf;
    for my $var ( $ini_obj->Parameters($section) ) {
        next unless defined $var;
        $log_conf .= sprintf "%s=%s\n", $var, $ini_obj->val( 'log4perl', $var );
    }
    Log::Log4perl::init( \$log_conf );
}

Subroutine in a module to get the right logger

=head2 _get_logger

Get the logger with the name of this package

Usage:

    my $logger = $self->_get_logger();
    # If this is package A::B::C -> get logger for 'A.B.C'

or

    my $logger = $self->_get_logger('XXX');
    # If this is package A::B::C -> get logger for 'A.B.C.XXX'

=cut

sub _get_logger {
    my $self = shift;
    my $cur_package = ref $self;

    my $log_focus = @_ ? $cur_package . '::' . shift() : $cur_package;
    $log_focus =~ s{::}{.}g;
    Log::Log4perl::get_logger($log_focus);
}

Log::Log4perl::ConfigByInifile

The program needs an inifile anyway, so I have an extra section for log4perl in this file too.

#!/usr/bin/perl
use strict;
use warnings;
use Config::IniFiles;
use Log::Log4perl::ConfigByInifile;

my $ini = Config::IniFiles->new( -file => 'fingerprints.ini' )
  || die;
Log::Log4perl::ConfigByInifile->new( { ini_obj => $ini, } );

my $logger = Log::Log4perl::get_logger();
$logger->info("ok!");

And this is the inifile

[log4perl]
log4perl.category = INFO, Screen 

log4perl.appender.Screen         = Log::Log4perl::Appender::Screen 
log4perl.appender.Screen.layout  = Log::Log4perl::Layout::SimpleLayout

log4perl.logger.main = INFO
log4perl.logger.Ju.Hei = INFO
Impressum