本日も乙

ただの自己満足な備忘録。

Monologでロギングする

プログラムを作成しているときに、必要な情報(ログイン情報やエラー情報)をログに出力することが多いと思います。Monologは強力なロギングライブラリで、ただファイルに出力するだけでなく、データベースに保存したり、メール送信したり、FireBugに出力したりと様々なことができます。
今回は、MonologをComposerでインストールして、ロギングするサンプルプログラムを作成してみます。

今回の目標

  • MonologをComposerでインストールする
  • Monologを使ってロギングしてみる

サーバ環境

Composerでインストール

composer.jsonを記述します。

// composer.json
{
    "require": {
        "monolog/monolog": "@stable"
    }
}

composer installを実行すればインストールは完了です。

$ composer install

Monologでロギング

基本的なロギング

以下のサンプルプログラムを見ていきます。

src/monolog_sample_1.php

<?php

namespace BlogSample\MonologSample1;

require_once __DIR__ . '/../vendor/autoload.php';

use Monolog\Logger;
use Monolog\Handler\StreamHandler;

$logging_path = __DIR__ . '/../logs/foo_test_1.log';

$logger = new Logger('foo_test');

// 基本的なロギング
$logger->pushHandler(new StreamHandler($logging_path, Logger::INFO));

// デバッグレベルなのでロギングされない
// $logger->debug()と同じ
$logger->addDebug('debug_bar');

// 以下、すべてロギングされる
// 下にいくほどログレベルが高くなる

// $logger->info()と同じ
$logger->addInfo('info_bar');

// $logger->notice()と同じ
$logger->addNotice('notice_bar');

// $logger->warning(), $logger->warn()と同じ
$logger->addWarning('warning_bar');

// $logger->error(), $logger->err()と同じ
$logger->addError('error_bar');

// $logger->critical(), $logger->crit()と同じ
$logger->addCritical('critical_bar');

// $logger->alert()と同じ
$logger->addAlert('alert_bar');

// $logger->emergency(), $logger->emerg()と同じ
$logger->addEmergency('emergency_bar');

exit;

ポイントをいくつか見ていきます。

  • 15行目でログをどのように扱うかのハンドラをスタックで格納します。ドキュメントから分かるように、多くのハンドラをサポートしています。
    よく使うと思われるのは、ログファイルにロギングする StreamHandler, 日付でログファイルをローテートしてくれる RotatingFileHandler, Syslogにロギングする SyslogHandler, 前回紹介したSwiftMailerでメール送信できる SwiftMailerHandler, MongoDBHandler などのデータベース(NoSQL)にログを格納するハンドラ、FireBugChromePHPにログを出力することでブラウザからデバッグ作業が行える FirePHPHandler, ChromePHPHandlerや、ログをバッファに格納しておいて指定したログレベルに到達したらバッファに格納しておいたログを出力する特殊な FingersCrossedHandler です。pushHandler()メソッドによって、1つのハンドラだけではなく複数のハンドラを同時に扱うこともできます。
  • 同じく15行目で、扱うログレベルを設定します(ログレベルについては後述)。そして、19行目以降にロギングしていますが、設定したログレベル(今回はINFO)より低いログレベルの場合は取扱いしません。今回の場合はINFOよりログレベルが低いDEBUGのログがロギングされません。
    環境によってこのログレベルを変えていく必要があると思います。例えば、開発環境では細かい挙動まで知りたい場合は、DEBUGまですべての情報をログに出力する必要があると思いますが、本番環境では必要最低限のログ出力だけで良いので、CRITICALやALERTやそれ以上を指定するはずです。
  • ログレベルについては、ドキュメントや、RFC 5424にもありますが、こちらでもまとめておきます。下にいくほどログレベルが高くなり、緊急度も高くなります。
ログレベル 説明
DEBUG 詳細なデバッグ情報
INFO 情報。重要なイベント。 例として、SQLのログ、ユーザーのログイン情報など
NOTICE 注意。正常だが重要なイベント
WARNING 警告。実行時には問題ないが正常ともいえない何らかの予期しない問題。例として、廃棄予定(deprecated)のAPIの使用など
ERROR エラー。即時アクションは必要ないが、監視されるべきであるランタイムエラー
CRITICAL クリティカルなエラー。例として、アプリケーションのコンポーネントが使用できない、予期しない例外など
ALERT アラート。SMS・メール通知などアクションをすぐ起こす必要がある。例として、ウェブサイトがダウン、データベースに接続できないなど
EMERGENCY 緊急。システムが使用不能である

サンプルプログラムを実行すると、以下のようにログファイルに出力されます。

$ php ./src/monolog_sample_1.php

$ cat logs/foo_test_1.log
[2014-05-11 11:12:13] foo_test.INFO: info_bar [] []
[2014-05-11 11:12:13] foo_test.NOTICE: notice_bar [] []
[2014-05-11 11:12:13] foo_test.WARNING: warning_bar [] []
[2014-05-11 11:12:13] foo_test.ERROR: error_bar [] []
[2014-05-11 11:12:13] foo_test.CRITICAL: critical_bar [] []
[2014-05-11 11:12:13] foo_test.ALERT: alert_bar [] []
[2014-05-11 11:12:13] foo_test.EMERGENCY: emergency_bar [] []

メールでロギング情報を送信

SwiftMailerHandler を使ってロギングした情報をメールで送信してみます。
SwiftMailerについては、[PHP]SwiftMailerを使ってメール送信する をご参照ください。

以下サンプルプログラムです *1

src/monolog_sample_2.php

<?php

namespace BlogSample\MonologSample2;

require_once __DIR__ . '/../vendor/autoload.php';

use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SwiftMailerHandler;

$logging_path = __DIR__ . '/../logs/foo_test_2.log';

$mailer = \Swift_Mailer::newInstance(
    \Swift_SmtpTransport::newInstance('localhost', 25)
);

$message = \Swift_Message::newInstance()
    ->setSubject('Monolog Sample')
    ->setTo('bar@bar.com')
    ->setFrom(['foo@foo.com' => 'Mr.FooBar']);

$logger = new Logger('foo_test');

// 基本的なロギング
$logger->pushHandler(new StreamHandler($logging_path, Logger::INFO));

// ログをメール送信する
$logger->pushHandler(new SwiftMailerHandler($mailer, $message, Logger::ALERT));

// ログファイルに出力される
$logger->addInfo('info_bar');

// ログファイルに出力される
$logger->addNotice('notice_bar');

// ログファイルに出力される
$logger->addWarning('warning_bar');

// ログファイルに出力される
$logger->addError('error_bar');

// ログファイルに出力される
$logger->addCritical('critical_bar');

// ログファイルに出力され、メール送信される
$logger->addAlert('alert_bar');

// ログファイルに出力され、メール送信される
$logger->addEmergency('emergency_bar');

exit;
  • 25行目、28行目で2つのハンドラを追加しています。StreamHandler (ログファイルに出力)のログレベルはINFO, SwiftMailerHandler (SwiftMailerでメール送信)のログレベルはALERTで設定しています。
  • 31行目以降はログ出力していますが、メール送信されるのは、ログレベルがALERTとEMERGENCYの場合のみです。

サンプルプログラムを実行すると、以下のようにログファイルに出力されます。

$ php ./src/monolog_sample_2.php

$ cat logs/foo_test_2.log
[2014-05-11 11:30:11] foo_test.INFO: info_bar [] []
[2014-05-11 11:30:11] foo_test.NOTICE: notice_bar [] []
[2014-05-11 11:30:11] foo_test.WARNING: warning_bar [] []
[2014-05-11 11:30:11] foo_test.ERROR: error_bar [] []
[2014-05-11 11:30:11] foo_test.CRITICAL: critical_bar [] []
[2014-05-11 11:30:11] foo_test.ALERT: alert_bar [] []
[2014-05-11 11:30:11] foo_test.EMERGENCY: emergency_bar [] []

メールの以下のようなものが2通届きます。

From:  Mr.FooBar <foo@foo.com>
To:  bar@bar.com
Subject:  Monolog Sample
Body:
[2014-05-11 11:30:11] foo_test.ALERT: alert_bar [] []
From:  Mr.FooBar <foo@foo.com>
To:  bar@bar.com
Subject:  Monolog Sample
Body:
[2014-05-11 11:30:11] foo_test.EMERGENCY: emergency_bar [] []

FingersCrossedHandlerを使う

例えば、本番環境で何かしらのエラーの原因を究明するためにログを見るとします。
このとき、ロギング対象のログレベルがALERTなど高い場合、INFOやNOTICE, WARNINGなどもっと知りたい情報がログに残りません。かといって、ログレベルをINFOまで下げてしまうとエラーでないのに通常の本番環境では不要な情報ばかりがログに出力されてしまい、本当に必要な情報が埋もれてしまいます。
こういう場合、FingersCrossedHandler を使うことで、指定したログレベル以下のログはバッファに格納しておき、あるログレベルの場合に、バッファ内のログを出力することができます。

以下、サンプルプログラムです。

src/monolog_sample_3.php

<?php

namespace BlogSample\MonologSample3;

require_once __DIR__ . '/../vendor/autoload.php';

use Monolog\Logger;
use Monolog\Handler\FingersCrossedHandler;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy;

$logging_path = __DIR__ . '/../logs/foo_test_3.log';

$logger = new Logger('foo_test');

// FingersCrossedHandlerを使う
$logger->pushHandler(
    new FingersCrossedHandler(
        new RotatingFileHandler($logging_path, 2, Logger::INFO),
        new ErrorLevelActivationStrategy(Logger::ALERT)
    )
);

// デバッグレベルなのでロギングされない
$logger->addDebug('debug_bar');

// この時点ではログファイルに出力されない
$logger->addInfo('info_bar');

// この時点ではログファイルに出力されない
$logger->addNotice('notice_bar');

// この時点ではログファイルに出力されない
$logger->addWarning('warning_bar');

// この時点ではログファイルに出力されない
$logger->addError('error_bar');

// この時点ではログファイルに出力されない
$logger->addCritical('critical_bar');

// この時点で上記のログが出力される
$logger->addAlert('alert_bar');

exit;

17行目付近に注目すると、FingersCrossedHandler クラスは2つの引数をとっていることが分かります。 1つ目は、ログを扱うハンドラで、今回はログをローテートできる RotatingFileHandlerを使ってみました。 StreamHandler と異なるのは、ログファイル名に日付が付くのと、保存するファイル数(今回は2)を指定することができます。 2つ目は、ログに出力するログレベルを指定しています。今回の場合、ALERT以上のログレベルの場合に、INFO以上のログレベルの情報をログに出力するようにしています。 ややこしいので、以下の表を見てください。

ログレベル ログに出力するか 備考
DEBUG × RotatingFileHandler でINFOにしているため
INFO ALERT以上のログレベルの場合にログに出力される
NOTICE ALERT以上のログレベルの場合にログに出力される
WARNING ALERT以上のログレベルの場合にログに出力される
ERROR ALERT以上のログレベルの場合にログに出力される
CRITICAL ALERT以上のログレベルの場合にログに出力される
ALERT ErrorLevelActivationStrategy で指定しているため
EMERGENCY ErrorLevelActivationStrategy で指定しているため

サンプルプログラムを実行すると、以下のようにログファイルに出力されます。

$ php ./src/monolog_sample_3.php

$ cat logs/foo_test_3-2014-05-11.log 
[2014-05-11 12:29:05] foo_test.INFO: info_bar [] []
[2014-05-11 12:29:05] foo_test.NOTICE: notice_bar [] []
[2014-05-11 12:29:05] foo_test.WARNING: warning_bar [] []
[2014-05-11 12:29:05] foo_test.ERROR: error_bar [] []
[2014-05-11 12:29:05] foo_test.CRITICAL: critical_bar [] []
[2014-05-11 12:29:05] foo_test.ALERT: alert_bar [] []

もちろん、このログ情報をメール等で送信することができます。

最後に

Monologでロギング処理を実装してみました。元々はSymfony2でMonologを使おうと思っていたのですが、設定ファイルを見てもよく分からなかったので自分で調べて実装してみようということで、このようなサンプルプログラムを作成してみました。 個人的にやはり FingersCrossedHandler がかなり便利そうでオススメです。まだまだ多くのことができそうなので色々調べて分かったらまた記事にしたいと思います。

また、今回のサンプルプログラムもGitHubにありますのでgit cloneしてcomposer installして試してみてください。 https://github.com/ohsawa0515/blog_sample_source_code

参考URL

*1:composer.jsonにswiftmailerを追加するのもお忘れなく