Smart Logging With Log4perl

advertisement
Smart Logging With Log4perl
Mike Schilli, Yahoo!
YAPC, 06/27/2007
Grab A T-Shirt!
Smart Logging With Log4perl
Mike Schilli, Yahoo!
YAPC, 06/27/2007
Logging – why?
• Go back in time and figure out what
happened.
• Measure performance
• Trace activity on live systems
• Debug during development
Log4perl and Log4j
• Log::Log4perl ports Log4j to Perl
• Log4j: de facto Java logging standard, by
Ceki Gülcü
– Latest development: ‘logback’
• http://logging.apache.org/log4j
• Log::Log4perl adds perlisms demanded by
users
Log4perl History
•
•
•
•
0.01 release 2002
Current release 1.11 (06/2007)
Authors: Mike Schilli, Kevin Goess
Used by major banks, target.com,
fedex.com, Yahoo!, Google, …
• Several CPAN modules support it (e.g.
Catalyst, Net::Amazon, …)
Log4perl Release History
Design Goals
• Easy to use in small scripts
• Scales easily with growing architecture
• Log4perl-enabled CPAN modules can be
used with and without Log4perl
initialization
• Optimized for Speed
• Open Architecture, extensible
Why Log4perl and not one of the
20 Logging modules on CPAN?
• Write once, and L4p-enabled scripts/modules
can be used
– with any logging configuration you desire
– by changing loggers, appenders and layouts
independently
• Target specific areas of your application only
• No need to change your source code to change
the logging behaviour
• Log4perl is aimed to be the last logging module
you’ll ever install
Log::Log4perl for Simple Scripts
#!/usr/bin/perl –w
use strict;
use Log::Log4perl qw(:easy);
Log::Log4perl->easy_init($INFO);
DEBUG "Won't make it";
INFO "Will go through";
ERROR "Will also go through";
$ easy.pl
2004/10/19 10:56:56 Will go through
2004/10/19 10:56:56 Will also go through
Log::Log4perl for CPAN Modules
use strict;
use YourCPANModule;
use Log::Log4perl qw(:easy);
Log::Log4perl->easy_init( $INFO );
YourCPANModule::somefunc();
package YourCPANModule;
use strict;
use Log::Log4perl qw(:easy);
sub somefunc {
DEBUG "Won't make it";
INFO "Will go through";
}
Log::Log4perl for CPAN Modules
use strict;
use YourCPANModule;
YourCPANModule::somefunc();
package YourCPANModule;
use strict;
use Log::Log4perl qw(:easy);
sub somefunc {
DEBUG "Won't make it";
INFO "Won’t make it";
}
Log::Log4perl Building Blocks
Loggers
Layouts
Appenders
Log::Log4perl Building Blocks
Loggers
Log it or suppress it
Layouts
Format it
Appenders
Write it out
Log::Log4perl Building Blocks
Loggers
DEBUG “Starting up”);
or
my $log = get_logger();
$log->debug(“Starting up”);
Layouts
Appenders
“Starting up” =>
2007-06-21 07:30:33 Foo.pm-123 Starting up
Log File
Database
…
Easy vs. Standard Mode
use Log::Log4perl qw(:easy);
sub somefunc {
DEBUG “Debug Message”;
INFO “Info Message”;
}
use Log::Log4perl qw(get_logger);
sub somefunc {
my $logger = get_logger();
$logger->debug(“Debug message”);
$logger->info(“Info message”);
}
Log4perl Operation
• Initialize once, at startup:
– Define which loggers should fire base on
• message priority (DEBUG, INFO, …)
• location (Foo::Bar, …)
– Define where messages should end up at
• Loggers planted in program code get
activated
Easy Init vs. Standard Init
use Log::Log4perl qw(:easy);
Log::Log4perl->easy_init($INFO);
use Log::Log4perl;
Log::Log4perl->init(“l4p.conf”);
#l4p.conf
log4perl.category = INFO, Screen
log4perl.appender.Screen = \
Log::Log4perl::Appender::Screen
log4perl.appender.Screen.stderr = 1
log4perl.appender.Logfile.layout = \
Log::Log4perl::Layout::SimpleLayout
Mix&Match:
Standard Init with Easy Functions
use Log::Log4perl qw(:easy);
Log::Log4perl->init(“l4p.conf”);
DEBUG “Debug Message”;
INFO “Info Message”;
#l4p.conf
log4perl.category = WARN, Logfile
log4perl.appender.Logfile = \
Log::Log4perl::Appender::File
log4perl.appender.Logfile.filename = test.log
log4perl.appender.Logfile.layout = \
Log::Log4perl::Layout::SimpleLayout
Mix and Match
Easy Mode
DEBUG(…)
Standard Mode
get_logger()
easy_init()
OK
OK
init()
OK
OK
Categories or:
What the hell does this logo mean?
Categories: Areas of your App
package Bar::Twix;
use Log::Log4perl qw(get_logger);
sub eat {
# Gets a Bar::Twix logger
my $logger = get_logger();
$logger->debug(“Debug Message”);
}
#l4p.conf
log4perl.category.Bar.Twix = WARN, Logfile
log4perl.appender.Logfile = \
Log::Log4perl::Appender::File
log4perl.appender.Logfile.filename = test.log
log4perl.appender.Logfile.layout = \
Log::Log4perl::Layout::SimpleLayout
Categories (Easy Mode)
package Bar::Twix;
use Log::Log4perl qw(:easy);
sub eat {
# Has a Bar::Twix logger
DEBUG “Debug Message”;
}
#l4p.conf
log4perl.category.Bar.Twix = WARN, Logfile
log4perl.appender.Logfile = \
Log::Log4perl::Appender::File
log4perl.appender.Logfile.filename = test.log
log4perl.appender.Logfile.layout = \
Log::Log4perl::Layout::SimpleLayout
Loggers
• Have a
– category (e.g. “Net::Amazon” )
– priority (e.g. $DEBUG )
• Take a message (e.g. “Starting” )
Loggers
• Pass on their message if
– logging has been configured for
• their category ( “Net::Amazon”) or
• a parent category ( “Net”, “” )
– and (!) the message priority (e.g. $DEBUG) is
greater or equal than the configured log level
(e.g. $DEBUG) for the category
Limit Throughput: Levels and
Categories
• Categories determine which parts of the
system are addressed.
my $log = get_logger(“Net::Amazon”);
or simply
package Net::Amazon;
DEBUG “…”;
• Levels indicate the message priority.
$log->debug(“Request sent ($bytes bytes)”;
Log Levels
• Levels
–
–
–
–
–
–
–
$TRACE
$DEBUG
$INFO
$WARN
$ERROR
$FATAL
$OFF
Log Levels
• Methods
–
–
–
–
–
–
$log->trace(“$bytes bytes transferred”);
$log->debug(“HTTP get OK”);
$log->info(“Starting up”);
$log->warn(“HTTP get failed, retrying”);
$log->error(“Out of retries!”);
$log->fatal(“Panic! Shutting down.”);
Log Levels
• Macros
– TRACE(“$bytes bytes transferred”);
– DEBUG(“HTTP get OK”);
– INFO(“Starting up”);
– WARN(“HTTP get failed, retrying”);
– ERROR(“Out of retries!”);
– FATAL(“Panic! Shutting down.”);
Remote Control via Categories
l4p.conf
log4perl.category
= DEBUG, Screen
#!/usr/bin/perl
use Net::Amazon;
Log::Log4perl->init(“l4p.conf”);
DEBUG “Makes it through!”
my $amzn = Net::Amazon->new();
package Net::Amazon;
ERROR “Makes it through!”;
DEBUG “Makes it through”;
Remote Control via Categories
l4p.conf
Log4perl.category
= ERROR, Screen
#!/usr/bin/perl
use Net::Amazon;
Log::Log4perl->init(“l4p.conf”);
DEBUG “Gets blocked!”
my $amzn = Net::Amazon->new();
package Net::Amazon;
ERROR “Makes it through!”;
DEBUG “Gets blocked”;
Remote Control via Categories
l4p.conf
Log4perl.category
= FATAL, Screen
#!/usr/bin/perl
use Net::Amazon;
Log::Log4perl->init(“l4p.conf”);
DEBUG “Gets blocked!”
my $amzn = Net::Amazon->new();
package Net::Amazon;
ERROR “Gets blocked!”;
DEBUG “Gets blocked”;
Remote Control via Categories
l4p.conf
Log4perl.category.main
= DEBUG, Screen
Log4perl.category.Net.Amazon = ERROR, Screen
#!/usr/bin/perl
use Net::Amazon;
Log::Log4perl->init(“l4p.conf”);
DEBUG “Makes it through!”
my $amzn = Net::Amazon->new();
package Net::Amazon;
ERROR “Makes it through!”;
DEBUG “Gets blocked”;
Remote Control via Categories
l4p.conf
Log4perl.category.main
= ERROR, Screen
Log4perl.category.Net.Amazon = DEBUG, Screen
#!/usr/bin/perl
use Net::Amazon;
Log::Log4perl->init(“l4p.conf”);
DEBUG “Gets blocked”;
my $amzn = Net::Amazon->new();
package Net::Amazon;
ERROR “Makes it through!”;
DEBUG “Makes it through!”;
Category Inheritance (1)
l4p.conf
Log4perl.category.main
Log4perl.category.Net
= DEBUG, Screen
= ERROR, Screen
#!/usr/bin/perl
use Net::Amazon;
Log::Log4perl->init(“l4p.conf”);
DEBUG “Makes it through!”
my $amzn = Net::Amazon->new();
package Net::Amazon;
ERROR “Makes it through!”;
DEBUG “Gets blocked”;
Category Inheritance (2)
l4p.conf
Log4perl.category.main = DEBUG, Screen
Log4perl.category
= ERROR, Screen
#!/usr/bin/perl
use Net::Amazon;
Log::Log4perl->init(“l4p.conf”);
DEBUG “Makes it through!”
my $amzn = Net::Amazon->new();
package Net::Amazon;
ERROR “Makes it through!”;
DEBUG “Gets blocked”;
Log4perl Flow
Application sends a log message
(Category, Priority)
Log4perl Configuration
decides go/no go, based
on Category and Priority
?
Layout
Appender
Appender
Appender
l4p.conf: Screen Appender
log4perl.category.main
= DEBUG, Screen
Log4perl.category.Net.Amazon = INFO, Screen
log4perl.appender.Screen = \
Log::Log4perl::Appender::Screen
…
l4p.conf: File Appender
log4perl.category.main
= DEBUG, Logfile
Log4perl.category.Net.Amazon = INFO, Logfile
log4perl.appender.Logfile = \
Log::Log4perl::Appender::File
log4perl.appender.Logfile.filename = test.log
…
l4p.conf: Two appenders
log4perl.category.main
= DEBUG, Logfile
Log4perl.category.Net.Amazon = INFO, Screen
log4perl.appender.Logfile = \
Log::Log4perl::Appender::File
log4perl.appender.Logfile.filename = test.log
log4perl.appender.Screen = \
Log::Log4perl::Appender::Screen
…
Init and Watch
• Log::Log4perl->init(“l4p.conf”);
• Log::Log4perl->init(\$conf_string);
• Log::Log4perl->init_and_watch(“l4p.conf”, 30);
• Log::Log4perl->init_and_watch(“l4p.conf”, ‘HUP’ );
Demo
Appenders
• Appenders are output sinks
• Get called if a message passes category
and level hurdles
Appenders
• Log::Log4perl::Appender::Screen
Log::Log4perl::Appender::File
Log::Log4perl::Appender::Socket
Log::Log4perl::Appender::DBI
Log::Log4perl::Appender::Synchronized
Log::Log4perl::Appender::RRDs
• Log::Dispatch provides even more:
Log::Dispatch Appenders
• Log::Dispatch::ApacheLog
Log::Dispatch::DBI, Log::Dispatch::Email,
Log::Dispatch::Email::MIMELite
Log::Dispatch::File
Log::Dispatch::FileRotate
Log::Dispatch::Screen
Log::Dispatch::Syslog, Log::Dispatch::Tk
Example: Rotating Log File
Appender
• Keep 5 days of logfiles, then delete:
log4perl.category = WARN, Logfile
log4perl.appender.Logfile = Log::Dispatch::FileRotate
log4perl.appender.Logfile.filename
= test.log
log4perl.appender.Logfile.max
= 5
log4perl.appender.Logfile.DatePattern = yyyy-MM-dd
log4perl.appender.Logfile.TZ
= PST
log4perl.appender.Logfile.layout
= \
Log::Log4perl::Layout::SimpleLayout
Rotating Files
• Rotating File Appender:
– Log::Dispatch::FileRotate
– Single request pays for rotation
– Rotation runs as particular user
• External Rotators (e.g. newsyslog):
– recreate flag makes sure file appender adjusts
– recreate_check_interval saves on stat() calls
Write Your Own Appender (1)
package ColorScreenAppender;
our @ISA = qw(Log::Log4perl::Appender);
use Term::ANSIColor;
sub new {
my($class, %options) = @_;
my $self = {%options, …};
bless $self, $class;
}
sub log {
my($self, %params) = @_;
print colored($params{message},
$self->{color});
}
Write Your Own Appender (2)
log4perl.logger = INFO, ColorApp
log4perl.appender.ColorApp=ColorScreenAppender
log4perl.appender.ColorApp.color = red
log4perl.appender.ColorApp.layout = SimpleLayout
Layouts
• SimpleLayout
log4perl.appender.Screen = \
Log::Log4perl::Appender::Screen
log4perl.appender.Screen.layout = SimpleLayout
$log->debug(“Sending Mail”);
DEBUG – Sending Mail
Layouts
• PatternLayout
log4perl.appender.Logfile.layout = \
Log::Log4perl::Layout::PatternLayout
log4perl.appender.Logfile.layout.ConversionPattern = \
%d %F{1}:%L> %m %n
$log->debug(“Sending Mail”);
2004/10/17 18:47:25 l4ptest.pl:25> Sending Mail
Layouts
• 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
%l calling method + file + line
%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
%x The elements of the NDC stack
%X{key} The entry 'key' of the MDC
%% A literal percent (%) sign
Layouts
• Still not enough? Write your own:
log4perl.PatternLayout.cspec.U = sub { return "UID $<" }
…
log4perl.appender.Logfile.layout = \
Log::Log4perl::Layout::PatternLayout
log4perl.appender.Logfile.layout.ConversionPattern = \
%d %U> %m %n
Filters
• Last minute chance for Appenders to block
• More expensive than Level/Category
blocking
• Text/Level Match comes with L4p
• Write your own custom filters
Filters
log4perl.category = INFO, Screen
log4perl.filter.MyFilter = \
Log::Log4perl::Filter::StringMatch
log4perl.filter.M1.StringToMatch = let this through
log4perl.appender.Screen = \
Log::Log4perl::Appender::Screen
log4perl.appender.Screen.Filter = MyFilter
log4perl.appender.Screen.layout = \
Log::Log4perl::Layout::SimpleLayout
Filters
log4perl.filter.M1 = Log::Log4perl::Filter::StringMatch
log4perl.filter.M1.StringToMatch = let this through
log4perl.filter.M1.AcceptOnMatch = true
log4perl.filter.M1 = Log::Log4perl::Filter::LevelMatch
log4perl.filter.M1.LevelToMatch = INFO
log4perl.filter.M1.AcceptOnMatch = true
Speed
Avoid Wasting Cycles
for (@super_long_array) {
$logger->debug("Element: $_\n");
}
if($logger->is_debug()) {
for (@super_long_array) {
$logger->debug("Element: $_\n");
}
}
Performance
• On a Pentium 4 Linux box at 2.4 GHz,
you'll get through
– 500,000 suppressed log statements per
second
– 30,000 logged messages per second (using
an in-memory appender)
– init_and_watch delay mode: 300,000
suppressed, 30,000 logged.
– init_and_watch signal mode: 450,000
suppressed, 30,000 logged.
Advanced Trickery
Infiltrating LWP
• Ever wondered what LWP is doing behind
the scenes?
use LWP::UserAgent;
use HTTP::Request::Common;
use Log::Log4perl qw(:easy);
Log::Log4perl->easy_init($DEBUG);
Log::Log4perl->infiltrate_lwp();
my $ua = LWP::UserAgent->new();
my $resp = $ua->request(GET
"http://www.yahoo.com");
Infiltrating LWP
• Getting LWP to use Log4perl for logging:
2004/10/20 18:36:43 ()
2004/10/20 18:36:43 ()
2004/10/20 18:36:43 GET
http://www.yahoo.com
2004/10/20 18:36:43 Not proxied
2004/10/20 18:36:43 ()
2004/10/20 18:36:43 read 634 bytes
2004/10/20 18:36:43 read 4096 bytes
…
2004/10/20 18:36:43 read 2488 bytes
2004/10/20 18:36:43 Simple response: OK
Resurrect in a Single File
• The :resurrect target uncomments lines
starting with ###l4p:
use Log4perl qw(:easy :resurrect);
sub foo {
# …
###l4p DEBUG “foo was here”;
}
Resurrect L4p in all Modules
• The Log::Log4perl::Resurrector touches all
subsequently loaded modules (experimental!):
use Log4perl qw(:easy);
use Log::Log4perl::Resurrector;
use Foo::Bar;
# Deploy a module without requiring L4p at all!
package Foo::Bar;
###l4p use Log4perl qw(:easy);
sub foo {
###l4p DEBUG “This will be resurrected!”;
}
Pitfalls
Beginner’s Pitfalls
Beginner’s Pitfalls
• Appender Additivity:
– Once a lower level logger decides to fire, it
will forward the message to its appenders.
– It will also forward the message
unconditionally to all of its parent logger’s
appenders.
Beginner’s Pitfalls
• Appender Additivity (with dupes)
log4perl.category = FATAL, Screen
log4perl.category.Net.Amazon = DEBUG, Screen
log4perl.appender.Screen = \
Log::Log4perl::Appender::Screen
log4perl.appender.Screen.layout = SimpleLayout
package Net::Amazon;
DEBUG(“Debugging!”);
DEBUG - Debugging!
DEBUG - Debugging!
Appender Additivity
File Appender
root
FATAL
Screen
Appender
Net
Net::Amazon
DEBUG
Beginner’s Pitfalls
• Appender Additivity (no dupes)
log4perl.category
= ERROR, Screen
log4perl.category.Net.Amazon
= DEBUG, Screen
log4perl.additivity.Net.Amazon = 0
log4perl.appender.Screen = \
Log::Log4perl::Appender::Screen
log4perl.appender.Screen.layout = SimpleLayout
package Net::Amazon;
DEBUG(“Debugging!”);
DEBUG – Debugging!
Q
Log::Log4perl Availability
• Cpan> install Log::Log4perl
– Only requires core modules
• Requires Perl 5.00503 or better
• Included in major Linux distros
• Windows: ppm package available in
ActiveState archives or from
log4perl.sf.net
Best Practices
• Don’t provide program name, function
name in the message. Use Layouts
instead.
• Log plenty. Don’t worry about space, use
rotating log files for chatty output.
What you should log
•
•
•
•
Program starting up, shutting down
Function parameters
Milestones in request processing
Start/end of lenghty operations (e.g. DB
access)
What you shouldn’t log
• Function/method names (although you
want parameters)
• Date
• Process ID
• … everything else provided by the layout
Best Practices
• Rolling out new L4p-enabled Modules
package My::Module;
use Log::Log4perl qw(:easy);
sub new { # … }
sub foo { DEBUG “I’m here”; # … }
sub bar { INFO “Now I’m here”; # … }
# Use w/o Log4perl
use My::Module;
$o = My::Module->new();
$o->foo();
# Log4perl enabled
use My::Module;
use Log::Log4perl qw(:easy);
Log::Log4perl->easy_init($DEBUG);
$o = My::Module->new();
$o->foo();
Gotchas
• Avoid ‘append’ on NFS (creating lots of
‘read’ traffic)
• Don’t put expensive operations into the
logger call which are executed before the
logger decides
• Permission issues with rotating file
appenders
mod_perl
• Init() in startup handler
• Current limitation: One init().
• Use init_once() if you’re not sure who inits
first.
Combat overlapping Messages
• Either live with it or
– Use the Synchronized appender
– Use Appender.File.syswrite=1
Driving Log::Log4perl further
• Bug Fixes:
Submit to log4perl-devel@sourceforge.net
• New Features:
Propose features or send patches
• New Appenders:
Release separately as a module in the
Log::Log4perl::Appender::xx namespace
on CPAN
• … or contact me: mschilli@yahoo-inc.com
Q&A
Q&A
Thank You!
Log::Log4perl Project Page:
http://log4perl.sourceforge.net
Email me:
Mike Schilli
mschilli@yahoo-inc.com
Post to the List:
log4perl-devel@sourceforge.net
Bonus Slides
Error Handling
use Log::Log4perl qw(:easy);
do_something or
LOGDIE “Horrible error!”;
• LOGCARP, LOGCLUCK, LOGCONFESS
etc. also available.
Displaying Logs with Chainsaw
• Log4j features an XML layout and a socket
appender
• XML output is displayed in a nice GUI
called Chainsaw
• Log::Log4perl also features an XML layout
and a socket appender
• … XML output is also displayed in a nice
GUI called Chainsaw!
Displaying Logs with Chainsaw
Log::Log4perl
enabled Perl Script
Log::Log4perl::Layout::XML
Log::Log4perl::Appender::Socket
Java / log4j
Program
Displaying Logs with Chainsaw
The Future
• Allow multiple configurations within the
same process (e.g. for mod_perl)
Easy Mode Init with Files
use Log::Log4perl qw(:easy);
Log::Log4perl->easy_init({
level
=> $DEBUG,
file
=> “>file.log”,
category => “Bar::Twix”,
});
Multiline Layout
Instead of
2007/04/04 23:59:01 This is
a message with
multiple lines
get this:
2007/04/04 23:59:01 This is
2007/04/04 23:59:01 a message with
2007/04/04 23:59:01 multiple lines
with PatternLayout::Multiline:
log4perl.appender.Logfile. layout = \
Log::Log4perl::Layout::PatternLayout::Multiline
log4perl.appender.Logfile.layout.ConversionPattern = %d %m %n
Perl Hooks
• Add parameters known at init() time
log4perl.appender.Logfile.filename = sub { \
“mylog.$$.log” }
Download