monologはログを取得するためのPHPライブラリで、LaravelやSymphonyなど有名な PHPフレームワークを含めさまざまな場所で広く利用されています。

ログはアプリケーションの開発時だけではなく、アプリケーションを安定運用していくために非常に重要な仕組みです。ログの取得にmonologを使用する場合に設定不備がないように設定方法を理解しておく必要があります。

本文書ではmonologを単独で使用することでPHPにおけるログ設定の理解を深めていきます。単独でのmonologの設定を理解することができればフレームワークに組み込まれたmonologの設定にも非常に役に立ちます。

monologのインストール

monologはパッケージ管理ツールのcomposerを使ってインストールを行います。

MAC環境であればcomposerのインストールは下記が参考になります。

適当なディレクトリを作成し、composerを使用してmonologのインストールを行います。


$ composer require monolog/monolog

インストール完了後、composer.jsonを確認するとインストールしたmonologのバージョンを確認することができます。


{
    "require": {
        "monolog/monolog": "^1.24"
    }
}

ログファイルへの書き出し(サンプルコード利用)

インストールが完了したので、monologのGitHubにあるサンプルのコードを利用して動作確認を行ってみます。

monologをインストールしたディレクトリの中にtest.phpという名前のファイルを作成し以下を記述します。

先頭には必ずrequire ‘vendor/autoload.php’を入れてください。また__DIR__はカレントディレクトリを表します。

<?php

require 'vendor/autoload.php';

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

// create a log channel
$log = new Logger('name');

$log->pushHandler(new StreamHandler(__DIR__.'/your.log', Logger::WARNING));

// add records to the log
$log->warning('Foo');
$log->error('Bar');

test.phpを実行するとtest.phpが保存されているディレクトリ内にyour.logファイルが自動で作成されます。


 $ php test.php

中身を確認すると下記の2行のログが記述されています。


[2019-08-10 08:54:19] name.WARNING: Foo [] []
[2019-08-10 08:54:19] name.ERROR: Bar [] []

えっ、これだけでログを作成できるのと思われている方もいるかと思います。この手軽さがさまざまなフレームワークで利用されている理由の一つかもしれません。

サンプルコードの解説

上記で実行したサンプルコードの解説を行っていきます。

チャネル名の設定


$log = new Logger('name');

newでLoggerクラスをインスタンス化していますが、インスタンス化する際に識別用の名前を引数に入れています。ここではnameにしていますが、任意の名前を指定することができます。monologではインスタンスを識別するための名前をチャネル名と呼び、作成するインスタンスをチャネルと呼びます。名前つけることで複数のチャネルを作成した場合に識別することができます。

LoggerクラスはgetNameメソッドを持っており、インスタンス化を行った時に付与したチャネル名を取得することができます。


$log = new Logger('channel-name');

var_dump($log->getName());

実行するとchannel-nameが取得できます。

ハンドラーの設定

サンプルではログファイルへの書き込みを行いました。monologはファイルへの書き込みだけではなくメールやSlackでログ内容を送信することもできます。そのため処理の方法によって異なるプログラムが必要になります。

各処理を行うプログラムをハンドラーと呼び、処理によって利用するハンドラーを分けています。ログファイルに書き出す場合はログファイル書き込み用のハンドラー、Slackに送信する場合はSlack送信用のハンドラーが準備されています。

単一のファイルにログを書き込むため時は、StreamHandlerハンドラーを利用します。

pushHandlerメソッドに使用するハンドラーを入力するとチャネルにハンドラーが設定されます。


$log->pushHandler(new StreamHandler(__DIR__.'/your.log', Logger::WARNING));

StreamHandlerでは2つの引数を取っていますが、最初の引数はログを書き込むファイルのパスを指定しています。次のLogger:WARNINGでは、WARINIG以上のログレベルの場合にログファイルへの書き込みを行う設定をしています。ログレベルについてこの後に説明を行っています。

ここまでのコードでログをファイルに書き込むための設定は完了です。

ログレベルについて

ログを書き込む前にログレベルの説明を行っておきます。

ログに書き込むメッセージにはレベルを設定することができ、以下の8つのレベルがあることがvendor/monolog/monolog/src/Monolog/Logger.phpファイルからも確認することができます。


protected static $levels = array(
    self::DEBUG     => 'DEBUG',
    self::INFO      => 'INFO',
    self::NOTICE    => 'NOTICE',
    self::WARNING   => 'WARNING',
    self::ERROR     => 'ERROR',
    self::CRITICAL  => 'CRITICAL',
    self::ALERT     => 'ALERT',
    self::EMERGENCY => 'EMERGENCY',
);

上のDEBUGが最も重要度が低く下のEMERGENCYの重要度が最も高いログのレベルです。上から下にいくにつれ重要度が高くなっていきます。

アプリケーションの開発時にはどのようなメッセージでも取得したいのでDEBUGレベルの設定を行っても問題がありません。しかし本番環境でDEBUGレベルやINFOレベルのログをSlackやメールで送信する処理で設定を行うとあまりのログの多さに重要なメッセージを見逃したり、一日中重要でないメッセージを受信し続けるということにもなります。

そのため、レベルはメッセージの重要さを決めるだけではなくハンドラーによってどのレベル以上のものを書き込んだり送信したりするのかを決めることにも利用されます。

例えば、下記のStreamHanderでLogger::WARNINGを設定することでWARNING以上のメッセージのみログへの書き込みが行われます。


new StreamHandler(__DIR__.'/your.log', Logger::WARNING)

ログへの書き込み

各ログのレベルに応じたメソッドが準備されているので、メソッドを選択することで書き込む際のログレベルを設定することができます。

WARNINGレベルで”Foo”の文字列を書き込み、ERRORレベルで”Bar”の文字列の書き込みを行っています。


$log->warning('Foo');
$log->error('Bar');

書き出しのログレベルがWARNINGに設定されているので、どちらのメッセージもログファイルに書き込まれます。


new StreamHandler(__DIR__.'/your.log', Logger::WARNING)

下記のようにWARNINGレベルよりも低いログレベルで書き込むとログには書き込みが行われません。


$log->info('Foo');
$log->notice('Bar');

ハンドラーの設定

ここまでは単一のログファイルに書き込みを行うStreamHanderハンドラーのみ動作確認を行ってきました。その他のハンドラーについても動作確認を行っていきます。

ログローテーションハンドラー

StreamHandlerでは単一のログファイルにログのメッセージを追記していきます。日毎にログを分けたい場合は、 RotatingFileHandlerハンドラーを利用します。

サンプルコードにRotatingFileHandlerハンドラーを追加します。monologでは1つのLoggerインスタンスに複数のハンドラーを持たせることができるため、pushHandlerでRotatingFileHandlerハンドラーを追加します。


require 'vendor/autoload.php';

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

$log = new Logger('name');

$log->pushHandler(new StreamHandler(__DIR__.'/your.log', Logger::WARNING));
$log->pushHandler(new RotatingFileHandler(__DIR__.'/your.log'));

$log->warning('Foo');
$log->error('Bar');

RotatingFileHandlerハンドラーの引数にファイルパスを追加すると自動で日付が付与されたファイルが作成されます。

2つのハンドラーを設定しているので、実行するとyour.logとyour-2019-08-14.logが作成されログメッセージが書き込まれます。

RotatingFileHandlerハンドラーは作成するファイル数を$maxfiles変数で設定することができます。デフォルトでは0に設定されているので、日付のログファイルが毎日作成し続けられます。 RotatingFileHandlerハンドラー の第2引数に数字を入力すると$maxfilesを設定することができます。下記では5を設定しているので、日付のログファイルが設定した数を超えるとその数以上のログファイルは自動で削除されます。


$log->pushHandler(new RotatingFileHandler(__DIR__.'/your.log',5));

SwiftMailerを利用してログ送信

monologには、メール送信用のハンドラーとしてNativeMailerHandlerとSwiftMailerHandlerハンドラーが準備されています。今回はSwiftMailerHandlerハンドラーを利用してメールでログ送信を行います。

SwiftMailerを利用するためにはSMTPサーバの情報が必要になります。本文書ではメール送信、受信環境用にMailtrapを利用しています。Mailtrapの設定情報を各自の環境に合わせて変更を行う必要があります。

MailTrapはダミーのSMTPサーバを設定することが行えるサービスで、開発時にテストメールの送受信に使用することができます。

SwiftMailerを利用するためには、composerでSwiftMailerをインストールする必要があります。


 % composer require swiftmailer/swiftmailer

インストールが完了するとcomposer.jsonファイルにSwiftMailerが追加されていることが確認できます。


{
    "require": {
        "monolog/monolog": "^1.24",
        "swiftmailer/swiftmailer": "^6.2"
    }
}

SwiftMailerHandlerの設定

monologではSwitfMailer用のハンドラーが準備されているので、SwiftMailが使用できる環境では簡単に設定を行うことができます。

サンプルコードにSwiftMailerHandlerハンドラーを追加します。SMTPサーバなどの設定が必要になります。


require 'vendor/autoload.php';

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

$log = new Logger('name');

$log->pushHandler(new StreamHandler(__DIR__.'/your.log', Logger::WARNING));

// Create the Transport
$transport = (new Swift_SmtpTransport('smtp.mailtrap.io', 2525))
  ->setUsername('XXXXXXXXXXXXX')
  ->setPassword('XXXXXXXXXXXXX');

// Create the Mailer using your created Transport
$mailer = new Swift_Mailer($transport);

// Create a message
$message = (new Swift_Message('This is Swift Mailer Test'))
  ->setFrom(['john@doe.com' => 'John Doe'])
  ->setTo(['receiver@domain.org', 'other@domain.org' => 'A name']);

$log->pushHandler(new SwiftMailerHandler($mailer, $message));

$log->warning('Foo');
$log->error('Bar');

各行の説明を行っていきます。

下記の行では、SMTPサーバの設定を行っています。各環境に合わせて設定値を変更してください。smpt.mailtrap.ioがSMTPサーバで2525はSMTPサーバが利用するポートを指定してください。setUsernameとsetPasswordはSMTPサーバに接続するユーザとパスワードを指定してください。


$transport = (new Swift_SmtpTransport('smtp.mailtrap.io', 2525))
  ->setUsername('XXXXXXXXXXXXX')
  ->setPassword('XXXXXXXXXXXXX');

作成した$transportを使用してSwift_Mailerインスタンスを作成しています。


$mailer = new Swift_Mailer($transport);

Swift_Messageの引数にはメールのタイトルを指定し、setFromには送信元のメールアドレス、setToには送信先のメールアドレスを指定します。


$message = (new Swift_Message('This is Swift Mailer Test'))
  ->setFrom(['john@doe.com' => 'John Doe'])
  ->setTo(['receiver@domain.org', 'other@domain.org' => 'A name']);
setBodyメソッドでメールの内容を設定することはできますが、ログメッセージで上書きされるため設定を行っていません。

SwiftMailerHanderの引数に$mailerと$meassageを入力し、pushHandlerメソッドに渡します。


$log->pushHandler(new SwiftMailerHandler($mailer, $message));

実行するとsetToに設定されたメールアドレス宛にログメールが送信されます。

mailtrapで受信したメール
mailtrapで受信したメール

SwiftMailerHandlerではデフォルトのログレベルがErrorに設定されているので、Error以上のログのみメールで送信されます。

SwiftMailerHandler の第3引数には、ログレベルを設定するので、DEBUGレベルを設定するとすべてのレベルのログがメールで送信されます。


$log->pushHandler(new SwiftMailerHandler($mailer, $message, Logger::DEBUG));

ログフォーマットのカスタマイズ

ログに書き込むメッセージのフォーマットを変更することができます。デフォルトでは下記のフォーマットが設定されています。


[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n

実際に出力されるメッセージは下記になります。


[2019-08-14 03:36:40] name.ERROR: Bar [] []

ログのメッセージは下記の項目から構成されているため、項目を並び替えたり、項目間の文字列を変更することでフォーマットを変えることができます。

  • %datetime%:日付
  • %channel%:チャネル名
  • %level_name%:レベル
  • %message%:メッセージ内容
  • %context%:コンテキスト情報
  • %extra%:追加情報
contextとextraについては後程説明を行います。

ログフォーマットの変更

ログのフォーマットを下記のように変更します。


"%datetime% > %level_name% > %message% %context% %extra%\n";

Monolog\Formatter\LineFormatterを利用してログフォーマットの設定を行います。


require 'vendor/autoload.php';

use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Formatter\LineFormatter;

$log = new Logger('name');

$format = "%datetime% > %level_name% > %message% %context% %extra%\n";

$formatter = new LineFormatter($format);

$stream = new StreamHandler(__DIR__.'/your.log', Logger::WARNING);

$stream->setFormatter($formatter);

$log->pushHandler($stream);

$log->warning('Foo');
$log->error('Bar');

実行すると変更したログフォーマットでメッセージが書き込まれます。


2019-08-14 08:11:39 > ERROR > Bar [] []

日付のフォーマットの変更

デフォルトの日付のフォーマットは、Y-m-d H:i:sで設定が行われているため日付は下記のように表示されます。


2019-08-14 08:11:39

Y-m-d H:i:sをY年n月d日 H:i:sに変更を行います。


$date_format = "Y年n月d日 H:i:s";

$format = "%datetime% > %level_name% > %message% %context% %extra%\n";

$formatter = new LineFormatter($format,$date_format);

$stream = new StreamHandler(__DIR__.'/your.log', Logger::WARNING);

$stream->setFormatter($formatter);

変更後実行すると下記のようにメッセージが書き込まれます。


2019年8月14日 08:23:16 > ERROR > Bar [] []

ログのメッセージに情報追加

monologではログメッセージ以外に追加の情報を追加することができます。

情報を追加する方法は2つ準備されているので、それぞれの設定方法を確認していきます。

Contextを利用した情報の追加

ログメソッドの引数に配列を指定することでログメッセージに情報を追加することができます。追加した情報は、ログフォーマットのコンテキスト情報%context%に対応します。


$log->error('Adding a new user', array('username' => 'Seldaek'));

実行後、ログメッセージを確認すると追加した情報が書き込まれていることを確認できます。


[2019-08-14 09:19:35] name.ERROR: Adding a new user {"username":"Seldaek"} []

プロセッサーを利用した情報の追加

Processor(プロセッサー)を利用して、ログメッセージに追加情報を付加することができます。 追加した情報は、ログフォーマットの追加情報 %extra% に対応します。

プロセッサーを使用する場合は、pushProcessorメソッドを使います。


$log->pushProcessor(function ($record) {
    $record['extra']['dummy'] = 'Hello world!';

    return $record;
});

pushProcessorメソッドの引数に$record変数を取るコールバック関数を利用して$record変数に情報を追加して追加した$recordを戻します。dummyは任意の名前なので変更することが可能です。

$recordはログメッセージの1行に対応します。$recordに情報を追加することですべてのメッセージ行に情報を追加します。

実行するとすべてのメッセージにHello world!が追加されます。


[2019-08-14 09:50:36] name.ERROR: Bar [] {"dummy":"Hello world!"}

monologに事前に準備されているProcessor

monologにはプロセッサーが事前にいくつか準備されています。例えばメモリの使用量を確認したい場合は、MemoryUsageProcessorを利用することができます。


require 'vendor/autoload.php';

use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Processor\MemoryUsageProcessor;

$log = new Logger('name');

$stream = new StreamHandler(__DIR__.'/your.log', Logger::WARNING);

$log->pushHandler($stream);

$log->pushProcessor(new MemoryUsageProcessor);

$log->error('Bar');

実行するとメモリ情報が各メッセージに追加されていることがわかります。


[2019-08-14 09:54:06] name.ERROR: Bar [] {"memory_usage":"2 MB"}

MemoryUsageProcessorがメモリ情報を出力することがわかりましたが、せっかくなのでその中身も確認してみましょう。難しいのかなと思いましたが、先ほど手動で追加した無名関数と処理の内容はほとんど変わらないことがわかります。


class MemoryUsageProcessor extends MemoryProcessor
{
    public function __invoke(array $record): array
    {
        $usage = memory_get_usage($this->;realUsage);

        if ($this->useFormatting) {
            $usage = $this->formatBytes($usage);
        }

        $record['extra']['memory_usage'] = $usage;

        return $record;
    }
}

※memory_get_usageはPHPの関数でformBytesはMemoryProcessorが持つメソッドでバイト単位へのフォーマットを行っています。

MemoryUsageProcessorだけではなく他にもさまざまなProcessorが準備されています。下記で利用するWebProcessorもその一つです。

1つのLoggerインスタンスに複数のプロセッサーを設定することも可能です。WebProcessorを利用するとリクエストしたURLやアクセスしたきたIPアドレスの情報をログに書き込むことができます。


require 'vendor/autoload.php';

use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Processor\MemoryUsageProcessor;
use Monolog\Processor\WebProcessor;

$log = new Logger('name');

$stream = new StreamHandler(__DIR__.'/your.log', Logger::WARNING);

$log->pushHandler($stream);

$log->pushProcessor(new MemoryUsageProcessor);
$log->pushProcessor(new WebProcessor);

$log->error('Bar');

WebProcessorを使用して情報をログに残す場合は、ブラウザから作成したファイルにアクセスする必要があります。ブラウザからアクセスできるようにphpのビルトインサーバーを利用します。


>php -S localhost:8080

ブラウザ経由で作成したtest.phpにアクセスすると下記のメッセージが書き込まれます。


[2019-08-14 10:04:13] name.ERROR: Bar [] {"url":"/test.php","ip":"::1","http_method":"GET","server":"localhost","referrer":null,"memory_usage":"2 MB"}

ここまでの読み進めた後であればmonolog自体はそれほど難しいものではないことがわかったもらえたのではないでしょうか。