パールは、メッセージを記録するための既製のソリューションをいくつか提供しています。 いつものように、それらはすべてCPANに投稿されています。 「ログ」のリクエストに応じて、あらゆる場面で使用できるモジュールを見つけることができます。
ただし、これらすべてのモジュールには、 Log :: Anyという特別なモジュールが1つあります。
このモジュールのロギングの特性は、ロギング自体を処理しないことです。 Log :: Anyモジュールは、ロギングに直接関係する他のモジュールにアクセスするための汎用APIをプログラム(およびプログラマー)に提供します。
Pearlでのロギング方法の選択の問題に苦しんでいるなら、この記事はあなたのためです。
問題
ネットワークからファイルをダウンロードするモジュールがあるとします。 ダウンロードが開始された時間、継続時間、ダウンロードされたバイト数を知りたいと思います。 最も簡単な方法で、モジュールに行を追加できます:
print "$time \n"; # print "$time \n"; print "$time $size \n";
注意してください-時間$時間を計算するために、いくつかの追加の手順が必要になりますが、これは主な問題ではないので、これにも焦点を合わせません。
コマンドラインから手動でこのモジュールを使用してこのスクリプトを実行する限り、すべて問題ありません。 コンソールにメッセージが表示されます。メッセージを読んで、必要なものをすべて見つけてください。
しかし、ある時点で、これらのメッセージを将来のために保管する必要があります。 または、このスクリプトを手でではなく、クローンから実行する必要があります。 または、これらのメッセージをさらに分析するためにデータベースに書き込む必要があります。 さて、ダウンロードされたファイルは多数あり、コンソールから長いログを読み取ることは困難です。
そして、コンソールからのログ出力を別の場所にリダイレクトしたいとします。 ファイル、データベース、別のプログラム、APIによるWebサービス、角地獄など。 ここで問題が発生します-メッセージをどこで正確に記録するのですか?
- ファイルに書き込みますか? ファイルを書き込めない環境でモジュールを使用するとどうなりますか?
- データベースに書き込みますか? データベースを操作するためのモジュールがインストールされない場合はどうなりますか?
- 別のプログラムにリダイレクトしますか? スクリプトのユーザーが、選択した完全に異なるプログラムを好む場合はどうなりますか?
ロギングには多くの優れたモジュールがありますが、それらにはすべて同じ共通の欠点があります-これらのモジュールは複数あります。 ロギングの1つの方法が好きで、ユーザー-もう1つ、顧客-3番目、上司-4番目です。 致命的な欠陥©。
解決策
Log :: Anyモジュールを使用します。
Log :: Anyは、誰もが満足できるように上記の選択の問題を解決します-プログラマーは、ログに送信したいものだけを考え、ログのユーザーは受信したメッセージをどこでどのように書くかを自分で決めます。
このモジュールのアイデアは、ロギングプロシージャを互いに独立して動作できる2つの部分に分割することです。 同時に、メッセージを生成する部分には、これらのメッセージがさらにどこでどのように記録されるかがわかりません。 そして、メッセージをどこかに記録する部分には、これらのメッセージがどこから来て、どのように形成されたかがわかりません。
仕組み
図の点線はuseを介したモジュールの接続を示し、実線はログメッセージの移動方向を示します。
メッセージを送信する
メッセージを送信するために、コード(モジュール内またはスクリプト内、重要ではありません)は、Log :: Anyモジュールが提供する標準関数を呼び出します。
# Log::Any $log, use Log::Any qw($log); # - ( error ) $log->error("- - ");
上記の2行は、ロギングを開始するために必要なすべてです。 ただし、ログを書き込む場所をまだ選択していないため、これまでに送信されたメッセージはどこにも記録されません。
メッセージ録音
ここで、ログを書き込む場所を決定する必要があります。 これを行うには、スクリプトでアダプターのいずれかを使用します。
# use Log::Any::Adapter ('Stdout'); # use Log::Any::Adapter ('File', '/path/to/file.log'); # Log::Dispatch use Log::Dispatch; my $log = Log::Dispatch->new(outputs => [[ ... ]]); Log::Any::Adapter->set( { category => 'Foo::Baz' }, 'Dispatch', dispatcher => $log );
Logを完備:: Anyには、File、Stdout、およびStderrといういくつかの単純な組み込みアダプターが付属しています。 名前が示すように、最初のファイルはメッセージをファイルに書き込み、残りの2つはメッセージを標準出力に送信します。
CPANの組み込みアダプターに加えて、 Log4perlやSyslogなどの外部アダプターを見つけることができます。 外部アダプターを使用すると、Twitterを含め、どこにでもログを書き込むことができます。
Facebookはどうですか? 問題ありません。 独自のアダプタを何にでも簡単に書くことができます。 アダプターの作成については、 モジュールのドキュメント 、またはロシア語で説明されています 。
タスクは上記のリンクで詳しく説明されているため、ここではアダプターの作成の詳細には触れません。 代わりに、別のタスク-既存のコードへのログインの実装を検討します。
既存のアクションのログ
コネクター
コードをゼロから作成する場合、すぐに適切な場所にロギングを追加できます。 ただし、すでに一定量のコードがある場合、ロギング機能を追加するには、既存のコードを掘り下げて修正するために多くの作業が必要になる場合があります。 代わりに、(ほとんど)何もせずにロギングを追加する方法があります。
これを行うには、コネクタを使用する必要があります。これは、既存のコードに接続してロギング機能を追加するモジュールです。
Remarque-「コネクタ」という用語を自分で作成しました。他の一般的に受け入れられている名前があるかもしれません。
コネクタモジュールは通常、Log :: Any :: For名前空間にあります。 Log :: Any :: For :: DBIまたはLog :: Any :: For :: LWPなど、既成のコネクタがいくつかあります。
独自のコネクタを記述することは、コネクタが実際に書き込まれるものに強く依存するため、形式化されていません。 一般に、コネクタは次のように機能します。
- ログに記録する必要があるイベントはインターセプトされます。 これには、 mokやtieなど、さまざまな手段を使用できます。
- イベントメッセージは、標準のcall $ log->メソッド( 'message')を使用してログに送信されます。
コネクタの使用は次のように行われます(例としてLWPコネクタを使用):
# LWP use LWP::Simple # LWP use Log::Any::For::LWP; # LWP get "http://www.google.com/";
このコードでは、コネクタへの接続を除き、ログにメッセージを送信するための明示的なアクションは実行されていません。 ただし、get関数はログを魔法のように受け取り、あらゆる種類の有用なメッセージを出力し始めます。
警告と例外のログ記録
残念なことに、プログラムでは、予見されないイベントが時々発生し、良い場合は発生しません。 たとえば、一部の関数では、初期化されていない変数へのアピールが発生したり、データベースへの接続が切断されたり、プログラムが一般にクラッシュしたりすることが突然判明しました。 このような場合、パールインタープリターは警告または例外をスローします。
コンソールから実行した単純なスクリプトでこのようなイベントが発生した場合、対応するメッセージが目の前に表示され、すぐに表示されます(これも事実ではありません)。 プログラムがKronから起動されるか、Webサーバーまたはそのような何かの制御下で実行される場合、そのようなメッセージに気付くのはそれほど簡単ではありません。
正しい解決策は、そのようなメッセージをログに書き込むことです。 しかし、どのように? 結局のところ、$ logオブジェクトを介してプログラマーによって明示的に送信されたメッセージがログに書き込まれ、インタープリターは警告と例外をスローします。インタープリターは素晴らしいロギングについては何も知らず、すべてのメッセージを単にSTDERRにダンプします。
そのため、STDERRに送られるすべてをログに強制する必要があります。
この問題を解決するために、CPANに適切なコネクターが見つからなかったため、独自のLog :: Any :: For :: Stdを作成しました 。 このコネクタは、プログラム実行の任意の段階で、インタープリターのすべての可能なメッセージをログに送信します。
STDERRをインターセプトするには、tie関数を使用します。
tie *STDERR, __PACKAGE__;
この設計は、必要なパッケージのSTDERRに送信されるすべてを完全にラップし、Log :: Anyを使用してメッセージをログにリダイレクトすることはもはや難しくありません。
必要に応じて、他のコネクタを実装して、警告と例外をインターセプトできます(または、まったくインターセプトできません)。
メッセージフィルタリング
既存のコードがすでにいくつかのメッセージを表示するような状況が発生する場合があります。 たとえば、すべてのロギングが次のように行われるレガシーコードを使用した大規模なプロジェクトがあります。
print STDERR "$time --- $login --- $pid --- \n";
ご覧のとおり、ここではすべてのメッセージがSTDERRに送信され、メッセージテキストにはすべての種類の変数、区切り記号、および改行があります。 さらに、これは目には見えませんが、すべてのメッセージはutf8なしで書き込まれます。
STDERRからログへのメッセージのリダイレクトは、Log :: Any :: For :: Stdコネクタを使用して簡単に解決できますが、メッセージテキストから余分なゴミを個別に削除する必要があります。 これを行うには、Log :: Anyモジュールを接続するときに、フィルタリングを有効にする必要があります。
これは次のように行われます。
use Log::Any '$log', filter => sub { my $msg = $_[2]; utf8::decode($msg); return $msg };
$ log-> method()を使用してログに送信される各メッセージは、フィルター引数で指定された関数を介して渡されます。 この関数の変数$ _ [2]には、送信するメッセージが含まれています。 メッセージで何かをしたい場合は、この変数からそれを取得し、変更して返さなければなりません。 返された値はログに書き込まれます。
たとえば、上記のコードでは、メッセージテキストはutf8にキャストされます。
スクリプトの例
これらすべてをtest.plスクリプトにまとめましょう。
#!/usr/bin/perl use strict; use warnings; use Log::Any '$log', filter => sub { my $msg = $_[2]; $msg =~ s///; return $msg }; use Log::Any::Adapter ('Stdout'); use Log::Any::For::Std; print " \n"; $log->info(" "); $log->info(" "); Module::func(); warn " "; die " "; # package Module; use Log::Any '$log'; sub func { $log->info(" "); }
以下を開始し、確認します。
$ ./test.pl at ./test.pl line 18. at ./test.pl line 20.
すべてのメッセージはコンソールに表示されますが、あなたの目を信じないでください-最初の行だけが通常の出力であり、他のすべてはログです。 このログだけがコンソールに表示されます。
しかし、コンソールではなくファイルにログを突然送信したい場合はどうでしょうか? Log :: Anyモジュールでは簡単なことはありません:
# # use Log::Any::Adapter ('Stdout'); # use Log::Any::Adapter ('File', 'file.log');
開始して確認します。
$ ./test.pl
予想どおり、印刷のみがコンソールに出力されます。
しかし、残りはどこに行きましたか:
$ cat file.log [Fri Jun 19 17:25:44 2015] [Fri Jun 19 17:25:44 2015] [Fri Jun 19 17:25:44 2015] [Fri Jun 19 17:25:44 2015] at ./test.pl line 18. [Fri Jun 19 17:25:44 2015] at ./test.pl line 20.
まとめ
- ログ::任意のモジュールを使用すると、プログラムやモジュールに柔軟なログを追加できます。ログを保存する方法を変更するときに、これらを再実行する必要はありません。
- アダプタログ::任意::アダプタを使用すると、ログを保存する任意の方法にプログラムを適合させることができます
- ログ::任意::コネクタの場合、任意のメッセージソースにログを接続できます