a new module log::report yapc::europe 2007, vienna by mark overmeer
TRANSCRIPT
A new module
Log::Report
YAPC::Europe 2007, Viennaby Mark Overmeer
How to handle errors?
Good programs produce many errors and warnings, have debug and verbose
MailBox: 188 messages in 1010 methods, but does not even test extensively.
How are they documented in POD?
Where in the code is an error message produced? In what language and charset?
Requirements (for CPAN6)
produce (error) messages which
can be used both graphically and command-line
internationalization: adapted to user's preference; Chinese to web-page English in syslog
extensively documented: one-liner message abstract for tool-tip extended description for web-page
strategy “die”
print/die/warn/carp/croak/confess/cluck
clear program flow, the “problem” is handled where it appears
very simple to use
clumsy to catch/handle: eval, $SIG{__DIE__}
limited to text messages
only two levels: either to die or not to die.
strategy “exceptions”
simulated try/catch
on location of error, throw exception object (die)
need to define exception classes (category). Not easy (quite a lot of typing) to use.
in some “unknown” caller environment, the object is caught and handled (eval) Less transparent program flow.
Very OO
translating messages
Translations with Locale::Textdomain
use Locale::TextDomain 'mydomain';
print __”Hello, World!”; # double quotes! “Hello, World!”
print __x “found user {name}”, name => $user; “found user john”
print __xn “found one file”, “found {nr} files”, scalar @files, nr => scalar @files; “found one file” / “found 5 files”
my @colors = (N__”red”, N__”green”, N__”blue”);print __$colors[1];
dispatching errors
Log::Log4perl
use Log::Log4perl;Log::Log4perl::init(“$ENV{HOME}/log4perl.conf”);
#everywhere when neededmy $log = Log::Log4perl->get_logger('my.config');
$log->notice('starting daemon');
translated & dispatched
In combination, it becomes like this:
$logger->debug( __”Hello World” );
translated & dispatched
In combination, it becomes like this:
However: too expensive: debug probably gets ignored
$logger->debug( __”Hello World” );
translated & dispatched
In combination, it becomes like this:
However: too expensive: debug probably gets ignored which language/character-set? Multiple dispatchers
with different needs.
$logger->debug( __”Hello World” );
translated & dispatched
In combination, it becomes like this:
However: too expensive: debug probably gets ignored which language/character-set? Multiple dispatchers
with different needs. exceptions? Already formatted data, but handler may
like the original values: avoid error-message parsing.
$logger->debug( __”Hello World” );
language support, extensions
Locale::Textdomain
use Locale::TextDomain 'mydomain';
print __”Hello World”; # double quotes!
print __x “found user {name}”, name => $user;
print __xn “found one file”, “found {nr} files”, scalar @files, nr => scalar @files;
my @colors = (N__”red”, N__”green”, N__”blue”);print __$colors[1];
language support, extensions
Locale::Textdomain and Log::Report
use Locale::TextDomain 'mydomain';use Log::Report 'mydomain';
print __”Hello World”; # double quotes!
print __x “found user {name}”, name => $user;
print __xn “found one file”, “found {nr} files”, scalar @files, nr => scalar @files;print __xn “found one file”, “found {_count} files”, +@files;
my @colors = N__w ”red green blue”;my @colors = (N__”red”, N__”green”, N__”blue”);print __$colors[1];
language support, delayed
compare Local::TextDomain / Log::Report
→ translation is delayed
use <either> 'mydomain';use POSIX ':locale_h';
my $x = __”Hello, World!”;
print $x; # Hello, World! Hello, World!
setlocale LC_MESSAGES, 'nl_NL';
print $x; # Hello, World! Hallo Wereld
language support, lexicon
xgettext-perl, using PPI
→ msgid extraction included
Makefile.PL: xgettext: $(TO_INST_PM)
xgettext-perl -p lib/My/Module/messages/
make xgettext # pm files from MANIFESTcd lib/My/Module/messagescp mydomain.utf-8.po mydomain/nl_NL.povi nl_NL.po # translate
nl_NL.po: msgid “Hello, World!” msgstr “Hallo Wereld!”
language support, formatting
Gettext
Local::TextDomain
Log::Report
→ simplified formats
printf dgettext(“mydomain”, “%5d bytes in %s”), $size, $fn;
printf __x “{size} bytes in {fn}”, size => sprintf(“%5d”, $size), fn => $fn;
printf __x “{size%5d} bytes in {fn}”, size => $size, fn => $fn;
dispatching with Log::Log4perl
Log::Log4perl
use Log::Log4perl;Log::Log4perl::init(“$ENV{HOME}/.log4perl.conf”);
# everywhere when needed (!)my $log = Log::Log4perl->get_logger('mylogger');$log->error('oops!');
### content of ~markov/log4perl.conflog4perl.logger.mylogger = ERROR, somefilelog4perl.appender.somefile = Log::Log4perl::Appender::Filelog4perl.appender.somefile.filename = /var/log/my.loglog4perl.appender.somefile.layout = Log::Log4perl::Layout::SimpleLayout
dispatching with Log::Dispatch
Log::Dispatch
use Log::Dispatch;
my $dispatcher = Log::Dispatch->new;
$dispatcher->add( Log::Dispatch::File->new( name => 'file1', filename => 'logfile' ) );
$dispatcher->log(level => 'info', message => 'Blah' );
$dispatcher->info(__“Blah”);
dispatching with Log::Report
Log::Report (hidden controller singleton)
# Main programuse Log::Report;
dispatcher close => 'default';dispatcher SYSLOG => 'syslog', facility => LOCAL7;
# in all (other) filesuse Log::Report 'my.domain';
report ERROR =>report ERROR => __x__x“filename too long ({char} max {max})” chars => length($fn), max => MAX_PATH if length($fn) > MAX_PATH;
dispatching with Log::Report
Log::Report short syntax
# Main programuse Log::Report;
dispatcher close => 'stderr';dispatcher SYSLOG => 'syslog', facility => LOCAL7;
# in all (other) filesuse Log::Report 'my.domain', syntax => 'SHORT'syntax => 'SHORT';
errorerror __x “filename too long ({char} max {max})”, chars => length($fn), max => MAX_PATH if length($fn) > MAX_PATH;
(intermission)
Log::Report::View (soon)<report label=”filename too long ({chars} max {max}”> <level>error</level>
<gettext lang=”nl”> bestandsnaam te lang ({chars} max {max}) </gettext>
<abstract lang=”en”> The filename is longer than supported by the file-system. Abbreviations are not automatically generated to avoid name clashes. </abstract></report>
dispatching
Log::Report
dispatcher PERL => 'default', mode => 'DEBUG';
dispatcher SYSLOG => 'syslog', accept => 'ERROR-', locale => 'nl_NL', charset => 'iso-8859-1';
dispatching
Log::Report
dispatcher PERL => 'default', mode => 'DEBUG';
dispatcher SYSLOG => 'syslog', accept => 'ERROR-', locale => 'nl_NL', charset => 'iso-8859-1';
# borrow back-ends from Log::Log4perldispatcher Log::Log4perl => 'mylogger', config => “$ENV{HOME}/.log4perl.conf;
# borrow back-ends from Log::Dispatchdispatcher Log::Dispatch::File => 'mydisp', filename => 'logfile', locale => 'pt_BR';
report()
any (single line) report to user
use Log::Report 'mydomain';report ERROR => __“Oops” if $problem;
report {to => 'syslog', errno => ENOENT} , FAILURE => 'network unreachable';
report()
any (single line) report to user
use Log::Report 'mydomain';report ERROR => __“Oops” if $problem;
report {to => 'syslog', errno => ENOENT} , FAILURE => 'network unreachable';
use Log::Report 'mydomain', syntax => 'SHORT';
error __“Oops” if $problem;
Perl5's waymy $dir = '/etc';File::Spec->file_name is_absolute($dir) or die "ERROR: directory name must be absolute.\n";
-d $dir or die "ERROR: what platform are you on?";
until(opendir DIR, $dir) { warn "ERROR: cannot read system dir $dir: $!"; sleep 60 }
$verbose && print "Processing directory $dir\n";
while(defined(my $file = readdir DIR)) { if($file =~ m/\.bak$/) { warn "WARNING: found backup file $dir/$f\n"; next }
die "ERROR: file $dir/$file is binary" if $debug && -B "$dir/$file";
$debug && print "DEBUG: processing file $dir/$file\n";
open FILE, "<", "$dir/$file" or die "ERROR: cannot read from $dir/$f: $!";
close FILE or croak "ERROR: read errors in $dir/$file: $!"; }
Log::Report my $dir = '/etc'; File::Spec->file_name is_absolute($dir) or mistake "directory name must be absolute";
-d $dir or panic "what platform are you on?";
until(opendir DIR, $dir) { alert "cannot read system directory $dir"; sleep 60 }
info "Processing directory $dir";
while(defined(my $file = readdir DIR)) { if($file =~ m/\.bak$/) { notice "found backup file $dir/$f"; next }
assert "file $dir/$file is binary" if -B "$dir/$file";
trace "processing file $dir/$file";
open FILE, "<", "$dir/$file" or fault "unable to read from $dir/$f";
close FILE or failure "read errors in $dir/$file"; }
message “reasons”
Perl5 Log::Dispatch Syslog Log4Perl Log::Report print 0,debug debug debug trace print 0,debug debug debug assert print 1,info info info info warn\n 2,notice notice info notice warn 3,warning warn warn mistake carp 3,warning warn warn warning die\n 4,error err error error die 5,critical crit fatal fault croak 6,alert alert fatal alert croak 7,emergency emerg fatal failure confess 7,emergency emerg fatal panic
Focus purely on reason, not handling Takes a little effort to learn (sorry)
report()
concatenation works! (still delayed)
info __”Hello” . “, “ . __”World” . “!”;
report()
concatenation works! (still delayed)
$! added automatically
info __”Hello” . “, “ . __”World” . “!”;
open IN, '<', $filename or fault __x“cannot read from {fn}”, fn => $filename;
“fault: cannot read from /etc/hosts: No such...\n”;
report()
concatenation works! (still delayed)
$! added automatically
info __”Hello” . “, “ . __”World” . “!”;
open IN, '<', $filename or fault __x“cannot read from {fn}”, fn => $filename;
“fault: cannot read from /etc/hosts: No such...\n”; “REASON: MSG: $!\n” # translatable!
REASON in domain “log-report”MSG in domain from user$! in domain libc
display mode
mode = 'NORMAL', 'VERBOSE', 'DEBUG'(per dispatcher)
-v / -vv / -vvv; --verbose 2; --mode=”DEBUG”
use Log::Report syntax => 'SHORT';use Getopt::Long qw(:config no_ignore_case bundling);
my $mode; # defaults to NORMALGetOptions 'v+' => \$mode , 'verbose=i' => \$mode , 'mode=s' => \$mode or exit 1;
dispatcher PERL => 'default', mode => $mode;
display mode
mode adapted formatting
S=show, C=confess, L=location, T=translate
mode mode mode mode REASON SOURCE TE! NORM -v -vv -vvv trace program ... S assert program ... SL SL info program T.. S S S notice program T.. S S S S mistake user T.. S S S SL warning program T.. SL SL SL SL error user/prog TE. S S SL SC fault system TE! S S SL SC alert system T.! S S SC SC failure system TE! S S SC SC panic program .E. SC SC SC SC
exceptions
# exceptions are not too specialtry { error __”help!” };if($@) { ... }
# implemented as dispatchertry \&doit, mode => 'DEBUG';
exceptions
# exceptions are not too specialtry { error __”help!” };if($@) { ... }
# implemented as dispatchertry \&doit, mode => 'DEBUG';
# very much like eval()my $x = try { sqrt $y };my @x = try { @lines };
# translates die/croak/confessmy $x = try { confess “help!” };
exceptions
# exceptions are not too specialtry { error __”help!” };if($@) { ... }print ref $@; # Log::Report::Dispatcher::Try
print $@; # “Died in $fn line 42”if($@->failed) ...if($@->success) ...
$@->reportAll; # also non-fatal$@->reportFatal(to => 'syslog');
if(my $exception = $@->wasFatal){ print $exception->reason; $exception->throw; # not report() print $exception->message->untranslated;}
Status
Translations lazy translation objects, with syntax of
Locale::TextDomain with a few extensions. extract gettext labels using PPI building and merging gettext-PO files
Dispatch direct stringification to own File, Syslog, Perl, Try back-ends to any Log::Dispatch back-end to any Log::Log4perl back-end