370 likes | 483 Views
Log::Report. A new module. YAPC::Europe 2007, Vienna by 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?
E N D
Log::Report A new module YAPC::Europe 2007, Vienna by 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 needed my $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 MANIFEST cd lib/My/Module/messages cp mydomain.utf-8.po mydomain/nl_NL.po vi 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.conf log4perl.logger.mylogger = ERROR, somefile log4perl.appender.somefile = Log::Log4perl::Appender::File log4perl.appender.somefile.filename = /var/log/my.log log4perl.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 program use Log::Report; dispatcher close => 'default'; dispatcher SYSLOG => 'syslog', facility => LOCAL7; # in all (other) files use Log::Report 'my.domain'; report ERROR =>__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 program use Log::Report; dispatcher close => 'stderr'; dispatcher SYSLOG => 'syslog', facility => LOCAL7; # in all (other) files use Log::Report 'my.domain', syntax => 'SHORT'; error __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::Log4perl dispatcher Log::Log4perl => 'mylogger', config => “$ENV{HOME}/.log4perl.conf; # borrow back-ends from Log::Dispatch dispatcher 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;
my $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: $!"; } Perl5's way
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"; } Log::Report
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 NORMAL GetOptions 'v+' => \$mode , 'verbose=i' => \$mode , 'mode=s' => \$mode or exit 1; dispatcher PERL => 'default', mode => $mode;
display mode • mode adapted formattingS=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 special try { error __”help!” }; if($@) { ... } # implemented as dispatcher try \&doit, mode => 'DEBUG';
exceptions # exceptions are not too special try { error __”help!” }; if($@) { ... } # implemented as dispatcher try \&doit, mode => 'DEBUG'; # very much like eval() my $x = try { sqrt $y }; my @x = try { @lines }; # translates die/croak/confess my $x = try { confess “help!” };
exceptions # exceptions are not too special try { 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