CGI::Carpでド派手なデバッグスクリーンを表示させる(2)
前回のはいささか手抜きが過ぎたので、nekokakさんのCGI::Application::Plugin::DebugScreenを参考にちょろっとブラッシュアップ。いくらなんでもここや本家のような緑系で画面つくるのはエラー画面っぽくないよなあということでCSSはほとんどnekokakさんの(ひいてはid:nipotanさんやid:tokuhiromさんの、ですね。失礼しました)パクリですが、地味なところでoverflow(これはKickstart my heartを見ていいなあと思ったもの)とか入れてあります。あとはデバッグモードかどうかで吐き出すエラー画面が異なるのが特徴といえば特徴。フラグひとつ変えればそのままプロダクション環境に移行……しちゃ本当はまずいんですが、まあ、そういうこともできるようにということで。
今度は use CGI::Carp qw/fatalsToBroswer/ のかわりに use CGI::Carp::DebugScreen するだけでそれなりの画面が出るはず。設定は
use CGI::Carp::DebugScreen ( debug => 1, engine => 'HTML::Template', lines => 5, modules => 1, environment => 1, raw_error => 1, );
のような格好で。
- CGI::Carp::DebugScreen
package CGI::Carp::DebugScreen; { use strict; use warnings; use Exporter; use CGI::Carp qw/fatalsToBrowser/; our $VERSION = '0.01'; BEGIN { my $MyDebug = 0; CGI::Carp::set_message( sub { __PACKAGE__->show(@_) } ) unless $MyDebug; } $Carp::Verbose = 1; # for stack traces my $Debug = 1; my $Engine = 'HTML::Template'; my $ShowLines = 3; my $ShowMod; my $ShowEnv; my $ShowRawError; my $Template; my $ErrorTemplate; sub import { my $pkg = shift; my %options = @_; while(my ($key, $value) = each %options) { next unless defined $value; $key = lc $key; $Debug = $value if $key =~ /^d(?:ebug)?$/; $Engine = $value if $key =~ /^e(?:ngine)?$/; $ShowLines = $value if $key =~ /^l(?:ines)?$/; $ShowMod = $value if $key =~ /^m(?:od(?:ules)?)?$/; $ShowEnv = $value if $key =~ /^env(?:ironment)?$/; $ShowRawError = $value if $key =~ /^raw(?:_error)?$/; $ErrorTemplate = $value if $key =~ /^e(?:rror_)?t(?:emplate)?$/; } } sub show_modules { shift; $ShowMod = shift; } sub show_environment { shift; $ShowEnv = shift; } sub show_raw_error { shift; $ShowRawError = shift; } sub debug { shift; $Debug = shift; } sub set_engine { shift; $Engine = shift; } sub set_template { shift; $Template = shift; } sub set_error_template { shift; $ErrorTemplate = shift; } sub get_contents { my ($file, $line_no) = @_; my @contents; if (open my $fh, '<'.$file) { my $ct = 0; while(my $line = <$fh>) { $ct++; next if $ct < $line_no - $ShowLines; last if $ct > $line_no + $ShowLines; push @contents, { no => $ct, line => $line, hit => ($ct == $line_no), }; } } \@contents; } sub show { my ($pkg, $errstr) = @_; my $first_message = ''; my @traces = grep { my $caller = $_->{package}; ( $caller eq $INC{'Carp.pm'} or # ignore Carp; $caller eq $INC{'CGI/Carp.pm'} # ignore CGI::Carp; ) ? 0 : 1; } map { my $line = $_; my ($message, $caller, $line_no) = $line =~ /^(?:\s*)(.*?)(?: called)? at (\S+) line (\d+)\.?$/; $first_message = $message unless $first_message && defined $message; my $file = $INC{$caller} || $caller; my $contents = get_contents($file,$line_no); +{ message => $message, package => $caller, file => $caller ne $file ? $file : '', contents => $contents, line => $line_no, } } split(/\n/,$errstr); my $error_at = $traces[$#traces]->{package}; my @modules = (); @modules = map { my $key = $_; (my $package = $key) =~ s|/|::|g; +{ package => $package, file => $INC{$key}, } } sort {$a cmp $b} keys %INC if $ShowMod; my @environment = (); @environment = map { +{ key => $_, value => $ENV{$_}, } } sort {$a cmp $b} keys %ENV if $ShowEnv; my $viewer = __PACKAGE__.'::'.$Engine; eval "require $viewer"; if ($@) { print "<html><body><pre>Error: $errstr</pre></body></html>"; exit; } $viewer->show( show_raw_error => $ShowRawError, raw_error => $errstr, error_at => $error_at, error_message => $first_message.' at '.$traces[0]->{package}.' line '.$traces[0]->{line}, traces => \@traces, modules => \@modules, environment => \@environment, debug => $Debug, tmplref => \$Template, error_tmpl => $ErrorTemplate, ); } } 1;
- CGI::Carp::DebugScreen::HTML::Template
package CGI::Carp::DebugScreen::HTML::Template; { use strict; use warnings; use HTML::Template; our $VERSION = '0.01'; my $Template =<<'EOT'; <TMPL_IF NAME="debug"> <html> <head> <title>Debug Screen</title> <style type="text/css"> <!-- body { font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana, Tahoma, Arial, helvetica, sans-serif; color: #000; background-color: #f90; margin: 0px; padding: 0px; } :link, :link:hover, :visited, :visited:hover { color: #333; } div#page { position: relative; background-color: #fff; border: 1px solid #aaa; padding: 4px; margin: 10px; -moz-border-radius: 10px; } div.navi { color: #333; padding: 0 4px; } div.box { background-color: #fff; border: 3px solid #fc9; padding: 8px; margin: 4px; margin-bottom: 10px; -moz-border-radius: 10px; } h1 { margin: 0; color: #666; } h2 { margin-top: 0; margin-bottom: 10px; font-size: medium; font-weight: bold; text-decoration: underline; } table.code { font-size: .8em; line-height: 120%; font-family: 'Courier New', Courier, monospace; background-color: #fc9; color: #333; border: 1px dotted #000; margin: 8px; width: 90%; border-collapse: collapse; } table.code tr.hit { font-weight: bold; color: #000; background-color: #E68200; } table.code td { padding-left: 1em; line-height: 130%; } table.code td.num { width: 4em; text-align:right } div.scrollable { font-size: .8em; overflow: auto; } pre.raw_error { background-color: #fff; border: 3px solid #fc9; padding: 8px; margin: 4px; margin-bottom: 10px; -moz-border-radius: 10px; font-size: .8em; line-height: 120%; font-family: 'Courier New', Courier, monospace; overflow: auto; } ul#traces, ul#modules { margin: 1em 1em; padding: 0 1em; } table#environment { margin: 0 1em; } --> </style> </head> <body> <a name="top"></a> <div id="page"> <h1><TMPL_VAR NAME="error_at" ESCAPE=HTML></h1> <TMPL_IF NAME="show_raw_error"> <pre class="raw_error"><TMPL_VAR NAME="raw_error"></pre> <TMPL_ELSE> <div class="box"> <TMPL_VAR NAME="error_message"> </div> </TMPL_IF> <div class="navi"> [<a href="#top">top</a>] [<a href="#traces">traces</a>] <TMPL_IF NAME="modules">[<a href="#modules">modules</a>]</TMPL_IF> <TMPL_IF NAME="environment">[<a href="#environment">environment</a>]</TMPL_IF> </div> <div class="box"> <h2><a name="traces">Stack Traces</a></h2> <ul id="traces"> <TMPL_LOOP NAME="traces"> <li> <TMPL_VAR NAME="package" ESCAPE=HTML> <TMPL_IF NAME="file"> (<TMPL_VAR NAME="file" ESCAPE=HTML>)</TMPL_IF> LINE : <TMPL_VAR NAME="line" ESCAPE=HTML> </li> <table class="code"> <TMPL_LOOP NAME="contents"> <TMPL_IF NAME="hit"><tr class="hit"><TMPL_ELSE><tr></TMPL_IF> <td class="num"><TMPL_VAR NAME="no" ESCAPE=HTML>:</td> <td><TMPL_VAR NAME="line" ESCAPE=HTML></td> </tr> </TMPL_LOOP> </table> </TMPL_LOOP> </ul> </div> <TMPL_IF NAME="modules"> <div class="navi"> [<a href="#top">top</a>] [<a href="#traces">traces</a>] <TMPL_IF NAME="modules">[<a href="#modules">modules</a>]</TMPL_IF> <TMPL_IF NAME="environment">[<a href="#environment">environment</a>]</TMPL_IF> </div> <div class="box"> <h2><a name="modules">Included Modules</a></h2> <ul id="modules"> <TMPL_LOOP NAME="modules"> <li><TMPL_VAR NAME="package" ESCAPE=HTML> (<TMPL_VAR NAME="file" ESCAPE=HTML>)</li> </TMPL_LOOP> </ul> </div> </TMPL_IF> <TMPL_IF NAME="environment"> <div class="navi"> [<a href="#top">top</a>] [<a href="#traces">traces</a>] <TMPL_IF NAME="modules">[<a href="#modules">modules</a>]</TMPL_IF> <TMPL_IF NAME="environment">[<a href="#environment">environment</a>]</TMPL_IF> </div> <div class="box"> <h2><a name="environment">Environmental Variables</a></h2> <table id="environment"> <TMPL_LOOP NAME="environment"> <tr> <td><TMPL_VAR NAME="key" ESCAPE=HTML></td> <td><div class="scrollable"><TMPL_VAR NAME="value" ESCAPE=HTML></div><//td> </tr> </TMPL_LOOP> </table> </div> </TMPL_IF> </div> </body> </html> <TMPL_ELSE> <TMPL_IF NAME="error_tmpl"> <TMPL_VAR NAME="error_tmpl"> <TMPL_ELSE> <html> <head> <title>An unexpected error has been detected</title> <style type="text/css"> <!-- body { font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana, Tahoma, Arial, helvetica, sans-serif; color: #000; background-color: #F90; margin: 0px; padding: 0px; } div#page { position: relative; background-color: #fff; border: 1px solid #aaa; padding: 4px; margin: 10px; -moz-border-radius: 10px; } div.box { background-color: #fff; border: 3px solid #FFCC99; padding: 8px; margin: 4px; margin-bottom: 10px; -moz-border-radius: 10px; } h1 { margin: 0; color: #666; } p { margin: 1em; } --> </style> </head> <body> <div id="page"> <h1>An unexpected error has been detected</h1> <p>Sorry for inconvenience.</p> </div> </body> </html> </TMPL_IF> </TMPL_IF> EOT sub show { my ($pkg, %options) = @_; my $tref = ${$options{tmplref}} ? $options{tmplref} : \$Template; my $tmpl = HTML::Template->new( scalarref => $tref, die_on_bad_params => 0, ); delete $options{tmplref}; $tmpl->param(%options); print $tmpl->output; } } 1;
もうちょい整理してPOD書いたらアップするつもりです。