【保存版】PHPエンジニアのためのモノログ完全ガイド2024:実践的な設定から運用まで

Monologとは:PHPで最も使われているログライブラリの実力

Composerで管理される信頼性の高いログシステム

Monologは、PHPアプリケーションにおいて最も広く使用されている、信頼性の高いログ管理ライブラリです。GitHubで49,000以上のスターを獲得し、Laravel、Symfonyなどの主要なPHPフレームワークでも標準のログライブラリとして採用されています。

Composerを通じて簡単にインストールでき、以下のコマンドで導入が可能です:

composer require monolog/monolog

基本的な使用例は以下の通りです:

<?php
// Monologの基本的な使用例
use Monolog\Level;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

// ロガーインスタンスの作成
$logger = new Logger('my_app');

// ログの出力先を設定
$logger->pushHandler(new StreamHandler(__DIR__.'/logs/app.log', Level::Debug));

// 様々なレベルでログを出力
$logger->info('アプリケーションが起動しました');
$logger->warning('メモリ使用率が80%を超えています');
$logger->error('データベース接続エラーが発生しました');

PSR-3に準拠した標準的なインターフェイス

MonologはPSR-3(PHP標準勧告-3)に準拠しており、これには以下のような重要な意義があります:

  1. 互換性の保証
  • 他のPSR-3準拠のログライブラリとの互換性
  • フレームワークやライブラリ間での一貫した使用が可能
  1. 標準化されたログレベル
  • DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY
  • 一般的なログ基準に従った8段階のレベル分け

以下は、PSR-3インターフェースを活用した実装例です:

<?php
use Psr\Log\LoggerInterface;

class UserService
{
    private LoggerInterface $logger;

    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    public function createUser(array $userData): bool
    {
        try {
            // ユーザー作成処理
            $this->logger->info('新規ユーザーを作成しました', ['user' => $userData['email']]);
            return true;
        } catch (\Exception $e) {
            $this->logger->error('ユーザー作成に失敗しました', [
                'error' => $e->getMessage(),
                'user' => $userData['email']
            ]);
            return false;
        }
    }
}

Monologの特筆すべき機能:

機能説明
柔軟なハンドラーシステムファイル、メール、Slack、データベースなど多様な出力先に対応
フォーマッターJSON、HTML、ライン形式など、様々な出力形式をサポート
プロセッサーログメッセージの加工や付加情報の追加が可能
バッファリングメモリ効率とパフォーマンスを考慮した設計

これらの機能により、Monologは開発環境から本番環境まで、あらゆる場面で活用できる強力なログ管理ソリューションとなっています。後続のセクションでは、これらの機能の具体的な活用方法について詳しく解説していきます。

Monologを導入する具体的な手順

Composerを使った簡単なインストール方法

Monologの導入は、以下の手順で進めていきます:

  1. Composerのインストール確認
# Composerがインストールされているか確認
composer --version

# プロジェクトディレクトリの作成と移動
mkdir my-logging-project
cd my-logging-project

# Composerの初期化(まだcomposer.jsonがない場合)
composer init --require="monolog/monolog:^3.0"
  1. Monologのインストール
# 既存プロジェクトへの追加の場合
composer require monolog/monolog

# 特定のバージョンを指定する場合
composer require monolog/monolog:^3.0
  1. ディレクトリ構造の準備
# ログ保存用ディレクトリの作成
mkdir logs
chmod 777 logs  # 書き込み権限の付与(本番環境ではより適切な権限設定を推奨)

基本的な設定ファイルの作成と解説

基本的な設定ファイルの例を示します:

<?php
// config/logging.php

return [
    'default' => [
        'type' => 'single',
        'path' => __DIR__.'/../logs/app.log',
        'level' => 'debug',
        'days' => 14,
    ],
    'error' => [
        'type' => 'daily',
        'path' => __DIR__.'/../logs/error.log',
        'level' => 'error',
        'days' => 30,
    ],
    'slack' => [
        'type' => 'slack',
        'url' => 'https://hooks.slack.com/services/YOUR/WEBHOOK/URL',
        'channel' => '#errors',
        'username' => 'MonologBot',
        'level' => 'critical',
    ],
];

実際の実装例:

<?php
// src/Logger/LoggerFactory.php

namespace App\Logger;

use Monolog\Level;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Handler\SlackWebhookHandler;
use Monolog\Formatter\LineFormatter;

class LoggerFactory
{
    private array $config;

    public function __construct(array $config)
    {
        $this->config = $config;
    }

    public function createLogger(string $name): Logger
    {
        $logger = new Logger($name);

        // デフォルトのログハンドラーを追加
        if (isset($this->config['default'])) {
            $handler = new StreamHandler(
                $this->config['default']['path'],
                $this->getLogLevel($this->config['default']['level'])
            );
            $handler->setFormatter($this->createFormatter());
            $logger->pushHandler($handler);
        }

        // エラーログハンドラーを追加
        if (isset($this->config['error'])) {
            $handler = new RotatingFileHandler(
                $this->config['error']['path'],
                $this->config['error']['days'],
                $this->getLogLevel($this->config['error']['level'])
            );
            $handler->setFormatter($this->createFormatter());
            $logger->pushHandler($handler);
        }

        // Slackハンドラーを追加
        if (isset($this->config['slack'])) {
            $handler = new SlackWebhookHandler(
                $this->config['slack']['url'],
                $this->config['slack']['channel'],
                $this->config['slack']['username'],
                true,
                null,
                false,
                true,
                $this->getLogLevel($this->config['slack']['level'])
            );
            $logger->pushHandler($handler);
        }

        return $logger;
    }

    private function createFormatter(): LineFormatter
    {
        // タイムスタンプ、ログレベル、メッセージ、コンテキストを含む標準的なフォーマット
        return new LineFormatter(
            "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n",
            "Y-m-d H:i:s"
        );
    }

    private function getLogLevel(string $level): Level
    {
        return match (strtolower($level)) {
            'debug' => Level::Debug,
            'info' => Level::Info,
            'notice' => Level::Notice,
            'warning' => Level::Warning,
            'error' => Level::Error,
            'critical' => Level::Critical,
            'alert' => Level::Alert,
            'emergency' => Level::Emergency,
            default => Level::Debug,
        };
    }
}

使用例:

<?php
// index.php

require 'vendor/autoload.php';

$config = require 'config/logging.php';
$loggerFactory = new App\Logger\LoggerFactory($config);
$logger = $loggerFactory->createLogger('app');

// ログの出力
$logger->info('アプリケーションが起動しました');
$logger->error('データベース接続エラー', ['db_host' => 'localhost']);

try {
    // 何らかの処理
    throw new \Exception('テストエラー');
} catch (\Exception $e) {
    $logger->critical('重大なエラーが発生しました', [
        'error' => $e->getMessage(),
        'file' => $e->getFile(),
        'line' => $e->getLine()
    ]);
}

よくあるトラブルと解決方法:

トラブル原因解決方法
ログファイルに書き込めないパーミッションの問題chmodでログディレクトリに適切な権限を付与
メモリ使用量が増大バッファリング設定の問題BufferHandlerを使用して適切にバッファリング
タイムゾーンが異なるPHPのデフォルト設定date.timezoneをphp.iniで設定
文字化けが発生文字エンコーディングの不一致LineFormatterで適切なエンコーディングを指定

以上の設定と実装により、堅牢なログ管理システムの基盤が整います。次のセクションでは、これらの基本設定を活用した実践的なログレベルの使い分けについて解説していきます。

実践的なログレベルの使い分け

8段階のログレベルとその使用シーン

Monologは、PSR-3に準拠した8段階のログレベルを提供しています。各レベルの適切な使用方法を具体例と共に解説します:

<?php
use Monolog\Level;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

$logger = new Logger('app');
$logger->pushHandler(new StreamHandler(__DIR__.'/logs/app.log', Level::Debug));
  1. DEBUG (Level::Debug)
   // 開発時のデバッグ情報
   $logger->debug('SQLクエリの実行時間', [
       'query' => 'SELECT * FROM users',
       'execution_time' => 0.023
   ]);
  • 変数の値の確認
  • パフォーマンス測定結果
  • 開発環境での詳細なトレース情報
  1. INFO (Level::Info)
   // 正常系の処理記録
   $logger->info('新規ユーザー登録完了', [
       'user_id' => $user->id,
       'email' => $user->email
   ]);
  • ユーザーのアクション記録
  • バッチ処理の開始/完了
  • 重要な処理のマイルストーン
  1. NOTICE (Level::Notice)
   // 通常とは異なる注目すべき事象
   $logger->notice('API呼び出しの遅延を検知', [
       'api' => 'payment_service',
       'response_time' => 2.5
   ]);
  • パフォーマンス低下の兆候
  • 設定値の自動変更
  • キャッシュの自動更新
  1. WARNING (Level::Warning)
   // 潜在的な問題の警告
   $logger->warning('セッションの有効期限切れ', [
       'session_id' => $sessionId,
       'user_ip' => $userIp
   ]);
  • 非推奨機能の使用
  • リソース使用量の閾値超過
  • 再試行を伴う処理
  1. ERROR (Level::Error)
   // アプリケーションエラー
   $logger->error('データベース接続エラー', [
       'exception' => $e->getMessage(),
       'trace' => $e->getTraceAsString()
   ]);
  • 例外の発生
  • API通信の失敗
  • バリデーションエラー
  1. CRITICAL (Level::Critical)
   // システムの重大な問題
   $logger->critical('ストレージ容量が危機的状況', [
       'free_space' => $freeSpace,
       'required_space' => $requiredSpace
   ]);
  • サービス停止の可能性がある問題
  • 深刻なセキュリティ問題
  • クリティカルなリソース不足
  1. ALERT (Level::Alert)
   // 即時対応が必要な問題
   $logger->alert('複数のマスターデータ更新が失敗', [
       'failed_tables' => $failedTables,
       'impact' => 'サービス全体に影響の可能性あり'
   ]);
  • サービスの主要機能の停止
  • データ整合性の重大な破損
  • セキュリティインシデント
  1. EMERGENCY (Level::Emergency)
   // システム停止レベルの致命的問題
   $logger->emergency('システム全体がダウン', [
       'last_error' => $lastError,
       'system_status' => $systemStatus
   ]);
  • システム全体の停止
  • 重大なデータ損失
  • 致命的なセキュリティ侵害

プロジェクトに応じたログレベルのカスタマイズ

プロジェクトの特性に応じて、以下のようにログレベルをカスタマイズできます:

<?php
// カスタムログレベルの定義
class CustomLogger extends Logger
{
    // プロジェクト固有のログレベル定数
    public const AUDIT = 250;
    public const BUSINESS = 300;

    public function audit(string $message, array $context = []): void
    {
        $this->addRecord(static::AUDIT, $message, $context);
    }

    public function business(string $message, array $context = []): void
    {
        $this->addRecord(static::BUSINESS, $message, $context);
    }
}

// 使用例
$logger = new CustomLogger('app');
$logger->audit('ユーザーが機密データにアクセス', [
    'user_id' => $userId,
    'accessed_data' => $dataId
]);

効果的なログレベル設定のベストプラクティス:

環境推奨最小ログレベル理由
開発Debug詳細なデバッグ情報が必要
テストInfo機能テストの検証に必要な情報
ステージングNotice本番に近い環境での異常検知
本番Warningパフォーマンスとストレージの最適化

また、特定の状況に応じて複数のハンドラーを使い分けることで、より柔軟なログ管理が可能になります:

<?php
// 環境に応じたログハンドラーの設定
$logger = new Logger('app');

// 全てのログをファイルに出力
$logger->pushHandler(new StreamHandler(
    __DIR__.'/logs/all.log',
    Level::Debug
));

// エラー以上を別ファイルに出力
$logger->pushHandler(new StreamHandler(
    __DIR__.'/logs/error.log',
    Level::Error
));

// クリティカル以上をSlackに通知
$logger->pushHandler(new SlackWebhookHandler(
    'webhook_url',
    '#alerts',
    'MonologBot',
    true,
    null,
    false,
    true,
    Level::Critical
));

適切なログレベルの使い分けにより、システムの監視と問題解決が効率化され、運用の質が向上します。次のセクションでは、これらのログレベルを活用した具体的なハンドラーの設定について解説していきます。

ログハンドラーの賢い選択と設定

目的別おすすめハンドラーの比較

Monologの強みの一つは、多様なハンドラーを提供していることです。主要なハンドラーの特徴と使用例を解説します:

  1. StreamHandler
<?php
use Monolog\Handler\StreamHandler;
use Monolog\Level;

// 基本的なファイルログ
$handler = new StreamHandler(
    __DIR__.'/logs/app.log',
    Level::Debug,
    true,  // バッファリングの有効化
    0666   // ファイルパーミッション
);
  • 用途:一般的なファイルログ
  • 特徴:シンプルで確実な記録
  • 注意点:ローテーションなし
  1. RotatingFileHandler
<?php
use Monolog\Handler\RotatingFileHandler;

// 日別のログローテーション
$handler = new RotatingFileHandler(
    __DIR__.'/logs/app.log',
    30,    // 保持する日数
    Level::Info,
    true,  // ファイルパーミッション
    0666
);
  • 用途:長期運用での記録
  • 特徴:自動的なログローテーション
  • 注意点:ディスク容量の管理が必要
  1. ElasticsearchHandler
<?php
use Monolog\Handler\ElasticsearchHandler;
use Elasticsearch\Client;

// Elasticsearchへの転送
$client = \Elasticsearch\ClientBuilder::create()
    ->setHosts(['localhost:9200'])
    ->build();

$handler = new ElasticsearchHandler(
    $client,
    [
        'index' => 'monolog',
        'type' => '_doc',
        'ignore_error' => false
    ]
);
  • 用途:ログの分析と可視化
  • 特徴:高度な検索と分析が可能
  • 注意点:リソース消費に注意
  1. SlackWebhookHandler
<?php
use Monolog\Handler\SlackWebhookHandler;

// Slackへの通知
$handler = new SlackWebhookHandler(
    'https://hooks.slack.com/services/YOUR/WEBHOOK/URL',
    '#alerts',
    'MonologBot',
    true,   // メンション付き
    null,   // エモジ
    false,  // 詳細表示
    true,   // HTMLフォーマット
    Level::Error
);
  • 用途:即時通知が必要なエラー
  • 特徴:チーム全体での監視が容易
  • 注意点:通知の頻度制御が重要

複数ハンドラーの連携による柔軟な運用

実践的なログ管理では、複数のハンドラーを組み合わせることで、より効果的な運用が可能になります:

<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Handler\SlackWebhookHandler;
use Monolog\Handler\ElasticsearchHandler;
use Monolog\Formatter\JsonFormatter;
use Monolog\Processor\IntrospectionProcessor;
use Monolog\Processor\WebProcessor;

class LoggerFactory
{
    public static function createProductionLogger(): Logger
    {
        $logger = new Logger('production');

        // 1. 全ログをJSONフォーマットで記録
        $fileHandler = new RotatingFileHandler(
            __DIR__.'/logs/app.log',
            30,
            Level::Debug
        );
        $fileHandler->setFormatter(new JsonFormatter());

        // 2. エラーログを別ファイルに記録
        $errorHandler = new StreamHandler(
            __DIR__.'/logs/error.log',
            Level::Error
        );

        // 3. クリティカルエラーをSlackに通知
        $slackHandler = new SlackWebhookHandler(
            'webhook_url',
            '#alerts',
            'MonologBot',
            true,
            null,
            false,
            true,
            Level::Critical
        );

        // 4. 全ログをElasticsearchに送信
        $elasticsearchHandler = new ElasticsearchHandler(
            \Elasticsearch\ClientBuilder::create()
                ->setHosts(['localhost:9200'])
                ->build(),
            [
                'index' => 'logs-'.date('Y.m.d'),
                'type' => '_doc'
            ]
        );

        // プロセッサーの追加
        $logger->pushProcessor(new IntrospectionProcessor());
        $logger->pushProcessor(new WebProcessor());

        // ハンドラーの追加(優先順位順)
        $logger->pushHandler($slackHandler);      // 最も重要な通知
        $logger->pushHandler($errorHandler);      // エラーログ
        $logger->pushHandler($elasticsearchHandler); // 分析用
        $logger->pushHandler($fileHandler);       // 全ログ保存

        return $logger;
    }
}

効果的なハンドラー設定のベストプラクティス:

目的推奨ハンドラー設定のポイント
デバッグStreamHandlerバッファリング有効化
監視SlackWebhookHandlerレート制限の設定
分析ElasticsearchHandlerインデックス設計
アーカイブRotatingFileHandler保持期間の最適化

高度なログ管理の実装例:

<?php
// カスタムフォーマッターの作成
class CustomJsonFormatter extends JsonFormatter
{
    public function format(array $record): string
    {
        // タイムスタンプのフォーマット調整
        $record['datetime'] = $record['datetime']->format('Y-m-d H:i:s.u');

        // 環境情報の追加
        $record['environment'] = getenv('APP_ENV');
        $record['server'] = gethostname();

        return parent::format($record);
    }
}

// カスタムプロセッサーの作成
class RequestContextProcessor
{
    public function __invoke(array $record): array
    {
        $record['extra']['url'] = $_SERVER['REQUEST_URI'] ?? null;
        $record['extra']['method'] = $_SERVER['REQUEST_METHOD'] ?? null;
        $record['extra']['client_ip'] = $_SERVER['REMOTE_ADDR'] ?? null;
        $record['extra']['user_agent'] = $_SERVER['HTTP_USER_AGENT'] ?? null;

        return $record;
    }
}

// 実装例
$logger = new Logger('app');

// カスタムフォーマッターとプロセッサーの適用
$handler = new StreamHandler(__DIR__.'/logs/app.log');
$handler->setFormatter(new CustomJsonFormatter());
$logger->pushHandler($handler);
$logger->pushProcessor(new RequestContextProcessor());

// 使用例
try {
    // 何らかの処理
    throw new \Exception('テストエラー');
} catch (\Exception $e) {
    $logger->error('エラーが発生しました', [
        'exception' => [
            'message' => $e->getMessage(),
            'file' => $e->getFile(),
            'line' => $e->getLine(),
            'trace' => $e->getTraceAsString()
        ]
    ]);
}

これらのハンドラー設定により、効率的なログ管理と問題の早期発見が可能になります。次のセクションでは、これらのハンドラーを活用した効果的なエラー監視について解説していきます。

Monologによる効果的なエラー監視

エラーハンドリングの実装パターン

PHPアプリケーションでの効果的なエラー監視を実現するため、Monologを使用した実装パターンを紹介します。

  1. グローバルエラーハンドラーの設定
<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\ErrorHandler;

// エラーログ専用のロガーを作成
$errorLogger = new Logger('error');
$errorLogger->pushHandler(new StreamHandler(
    __DIR__.'/logs/error.log',
    Level::Debug
));

// エラーハンドラーを登録
ErrorHandler::register($errorLogger);

// 例外ハンドラーの設定
set_exception_handler(function (\Throwable $e) use ($errorLogger) {
    $errorLogger->error('未捕捉の例外が発生しました', [
        'exception' => get_class($e),
        'message' => $e->getMessage(),
        'file' => $e->getFile(),
        'line' => $e->getLine(),
        'trace' => $e->getTraceAsString()
    ]);
});
  1. カスタムエラーハンドラーの実装
<?php
class CustomErrorHandler
{
    private Logger $logger;
    private array $errorTypesMap = [
        E_ERROR => 'ERROR',
        E_WARNING => 'WARNING',
        E_PARSE => 'PARSE',
        E_NOTICE => 'NOTICE',
        E_CORE_ERROR => 'CORE_ERROR',
        E_CORE_WARNING => 'CORE_WARNING',
        E_COMPILE_ERROR => 'COMPILE_ERROR',
        E_COMPILE_WARNING => 'COMPILE_WARNING',
        E_USER_ERROR => 'USER_ERROR',
        E_USER_WARNING => 'USER_WARNING',
        E_USER_NOTICE => 'USER_NOTICE',
        E_STRICT => 'STRICT',
        E_RECOVERABLE_ERROR => 'RECOVERABLE_ERROR',
        E_DEPRECATED => 'DEPRECATED',
        E_USER_DEPRECATED => 'USER_DEPRECATED'
    ];

    public function __construct(Logger $logger)
    {
        $this->logger = $logger;
    }

    public function handle($level, $message, $file = '', $line = 0, $context = [])
    {
        $errorType = $this->errorTypesMap[$level] ?? 'UNKNOWN';

        $this->logger->error('PHPエラーが発生しました', [
            'type' => $errorType,
            'message' => $message,
            'file' => $file,
            'line' => $line,
            'context' => $context
        ]);
    }
}

Slackとメールとの連携による即時通知

  1. Slack通知の実装
<?php
use Monolog\Handler\SlackWebhookHandler;
use Monolog\Formatter\LineFormatter;

class ErrorNotifier
{
    private Logger $logger;

    public function __construct()
    {
        $this->logger = new Logger('error_notifier');

        // Slack通知の設定
        $slackHandler = new SlackWebhookHandler(
            'webhook_url',
            '#errors',
            'ErrorBot',
            true,
            null,
            false,
            true,
            Level::Error
        );

        // カスタムフォーマットの設定
        $formatter = new LineFormatter(
            "環境: %extra.environment%\n発生時刻: %datetime%\n%level_name%: %message%\n場所: %extra.file%:%extra.line%\n%context%",
            "Y-m-d H:i:s"
        );
        $slackHandler->setFormatter($formatter);

        $this->logger->pushHandler($slackHandler);

        // 環境情報を追加するプロセッサー
        $this->logger->pushProcessor(function ($record) {
            $record['extra']['environment'] = getenv('APP_ENV');
            $record['extra']['file'] = debug_backtrace()[1]['file'] ?? 'unknown';
            $record['extra']['line'] = debug_backtrace()[1]['line'] ?? 0;
            return $record;
        });
    }

    public function notifyError(\Throwable $e, array $context = [])
    {
        $this->logger->error($e->getMessage(), [
            'exception' => [
                'class' => get_class($e),
                'trace' => $e->getTraceAsString()
            ],
            'context' => $context
        ]);
    }
}
  1. メール通知の実装
<?php
use Monolog\Handler\NativeMailerHandler;
use Monolog\Formatter\HtmlFormatter;

// メール通知ハンドラーの設定
$mailHandler = new NativeMailerHandler(
    'alerts@example.com',
    '[ERROR] アプリケーションエラー',
    'monitoring@example.com',
    Level::Critical
);

// HTML形式のメール本文
$mailHandler->setFormatter(new HtmlFormatter());

// ハンドラーの追加
$logger->pushHandler($mailHandler);
  1. 通知の統合例
<?php
class IntegratedErrorMonitor
{
    private Logger $logger;
    private ErrorNotifier $notifier;

    public function __construct()
    {
        $this->logger = new Logger('error_monitor');
        $this->notifier = new ErrorNotifier();

        // ファイルログ
        $this->logger->pushHandler(new StreamHandler(
            __DIR__.'/logs/error.log',
            Level::Error
        ));

        // Slack通知
        $this->logger->pushHandler(new SlackWebhookHandler(
            'webhook_url',
            '#errors',
            'MonitorBot',
            true,
            null,
            false,
            true,
            Level::Error
        ));

        // メール通知(クリティカルエラーのみ)
        $this->logger->pushHandler(new NativeMailerHandler(
            'alerts@example.com',
            '[CRITICAL] システムエラー',
            'monitoring@example.com',
            Level::Critical
        ));
    }

    public function monitorException(\Throwable $e, array $context = [])
    {
        // エラーレベルの判定
        $level = $this->determineErrorLevel($e);

        // コンテキスト情報の収集
        $contextData = array_merge($context, [
            'exception' => [
                'class' => get_class($e),
                'message' => $e->getMessage(),
                'code' => $e->getCode(),
                'file' => $e->getFile(),
                'line' => $e->getLine(),
                'trace' => $e->getTraceAsString()
            ],
            'server' => [
                'hostname' => gethostname(),
                'ip' => $_SERVER['SERVER_ADDR'] ?? 'unknown',
                'time' => date('Y-m-d H:i:s')
            ]
        ]);

        // ログ記録と通知
        $this->logger->log($level, $e->getMessage(), $contextData);

        // クリティカルエラーの場合は追加アクション
        if ($level === Level::Critical) {
            $this->notifier->notifyError($e, $context);
        }
    }

    private function determineErrorLevel(\Throwable $e): Level
    {
        // エラーの種類に応じてログレベルを決定
        return match (true) {
            $e instanceof \Error => Level::Critical,
            $e instanceof \RuntimeException => Level::Error,
            $e instanceof \InvalidArgumentException => Level::Warning,
            default => Level::Error,
        };
    }
}

// 使用例
$monitor = new IntegratedErrorMonitor();

try {
    // アプリケーションコード
    throw new \RuntimeException('データベース接続エラー');
} catch (\Throwable $e) {
    $monitor->monitorException($e, [
        'database' => 'main',
        'query' => 'SELECT * FROM users'
    ]);
}

エラー監視のベストプラクティス:

項目推奨設定理由
ログレベルエラー以上重要な問題の見逃し防止
通知頻度レート制限あり通知疲れの防止
コンテキスト詳細情報を含める迅速な問題解決
バッファリング有効パフォーマンス最適化

この実装により、以下のような効果的なエラー監視が可能になります:

  1. 全てのエラーを確実に記録
  2. 重要なエラーの即時通知
  3. 詳細なコンテキスト情報の収集
  4. 環境に応じた通知レベルの調整

次のセクションでは、これらの監視システムを運用する際のパフォーマンスについて解説していきます。

パフォーマンスを考慮したログ設定

非同期ログ処理による負荷軽減

アプリケーションのパフォーマンスを最適化するため、Monologでの非同期ログ処理を実装する方法を解説します。

  1. BufferHandlerの活用
<?php
use Monolog\Handler\BufferHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Level;

class AsyncLogger
{
    private Logger $logger;

    public function __construct()
    {
        $this->logger = new Logger('async_logger');

        // 基本のStreamHandler
        $streamHandler = new StreamHandler(
            __DIR__.'/logs/app.log',
            Level::Debug
        );

        // BufferHandlerでラップ
        $bufferHandler = new BufferHandler(
            $streamHandler,
            50,    // バッファサイズ
            Level::Debug,
            true,  // バッファがいっぱいになった時に自動的にフラッシュ
            true   // シャットダウン時にフラッシュ
        );

        $this->logger->pushHandler($bufferHandler);
    }

    public function __destruct()
    {
        // 確実にログを書き出す
        foreach ($this->logger->getHandlers() as $handler) {
            if ($handler instanceof BufferHandler) {
                $handler->flush();
            }
        }
    }
}
  1. 非同期キューの実装
<?php
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Level;

class AsyncQueueHandler extends AbstractProcessingHandler
{
    private $queue;
    private $queueFile;

    public function __construct(
        string $queueFile = '/tmp/monolog_queue',
        $level = Level::Debug,
        bool $bubble = true
    ) {
        parent::__construct($level, $bubble);
        $this->queueFile = $queueFile;
        $this->openQueue();
    }

    protected function write(array $record): void
    {
        // レコードをシリアライズしてキューに追加
        $serialized = serialize($record) . PHP_EOL;
        fwrite($this->queue, $serialized);
    }

    private function openQueue(): void
    {
        $this->queue = fopen($this->queueFile, 'a');
    }

    public function close(): void
    {
        if ($this->queue) {
            fclose($this->queue);
        }
    }
}

// 使用例
$logger = new Logger('async');
$logger->pushHandler(new AsyncQueueHandler());

// キュー処理用のスクリプト(別プロセスで実行)
class QueueProcessor
{
    public function processQueue(string $queueFile): void
    {
        $logger = new Logger('processor');
        $logger->pushHandler(new StreamHandler(__DIR__.'/logs/app.log'));

        while (true) {
            $queue = @fopen($queueFile, 'r+');
            if ($queue) {
                while (($line = fgets($queue)) !== false) {
                    $record = unserialize($line);
                    if ($record) {
                        $logger->log(
                            $record['level'],
                            $record['message'],
                            $record['context']
                        );
                    }
                }
                ftruncate($queue, 0);
                fclose($queue);
            }
            sleep(1);
        }
    }
}

ログローテーションの最適な設定方法

  1. RotatingFileHandlerの高度な設定
<?php
use Monolog\Handler\RotatingFileHandler;
use Monolog\Formatter\JsonFormatter;

class OptimizedLogger
{
    private Logger $logger;

    public function __construct()
    {
        $this->logger = new Logger('optimized');

        // 高度なローテーション設定
        $handler = new RotatingFileHandler(
            __DIR__.'/logs/app.log',
            30,        // 保持する日数
            Level::Info,
            true,      // ファイルパーミッション
            0664,
            true       // ファイル名にタイムスタンプを付加
        );

        // JSONフォーマットの使用(パース効率の向上)
        $handler->setFormatter(new JsonFormatter());

        $this->logger->pushHandler($handler);
    }

    // ログファイルのクリーンアップ
    public function cleanupOldLogs(int $daysToKeep = 30): void
    {
        $logDir = __DIR__.'/logs';
        $now = time();

        foreach (glob($logDir.'/*.log') as $file) {
            if (is_file($file)) {
                $fileTime = filemtime($file);
                if (($now - $fileTime) > ($daysToKeep * 86400)) {
                    unlink($file);
                }
            }
        }
    }
}
  1. 圧縮機能の実装
<?php
class LogCompressor
{
    public function compressOldLogs(string $logDir): void
    {
        foreach (glob($logDir.'/*.log') as $file) {
            if (is_file($file) && !$this->isCurrentLog($file)) {
                $gzFile = $file.'.gz';
                $fp = gzopen($gzFile, 'w9');
                gzwrite($fp, file_get_contents($file));
                gzclose($fp);
                unlink($file);
            }
        }
    }

    private function isCurrentLog(string $file): bool
    {
        return basename($file) === 'app.log';
    }
}

パフォーマンス最適化のベストプラクティス:

設定項目推奨値説明
バッファサイズ50-100メモリ使用量とディスクI/Oのバランス
ローテーション期間日次ファイル管理の効率化
保持期間30日ストレージ容量の最適化
圧縮タイミング7日以上経過処理負荷の分散

高負荷環境での最適化実装例:

<?php
class HighPerformanceLogger
{
    private Logger $logger;
    private array $buffer = [];
    private int $bufferSize;

    public function __construct(int $bufferSize = 100)
    {
        $this->logger = new Logger('high_performance');
        $this->bufferSize = $bufferSize;

        // メインのログハンドラー
        $mainHandler = new RotatingFileHandler(
            __DIR__.'/logs/app.log',
            30,
            Level::Info
        );
        $mainHandler->setFormatter(new JsonFormatter());

        // バッファリングの設定
        $bufferedHandler = new BufferHandler(
            $mainHandler,
            $this->bufferSize,
            Level::Debug,
            true,
            true
        );

        // エラーレベルのログは即時書き込み
        $errorHandler = new StreamHandler(
            __DIR__.'/logs/error.log',
            Level::Error,
            false
        );

        $this->logger->pushHandler($bufferedHandler);
        $this->logger->pushHandler($errorHandler);
    }

    public function log($level, string $message, array $context = []): void
    {
        // エラーレベル以上は即時処理
        if ($level >= Level::Error) {
            $this->logger->log($level, $message, $context);
            return;
        }

        // 通常のログはバッファリング
        $this->buffer[] = [
            'level' => $level,
            'message' => $message,
            'context' => $context,
            'time' => microtime(true)
        ];

        // バッファがいっぱいになったら一括処理
        if (count($this->buffer) >= $this->bufferSize) {
            $this->flushBuffer();
        }
    }

    private function flushBuffer(): void
    {
        foreach ($this->buffer as $record) {
            $this->logger->log(
                $record['level'],
                $record['message'],
                $record['context']
            );
        }

        $this->buffer = [];
    }

    public function __destruct()
    {
        // 残っているログを確実に書き込み
        $this->flushBuffer();
    }
}

これらの最適化により、以下のような効果が期待できます:

  1. アプリケーションのレスポンスタイム改善
  2. ディスクI/O操作の削減
  3. リソース使用量の最適化
  4. ログファイルの効率的な管理

次のセクションでは、これらの設定を活用した実際の運用におけるヒントについて解説していきます。

実際の運用で使えるMonologのヒント集

デバッグ時に使えるフォーマッターの活用

デバッグ作業を効率化するための実践的なフォーマッター活用方法を紹介します。

  1. カラー付きCLIフォーマッター
<?php
use Monolog\Formatter\LineFormatter;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

class ColoredDebugFormatter extends LineFormatter
{
    private const COLORS = [
        'error' => "\033[31m",      // 赤
        'warning' => "\033[33m",    // 黄
        'info' => "\033[36m",       // シアン
        'debug' => "\033[37m",      // 白
        'reset' => "\033[0m"        // リセット
    ];

    public function format(array $record): string
    {
        $level = strtolower($record['level_name']);
        $color = self::COLORS[$level] ?? self::COLORS['debug'];

        $output = parent::format($record);
        return sprintf(
            "%s%s%s",
            $color,
            $output,
            self::COLORS['reset']
        );
    }
}

// 使用例
$handler = new StreamHandler('php://stdout', Level::Debug);
$handler->setFormatter(new ColoredDebugFormatter(
    "[%datetime%] %level_name%: %message% %context%\n"
));
  1. デバッグ用JSONフォーマッター
<?php
use Monolog\Formatter\JsonFormatter;

class DebugJsonFormatter extends JsonFormatter
{
    public function format(array $record): string
    {
        // デバッグに有用な情報を追加
        $record['debug_info'] = [
            'memory_usage' => memory_get_usage(true),
            'peak_memory' => memory_get_peak_usage(true),
            'timing' => microtime(true),
            'included_files' => get_included_files(),
        ];

        return parent::format($record);
    }
}

本番環境での安全なログ運用方法

  1. 機密情報のマスキング処理
<?php
class SensitiveDataProcessor
{
    private array $sensitiveKeys = [
        'password',
        'credit_card',
        'token',
        'api_key',
        'secret'
    ];

    public function __invoke(array $record): array
    {
        $record['context'] = $this->maskSensitiveData($record['context']);
        if (isset($record['extra'])) {
            $record['extra'] = $this->maskSensitiveData($record['extra']);
        }

        return $record;
    }

    private function maskSensitiveData($data): array
    {
        if (!is_array($data)) {
            return $data;
        }

        foreach ($data as $key => $value) {
            if (is_array($value)) {
                $data[$key] = $this->maskSensitiveData($value);
            } elseif ($this->isSensitiveKey($key)) {
                $data[$key] = '********';
            }
        }

        return $data;
    }

    private function isSensitiveKey(string $key): bool
    {
        $key = strtolower($key);
        foreach ($this->sensitiveKeys as $sensitiveKey) {
            if (str_contains($key, $sensitiveKey)) {
                return true;
            }
        }
        return false;
    }
}
  1. 環境別ログ設定
<?php
class EnvironmentAwareLogger
{
    private Logger $logger;

    public function __construct()
    {
        $this->logger = new Logger('production');

        $environment = getenv('APP_ENV') ?: 'production';
        $config = $this->getEnvironmentConfig($environment);

        foreach ($config['handlers'] as $handler) {
            $this->logger->pushHandler($handler);
        }

        // 機密情報のマスキング
        $this->logger->pushProcessor(new SensitiveDataProcessor());
    }

    private function getEnvironmentConfig(string $environment): array
    {
        return match($environment) {
            'production' => [
                'handlers' => [
                    new RotatingFileHandler(
                        __DIR__.'/logs/production.log',
                        30,
                        Level::Info
                    ),
                    new SlackWebhookHandler(
                        'webhook_url',
                        '#prod-alerts',
                        'MonologBot',
                        true,
                        null,
                        false,
                        true,
                        Level::Error
                    )
                ]
            ],
            'staging' => [
                'handlers' => [
                    new StreamHandler(
                        __DIR__.'/logs/staging.log',
                        Level::Debug
                    )
                ]
            ],
            default => [
                'handlers' => [
                    new StreamHandler('php://stdout', Level::Debug)
                ]
            ]
        };
    }
}

実践的なログ運用のTips:

カテゴリTip実装例
デバッグスタックトレースの詳細化IntrospectionProcessor の活用
セキュリティIPアドレスの匿名化WebProcessor のカスタマイズ
パフォーマンス条件付きログ記録ClosureHandler の使用
運用管理ログの集中管理Fluentd/ELK との連携

運用時の具体的なベストプラクティス:

  1. ログレベルの使い分け
<?php
class ProductionLogger
{
    public function logUserAction(string $action, array $context = []): void
    {
        match($action) {
            'login_success' => $this->logger->info('ログイン成功', $context),
            'login_failure' => $this->logger->warning('ログイン失敗', $context),
            'password_reset' => $this->logger->notice('パスワードリセット', $context),
            'account_locked' => $this->logger->error('アカウントロック', $context),
            default => $this->logger->debug('ユーザーアクション', [
                'action' => $action,
                'context' => $context
            ])
        };
    }
}
  1. エラートラッキング
<?php
class ErrorTracker
{
    private const ERROR_THRESHOLD = 5;
    private array $errorCount = [];

    public function trackError(string $errorType, \Throwable $e): void
    {
        $key = $this->getErrorKey($errorType, $e);
        $this->errorCount[$key] = ($this->errorCount[$key] ?? 0) + 1;

        if ($this->errorCount[$key] >= self::ERROR_THRESHOLD) {
            $this->logger->critical('エラーの多発を検知', [
                'error_type' => $errorType,
                'error_message' => $e->getMessage(),
                'occurrence_count' => $this->errorCount[$key]
            ]);

            // カウントをリセット
            $this->errorCount[$key] = 0;
        }
    }

    private function getErrorKey(string $type, \Throwable $e): string
    {
        return md5($type . $e->getMessage() . $e->getFile() . $e->getLine());
    }
}
  1. ログの分析と可視化
<?php
class LogAnalyzer
{
    public function analyzeErrorPatterns(string $logFile): array
    {
        $patterns = [];
        $handle = fopen($logFile, 'r');

        while (($line = fgets($handle)) !== false) {
            $log = json_decode($line, true);
            if ($log && isset($log['level_name']) && $log['level_name'] === 'ERROR') {
                $key = $log['message'] ?? 'unknown';
                $patterns[$key] = ($patterns[$key] ?? 0) + 1;
            }
        }

        fclose($handle);
        arsort($patterns);

        return array_slice($patterns, 0, 10); // Top 10 エラーパターン
    }
}

トラブルシューティングのチェックリスト:

  1. ログファイルのパーミッション確認
# ログディレクトリのパーミッション確認
ls -la /path/to/logs

# パーミッションの修正
chmod 755 /path/to/logs
chmod 644 /path/to/logs/*.log
  1. ログローテーションの確認
# ログファイルのサイズと数の確認
du -sh /path/to/logs/*
ls -l /path/to/logs | wc -l

# 古いログの圧縮
find /path/to/logs -name "*.log" -mtime +7 -exec gzip {} \;
  1. メモリ使用量の監視
<?php
$logger->pushProcessor(function ($record) {
    $record['extra']['memory_usage'] = memory_get_usage(true);
    $record['extra']['memory_peak'] = memory_get_peak_usage(true);
    return $record;
});

これらのヒントを活用することで、Monologをより効果的に運用し、システムの安定性と可観測性を向上させることができます。