ルモーリン

Perlサンプル18 エラーを表示するCGI

投稿:2020-02-07

CGIの実行でエラーが発生すると「500 Internal Server Error」と表示されるだけで、エラーの内容がさっぱり分かりません。 エラーが発生した際に、どんなエラーがどの行で起きたのか分かれば対策も素早くできますよね。

#/usr/bin/perl

use utf8;
use strict;
use warnings;

use CGI qw/ -utf8 :html /;
use DateTime;
use Encode::Locale;

binmode STDOUT, ":utf8";

use constant TITLE => "Perlサンプル18の実行結果";

# エラー発生時のトレースバックを記録
my @trace_info;
$SIG{__DIE__} = sub {
	for (0 .. 2) {
		my (undef, $filename, $line, $subroutine) = caller $_;
		push @trace_info, "$filename - line $line. → $subroutine<br />" if $filename;
	}
};

my $cgi = CGI->new;
print $cgi->header(
	-charset => "utf-8",
);
print $cgi->start_html(
	-title => TITLE,
	-lang => "ja",
);
print $cgi->p(TITLE);
my $dt = DateTime->now(time_zone => "local");
print $cgi->p($dt->strftime("只今の日時は%F %Tです。"));
my $runcondition = $cgi->param("runcondition");
print $cgi->p("処理内容:$runcondition");
print $cgi->p("処理開始");

# 実際にやりたい処理
my @out_html;
eval {
	# evalの中でエラーが発生するとevalの外に抜ける
	@out_html = myfunc($runcondition);
};

# エラー発生の場合
if ($@) {
	# エラーメッセージを表示
	print $cgi->p("エラー発生");
	print $cgi->p($@);

	# トレースバックを表示
	print $cgi->p("トレースバック");
	print $cgi->p($_) for @trace_info;
} else {
	# 正常に処理した場合
	print $_ for @out_html;
}

print $cgi->p("処理終了");

print $cgi->a({ href => "../sample/0i_perlsample_018.html" }, "元のページに戻る");
print $cgi->end_html();

exit;

# やりたい処理(本当?)
sub myfunc {
	my ($run) = @_;

	my @html;

	if ("perlerror" eq $run) {
		# Perlのエラー発生
		# 未定義のサブルーチンを呼んでエラーを発生させる
		myfunc_undef();
	} elsif ("die" eq $run) {
		# 自分でエラーを発生させる
		die "何かエラーが見つかりました(泣";
	} elsif ("normal" eq $run) {
		# 正常ケース
		push @html, $cgi->p("正常に処理しました♪");
	} else {
		# フォーム不一致
		die "予定外のフォームから送信されました(あれ?)";
	}

	return @html;
}

実行できるフォームを用意しました。 エラーの種類を選択して送信ボタンをクリックしてください。

正常ケースの場合です。 エラーを検知していないので特に表示されません。

Perlサンプル18の実行結果

只今の日時は2020-02-07 17:51:27です。

処理内容:normal

処理開始

正常に処理しました♪

処理終了
元のページに戻る

Perlのエラーが発生した場合です。 このケースは発生場所が76行目と分かります。

Perlサンプル18の実行結果

只今の日時は2020-02-07 17:53:11です。

処理内容:perlerror

処理開始

エラー発生

Undefined subroutine &main::myfunc_undef called at perlsample_018.cgi line 76.

トレースバック

perlsample_018.cgi - line 76. → main::__ANON__

perlsample_018.cgi - line 43. → main::myfunc

perlsample_018.cgi - line 41. → (eval)

処理終了
元のページに戻る

処理中にエラーを見つけてdieした場合です。 このケースは発生場所が79行目と分かります。

Perlサンプル18の実行結果

只今の日時は2020-02-07 17:58:05です。

処理内容:die

処理開始

エラー発生

何かエラーが見つかりました(泣 at perlsample_018.cgi line 79.

トレースバック

perlsample_018.cgi - line 79. → main::__ANON__

perlsample_018.cgi - line 43. → main::myfunc

perlsample_018.cgi - line 41. → (eval)

処理終了
元のページに戻る

処理したい内容をevalで囲むと処理中のエラーを捕捉してevalを抜けるのでPerlのエラーで止まるのを防止できます。 エラーの有無は$@にエラーメッセージがあるかどうかで判断します。 エラーの発生場所が共通サブルーチンですと、その呼び出し元を知りたくなるのでトレースバックを取得します。 エラー発生のタイミングで$SIG{__DIE__}に設定したサブルーチン(のリファレンス)が呼ばれるので、その際に記録します。 トレースバック情報はcallerを呼んで取得します。