Hack 55. Show Source Code on Errors


Don't guess which line is the problemsee it!

Debugging errors and warning messages isn't often fun. Instead, it can be tedious. Often even finding the problem takes too long.

Perl can reveal the line number of warnings and errors (with warn and die and the warnings pragma in effect); why can't it show the source code of the affected line?

The Hack

The code to do this is pretty easy, if unsubtle:

package SourceCarp; use strict; use warnings; sub import {     my ($class, %args) = @_;     $SIG{__DIE__}  = sub { report( shift, 2 ); exit } if $args{fatal};     $SIG{__WARN__} = \\&report                         if $args{warnings}; } sub report {     my ($message, $level)  = @_;     $level               ||= 1;     my ($filename, $line)  = ( caller( $level - 1 ) )[1, 2];     warn $message, show_source( $filename, $line ); } sub show_source {     my ($filename, $line) = @_;     return '' unless open( my $fh, $filename );     my $start = $line - 2;     my $end   = $line + 2;     local $.;     my @text;     while (<$fh>)     {         next unless $. >= $start;         last if     $. >  $end;         my $highlight   = $. = = $line ? '*' : ' ';         push @text, sprintf( "%s%04d: %s", $highlight, $., $_ );     }     return join( '', @text, "\\n" ); } 1;

The magic here is in three places. report( ) looks at the call stack leading to its current position, extracting the name of the file and the line number of the calling code. It's possible to call this function directly with a message to display (and an optional level of calls to ignore).

show_source( ) simply reads the named file and returns a string containing two lines before and after the numbered line, if possible. It also highlights the specific line with an asterisk in the left column. Note the localization and use of the $. magic variable to count the current line in the file.

import( ) adds global handlers for warnings and exceptions, if requested from the calling module. The difference between the handlers is that when Perl issues a lexical warning, it doesn't affect the call stack in the same way that it does when it throws an exception.

Running the Hack

This short program shows all three ways of invoking SourceCarp:

#!/usr/bin/perl use strict; use warnings; use lib 'lib'; use SourceCarp fatal => 1, warnings => 1; # throw warning open my $fh, '<', '/no/file'; print {$fh}... # report from subroutine report_with_level( ); sub report_with_level {     SourceCarp::report( "report caller, not self\\n", 2 ); } # throw error die "Oops!";

Hacking the Hack

There's no reason to limit your error and warning reporting to showing the file context around the calling line. caller( ) offers much more information, including the variables passed to each function in certain circumstances.[2] It's possible to provide and present this information in a much more useful manner.

[2] See perldoc -f caller.

Overriding the global __WARN__ and __DIE__ handlers is serious business as it can interfere with large programs. A more robust implementation of this hack might work nicely with Carp, not only because it is more widely compatible, but also because that module offers more features. Another possibility is to integrate this code somehow with Log::Log4perl.



Perl Hacks
Perl Hacks: Tips & Tools for Programming, Debugging, and Surviving
ISBN: 0596526741
EAN: 2147483647
Year: 2004
Pages: 141

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net