アプリケーションログは、何か障害が発生した時は調査の起点になりますので大事ですよね。
ロガーは通常、ログレベルを設定できて、debug用の情報などは開発環境だけ出力し、本番環境では抑制しておきます。無駄な大量のログ出力は、それなりにパフォーマンスに悪影響を与えますからね。
しかし、何かエラーが起きたときは、ログ出力が抑制されていることにより、原因がわかりにくくなってしまいます。
Monolog特有の機能であるFingersCrossedは、このジレンマを解消します。
ある特定のレベル(CRITICALなど)のログ出力を指示した瞬間、ログレベルを切り替え、過去にさかのぼってINFOなどのすべてのログを出力します。
これにより、問題が起きた時だけ詳細にログを吐くことができ、障害対応に効果を発揮します。
- Monologのfingers_crossedがようやくわかった - ゆっくり*ゆっくり
- [Monolog][Symfony] Monolog が神過ぎる件 ~エラーログをメールで送ろう~ - Issei.M's Techlog
で、本題なのですが。
Zend Framework2の公式ロガーであるZend\Logには似たような機能がないか調べていたら、Monologからポーティングされているようでした。しかしマニュアルには使い方が書かれていませんでしたので、メモしておきます。
Zend\Logのインストール
まあ、composerでインストールするのが楽ですかね。zf2全体のパッケージ(zendframework/zendframework)をインストール済みなら、Zend\Logも含まれています。
$ mkdir some-project
$ cd some-project
$ composer require 'zendframework/zend-log:*'
Zend\Logはロギング自体を提供するコア部分、出力先へ実際の出力を行うWriter部分、ログ出力の抑制を行うFilter部分、すべてバラバラに作られており、使う場合は組み立て作業が必要です。
Zend\Log\Writer\FingersCrossed の例
<?php
require 'vendor/autoload.php';
use Zend\Log;
use Zend\Log\Writer;
$logger = new Log\Logger;
//実際は適当なファイルなどを指定する。ここではお試しなので標準出力を指定
$writer = new Writer\Stream('php://output');
//FingersCrossed でラッピングする
$fingersCrossed = new Writer\FingersCrossed($writer, Log\Logger::CRIT);
$logger->addWriter($fingersCrossed);
// ここまででloggerの使用準備完了
$logger->debug('debug!!');
$logger->info('info!!');
$logger->notice('notice!!');
$logger->warn('warn!!');
$logger->err('error!!');
sleep(2);
$logger->crit('crit!!');
$logger->alert('alert!!');
$logger->emerg('emerg!!');
Zend\LogのログレベルはsyslogのRFCに準拠して決められていて、debugが一番緩やかなレベル、emergが一番ヤバいレベルとなっています。
ただのロガーであれば、上記コードはdebugからerrまで出力した後、2秒待って、crit以降を出力するはずです。
FingersCrossedでは、critが最初に呼ばれるまでdebugからerrまでのログ出力は抑制されているので、2秒待ってから、一気にdebugからemergeまで出力される挙動になります。
Filter\Priorityとの組み合わせ
なお、FingersCrossedモードで出力する場合でもdebugレベルは出力しなくてよい、というのであれば、予めwriterにpriorityによるfilterを設定しておきましょう。
<?php
require 'vendor/autoload.php';
use Zend\Log;
use Zend\Log\Writer;
$logger = new Log\Logger;
//実際は適当なファイルなどを指定する。ここではお試しなので標準出力を指定
$writer = new Writer\Stream('php://output');
$writer->addFilter(new Log\Filter\Priority(Log\Logger::INFO)); //info以降のみ出力するよう抑制
//FingersCrossed でラッピングする
$fingersCrossed = new Writer\FingersCrossed($writer, Log\Logger::CRIT);
$logger->addWriter($fingersCrossed);
// ここまでで使用準備完了
$logger->debug('debug!!'); //これが出力されることはない
$logger->info('info!!');
$logger->notice('notice!!');
$logger->warn('warn!!');
$logger->err('error!!');
sleep(2);
$logger->crit('crit!!');
$logger->alert('alert!!');
$logger->emerg('emerg!!');
FingersCrossedが使いたいがために、ロガーだけはMonologを使う、なんてことはしなくても大丈夫そうです。ZF使いならZend\Logを使ってしまう方が楽でしょう。
全部バラバラに作ってあるのはお作法として正しいのですが、使うまでの設定処理はこのままだと面倒ですので、実際はフレームワーク付属のDIコンテナなりサービスロケータなりの中で組み立てるとよいでしょう。