Perlで警告や例外を捕捉してログに書き込む
すべてのPerlスクリプトとモジュールで警告をオンにすることが推奨されていますが、Perlからの警告をユーザーに見せたくない場合があります。
一方では、コードの前に 警告を使用する というセーフティネットがある一方で、画面には警告が表示されるのが普通です。多くの場合、お客様はこの警告をどのように扱えばいいのかわからない。運が良ければ、これらの警告はクライアントを驚かせるだけだが、もちろん、不幸にもクライアントはそれを修正しようとする...。(ここではPerlプログラマーの話ではない。)
第三に、これらの警告は後の分析のために保存しておくとよいでしょう。
また、Perlスクリプトやアプリケーションでは、use warningsを使用せず、#!で-wを使用しているところが多くあります。 use warningsを追加すると、多くの警告を発生させることができます。
長期的には、もちろんこれらの警告をなくすことが重要ですが、短期的にはどうでしょうか。
長期的な計画を立てたとしても、完全にバグのないコードを書くことはできませんし、アプリが今後絶対に警告メッセージを表示しないことを保証することもできません。
できる?
画面に出力される前に警告をキャプチャすることができます。
シグナル
Perl には %SIG という組み込みのハッシュ テーブルがあり、キーはオペレーティング システムのシグナルの名前になっています。対応する値は、特定のシグナルがトリガーされたときに呼び出される関数(ほとんどは関数リファレンス)です。
システムが提供する標準的なシグナルに加えて、Perlは2つの内部シグナルを追加しています"signal"。ひとつは <h__warn__< span=""> で、コードが warn() 関数を呼び出すたびにトリガーされます。もうひとつは __DIE__ で、die() を呼び出すたびに発生します。
今回は、これらが警告メッセージにどのような影響を与えるかを見ていきましょう。
匿名機能
sub { } は無名関数です。つまり、関数本体を持ち、名前がない関数です。(この例では関数本体も空ですが、意味はおわかりいただけると思います)。
キャッチ警告 - 未対応
以下のコードを追加した場合。
local $SIG{__WARN__} = sub {
# This is where you get the warning message
};
これは、プログラムのどこかで警告メッセージが発生するたびに、それに対して何もしないことを効果的に意味します。基本的に、これはすべての警告を隠します。
警告をキャッチする - そして例外に変換する
書くこともできます。また、次のように書くこともできます。
local $SIG{__WARN__} = sub {
die;
};
これにより、警告が発生するたびに die() が呼び出され、各警告は例外に変換されることになります。
例外に警告メッセージを含めたい場合は、このように書きます。
local $SIG{__WARN__} = sub {
my $message = shift;
die $message;
};
実際の警告メッセージは、無名関数への唯一のパラメータとして渡されます。
警告を捕捉し、ログに書き込む
その間に何か別のことをした方がいいかもしれません。
ノイズの多い警告メッセージをフィルタリングし、後で解析する:。
local $SIG{__WARN__} = sub {
my $message = shift;
logger($message);
};
ここでは、logger()を実装する書き込みロギング関数とします。
ログの書き込み
あなたのアプリケーションには、すでにログの仕組みがあることを前提としています。もしそうでなければ、それを追加するのがよいでしょう。追加できない場合でも、オペレーティング・システムの組み込みのロギング・メカニズムが必要です。例えば、Linuxのsyslog、MS WindowsのEvent Logger、その他のオペレーティング・システムには、独自の内部ロギング・メカニズムがあります。
この記事の例では、この考えを表すために自作のlogger()関数を使用しています。
ログの取り込みと書き込みの完全な例
#! /usr/bin/perl
use strict;
use warnings;
local $SIG{__WARN__} = sub {
my $message = shift;
logger('warning', $message);
};
my $counter;
count();
print "$counter\n";
sub count {
$counter = $counter + 42;
}
sub logger {
my ($level, $msg) = @_;
if (open my $out, '>>', 'log.txt') {
chomp $msg;
print $out "$level - $msg\n";
}
}
上記のコードにより、log.txt ファイルに以下の行が追加されます。
Use of uninitialized value in addition (+) at code_with_warnings.pl line 14.
変数$counterと関数count()は、警告生成の例の一部でしかありません。
警告ハンドラ関数の警告メッセージ
WARN__ は、そのハンドラ関数の実行中に自動的に無効化されます。そのため、警告ハンドラ関数の実行中に生成された(新しい)警告メッセージは、無限ループを引き起こしません。
詳しくは、perlvarのドキュメントをご覧ください。
複数の警告を避ける
注意点としては、警告メッセージが重複しているとログファイルがいっぱいになってしまうことです。単純なキャッシュのような機能を使って、重複する警告メッセージの数を減らすことができるんだ。
#! /usr/bin/perl
use strict;
use warnings;
my %WARNS;
local $SIG{__WARN__} = sub {
my $message = shift;
return if $WARNS{$message}++;
logger('warning', $message);
};
my $counter;
count();
print "$counter\n";
$counter = undef;
count();
sub count {
$counter = $counter + 42;
}
sub logger {
my ($level, $msg) = @_;
if (open my $out, '>>', 'log.txt') {
chomp $msg;
print $out "$level - $msg\n";
}
}
ご覧のように、変数$counterをundefに代入してから、再度count()関数を呼び出すと、同じ警告が発生します。
また、__WARN__ハンドラ関数を少し複雑なバージョンに置き換えると
my %WARNS;
local $SIG{__WARN__} = sub {
my $message = shift;
return if $WARNS{$message}++;
logger('warning', $message);
};
logger を呼び出す前に、現在の文字列が %WARNShash テーブルにすでにあるかどうかを確認します。ない場合、追加され、logger() が呼び出されます。すでに存在する場合は、return を呼び出し、同じイベントを 2 回ログに記録しないようにします。
配列の中の一意な値についても、同じような考え方をしたことを思い出してください。
ローカルとは何ですか?
上記のすべての例で、効果をローカライズ(警告処理)するためにlocal関数を使用しました。厳密には、これらの例では、メインスクリプトの最初の部分であると想定しているので、このようなことをする必要はありません。この場合は、結局のところグローバルスコープの中にあるので、問題ありません。
しかし、そのように使用したほうがよいでしょう。
local は、モジュールの変更を (警告に) 制限するために重要です。特に、公開される予定のモジュールについては。ローカライズしないと、アプリケーション全体に影響を及ぼします。limit は、それが含まれる閉じたコードブロックに影響を制限します。
グローバルな %WARNS の使用は避ける
Perl 5.10 以降をお使いの場合、スクリプトを use v5.10; で開始し、匿名関数内で state キーワードを使用して変数を宣言することで、グローバル変数 %WARNS を置き換えるようにコードを書き換えることができます。
#! /usr/bin/perl
use strict;
use warnings;
use v5.10;
local $SIG{__WARN__} = sub {
state %WARNS;
my $message = shift;
return if $WARNS{$message}++;
logger('warning', $message);
};
関連
-
Perlの文字列処理関数
-
Perl学習ノート - CPANの使い方入門
-
[解決済み】グローバルシンボルはパッケージ名を明示する必要がある
-
Perlの制御構造に関する学習ノート
-
PerlモジュールData::Dumperを使用した共有例です。
-
perlのour-my-localスコープ宣言の紹介
-
Perl 組み込み特殊変数まとめ
-
[解決済み] Perlからsedを使うには?
-
[解決済み] strict pragma のもとで変数を NULL に設定するにはどうすればよいですか?
-
問題発生 ----DBI ODBCエラー Perlスクリプトを実行中、エラー:[unixODBC][Driver Manager] データソース名が見つからない、およびデフォルトがない
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
指定したフォルダーにあるリンク切れファイルのシンボリックリンクを自動的に削除するスクリプト
-
Perlについて(Perl公式サイトの翻訳です。)
-
Windows の Thumbs.db から画像キャッシュのサムネイルを削除する Perl
-
perlを使ってデータテーブル(mysql)を分割し、データインスタンスを移行する。
-
PerlによるMSSQLへのアクセスとMySQLデータベースへの移行スクリプト例
-
perlのsrand()とtime関数の使い方の紹介
-
File::Basename を使用してファイル拡張子を取得する Perl コード
-
perl変数$/の使用方法について説明します。コンテキストが行モードのとき、$/は行を区別するものを定義します。
-
[解決済み] CPAN モジュールをすべて最新版に更新するにはどうしたらいいですか?
-
[解決済み] Perlでタイムアウトを行う方法?