Monolog

PHP向けのデファクトスタンダードロギングライブラリ。Laravel、Symfony、Lumenでデフォルト採用。PSR-3インターフェースを実装し、複数のハンドラー(ファイル、データベース、メール、Slack等)とプロセッサー、フォーマッターを提供。

ロギングライブラリPHPPSR-3LaravelSymfony階層化ログ

ライブラリ

Monolog

概要

MonologはPHP向けの標準的ロギングライブラリです。PSR-3ログ仕様に準拠し、階層的ログレベル、多様なハンドラー、プロセッサーによるメタデータ追加をサポートします。LaravelやSymfonyなど主要PHPフレームワークで標準採用されており、豊富なハンドラーによりファイル、データベース、メール、Slackなど様々な出力先に対応。構造化ログ、コンテキスト情報、カスタムフォーマッターによる現代的なログ管理を実現します。

詳細

Monolog 3.xは2025年現在、PHPエコシステムの定番ロギングソリューションとして不動の地位を維持しています。PHP 8.0以上を要求する最新版では、型安全性の向上、パフォーマンス改善、現代的なPHP機能の活用により、エンタープライズグレードのロギング機能を提供。PSR-3準拠により他のライブラリとの互換性が高く、豊富なハンドラー(File、Database、Mail、Slack、Elasticsearch等)とプロセッサー(IP、User、Memory等)により、小規模アプリケーションから大規模エンタープライズシステムまで対応します。

主な特徴

  • PSR-3準拠: PHP標準ロギング仕様への完全準拠による高い互換性
  • 豊富なハンドラー: ファイル、データベース、メール、Slack、クラウドサービス等20以上の出力先
  • 階層的ロガー: チャネル別の詳細なログ管理とレベル制御
  • プロセッサー対応: IP、ユーザー、メモリ使用量等のメタデータ自動追加
  • フレームワーク統合: Laravel、Symfonyでの標準採用とゼロ設定での利用
  • 構造化ログ: JSON形式とコンテキスト情報による現代的ログ形式対応

メリット・デメリット

メリット

  • PSR-3準拠により他のPHPライブラリとの高い互換性を実現
  • Laravel、Symfonyでの標準採用により即座に利用開始可能
  • 20以上の豊富なハンドラーにより多様な出力先に対応
  • プロセッサーによる自動メタデータ追加で詳細なログ情報を取得
  • チャネル機能による用途別ロガーの分離管理が可能
  • クラウドサービス(AWS CloudWatch、Google Cloud Logging等)との統合
  • JSON形式対応による現代的なログ分析プラットフォームとの親和性

デメリット

  • 軽量なアプリケーションには機能がオーバースペックとなる可能性
  • 多数のハンドラー使用時にパフォーマンスオーバーヘッドが発生
  • 高度な設定では配列ベースの設定が複雑になる場合がある
  • 非同期ロギングサポートが限定的(他ライブラリと比較)
  • PHP専用のため他言語プロジェクトでは一貫性が保てない
  • カスタムハンドラー作成時の学習コストがやや高い

参考ページ

書き方の例

インストールと基本セットアップ

# Composerでのインストール
composer require monolog/monolog

# 特定バージョンの指定(PHP 8.0以上推奨)
composer require monolog/monolog:^3.0
<?php
// Composerオートローダーの読み込み
require_once 'vendor/autoload.php';

// 最もシンプルな使用例
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

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

// ハンドラーの追加(ファイル出力)
$logger->pushHandler(new StreamHandler('app.log', Logger::DEBUG));

// 基本的なログ出力
$logger->debug('Debug message');
$logger->info('Info message');
$logger->notice('Notice message');
$logger->warning('Warning message');
$logger->error('Error message');
$logger->critical('Critical message');
$logger->alert('Alert message');
$logger->emergency('Emergency message');

基本的なロギング操作(レベル、フォーマット)

<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Formatter\LineFormatter;

// 複数ハンドラーを持つロガーの設定
$logger = new Logger('myapp');

// ローテーティングファイルハンドラー(日次ローテーション)
$fileHandler = new RotatingFileHandler('logs/app.log', 30, Logger::DEBUG);

// カスタムフォーマッターの設定
$formatter = new LineFormatter(
    "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n",
    'Y-m-d H:i:s'
);
$fileHandler->setFormatter($formatter);
$logger->pushHandler($fileHandler);

// エラー専用ハンドラー
$errorHandler = new StreamHandler('logs/error.log', Logger::ERROR);
$logger->pushHandler($errorHandler);

// コンテキスト情報付きログ
$logger->info('User action', [
    'user_id' => 12345,
    'action' => 'login',
    'ip_address' => '192.168.1.100',
    'user_agent' => 'Mozilla/5.0...'
]);

// プレースホルダーを使用したメッセージ
$logger->warning('Failed login attempt for user {username} from {ip}', [
    'username' => 'john_doe',
    'ip' => '192.168.1.200'
]);

// 例外ログ
try {
    $result = 10 / 0;
} catch (Exception $e) {
    $logger->error('Division by zero error', [
        'exception' => $e,
        'trace' => $e->getTraceAsString()
    ]);
}

// 配列・オブジェクトデータのログ
$userData = [
    'id' => 123,
    'name' => 'John Doe',
    'email' => '[email protected]'
];

$logger->info('User data processed', ['user_data' => $userData]);

// 条件付きログ(パフォーマンス考慮)
if ($logger->isHandling(Logger::DEBUG)) {
    $expensiveData = generateExpensiveDebugData();
    $logger->debug('Expensive debug information', ['data' => $expensiveData]);
}

高度な設定とカスタマイズ(ハンドラー、プロセッサー等)

<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\MailHandler;
use Monolog\Handler\SlackWebhookHandler;
use Monolog\Handler\DatabaseHandler;
use Monolog\Processor\IntrospectionProcessor;
use Monolog\Processor\WebProcessor;
use Monolog\Processor\MemoryUsageProcessor;
use Monolog\Formatter\JsonFormatter;

// 高度な設定を持つロガー
$logger = new Logger('production');

// JSON形式のファイルハンドラー
$jsonHandler = new StreamHandler('logs/app.json', Logger::INFO);
$jsonHandler->setFormatter(new JsonFormatter());
$logger->pushHandler($jsonHandler);

// メールハンドラー(重要なエラー通知用)
$mailHandler = new MailHandler(
    '[email protected]',
    'Critical Error Alert',
    '[email protected]',
    Logger::CRITICAL
);
$logger->pushHandler($mailHandler);

// Slackハンドラー(チーム通知用)
$slackHandler = new SlackWebhookHandler(
    'https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK',
    '#alerts',
    'MonologBot',
    true,
    null,
    Logger::ERROR
);
$logger->pushHandler($slackHandler);

// データベースハンドラー
$pdo = new PDO('mysql:host=localhost;dbname=logs', 'user', 'password');
$dbHandler = new DatabaseHandler($pdo, 'logs', [], Logger::WARNING);
$logger->pushHandler($dbHandler);

// プロセッサーの追加
$logger->pushProcessor(new IntrospectionProcessor()); // ファイル・行番号情報
$logger->pushProcessor(new WebProcessor()); // HTTP情報
$logger->pushProcessor(new MemoryUsageProcessor()); // メモリ使用量

// カスタムプロセッサーの追加
$logger->pushProcessor(function ($record) {
    $record['extra']['application_version'] = '1.2.3';
    $record['extra']['environment'] = $_ENV['APP_ENV'] ?? 'production';
    $record['extra']['server_name'] = gethostname();
    return $record;
});

// 使用例
$logger->error('Database connection failed', [
    'database' => 'main',
    'host' => 'localhost',
    'timeout' => 30
]);

構造化ログと現代的な可観測性対応

<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\ElasticaHandler;
use Monolog\Formatter\ElasticaFormatter;
use Monolog\Formatter\JsonFormatter;
use Elasticsearch\ClientBuilder;

// Elasticsearch対応ロガー
$client = ClientBuilder::create()
    ->setHosts(['localhost:9200'])
    ->build();

$logger = new Logger('observability');

// Elasticsearchハンドラー
$elasticHandler = new ElasticaHandler($client, [
    'index' => 'application-logs',
    'type' => '_doc'
], Logger::INFO);
$elasticHandler->setFormatter(new ElasticaFormatter('application', 'logs'));
$logger->pushHandler($elasticHandler);

// 構造化ログファイル
$structuredHandler = new StreamHandler('logs/structured.json', Logger::DEBUG);
$structuredHandler->setFormatter(new JsonFormatter());
$logger->pushHandler($structuredHandler);

// 分散トレーシング対応
function logWithTracing($logger, $traceId, $spanId, $message, $context = []) {
    $context['trace_id'] = $traceId;
    $context['span_id'] = $spanId;
    $context['service_name'] = 'user-service';
    $context['service_version'] = '1.0.0';
    
    $logger->info($message, $context);
}

// ビジネスメトリクスのログ
function logBusinessMetric($logger, $metricName, $value, $labels = []) {
    $logger->info('business_metric', [
        'metric_name' => $metricName,
        'metric_value' => $value,
        'metric_type' => 'counter',
        'labels' => $labels,
        'timestamp' => time()
    ]);
}

// 使用例
$traceId = uniqid('trace_');
$spanId = uniqid('span_');

logWithTracing($logger, $traceId, $spanId, 'User order processing started', [
    'user_id' => 12345,
    'order_id' => 'order_67890',
    'amount' => 99.99,
    'currency' => 'USD'
]);

// セキュリティイベントのログ
$logger->warning('security_event', [
    'event_type' => 'suspicious_login',
    'user_id' => 12345,
    'ip_address' => '192.168.1.100',
    'user_agent' => 'curl/7.68.0',
    'severity' => 'medium',
    'detection_method' => 'rate_limiting'
]);

// パフォーマンスメトリクス
$startTime = microtime(true);
// ... 処理実行 ...
$endTime = microtime(true);

$logger->info('performance_metric', [
    'operation' => 'database_query',
    'duration_ms' => round(($endTime - $startTime) * 1000, 2),
    'query_type' => 'SELECT',
    'table' => 'users',
    'result_count' => 150
]);

// ビジネスメトリクスの記録
logBusinessMetric($logger, 'orders_completed', 1, [
    'payment_method' => 'credit_card',
    'customer_tier' => 'premium'
]);

エラーハンドリングとパフォーマンス最適化

<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\BufferHandler;
use Monolog\Handler\FingersCrossedHandler;
use Monolog\Handler\GroupHandler;
use Monolog\Handler\FilterHandler;

// パフォーマンス最適化されたロガー設定
$logger = new Logger('optimized');

// バッファハンドラー(バッチ処理)
$fileHandler = new StreamHandler('logs/app.log', Logger::DEBUG);
$bufferHandler = new BufferHandler($fileHandler, 100, Logger::DEBUG, true, true);
$logger->pushHandler($bufferHandler);

// FingersCrossedハンドラー(エラー時のみ詳細ログ)
$debugHandler = new StreamHandler('logs/debug.log', Logger::DEBUG);
$fingersCrossedHandler = new FingersCrossedHandler(
    $debugHandler,
    Logger::ERROR,
    100,
    true,
    true,
    Logger::INFO
);
$logger->pushHandler($fingersCrossedHandler);

// エラーレベル別ハンドラー
$errorHandler = new FilterHandler(
    new StreamHandler('logs/error.log', Logger::ERROR),
    Logger::ERROR,
    Logger::EMERGENCY
);
$logger->pushHandler($errorHandler);

// 高性能なロギング関数
function performanceAwareLog($logger, $level, $message, $context = []) {
    // ログレベルチェックによる無駄な処理回避
    if (!$logger->isHandling($level)) {
        return;
    }
    
    // 重い処理は必要な場合のみ実行
    if (isset($context['include_stack_trace']) && $context['include_stack_trace']) {
        $context['stack_trace'] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
    }
    
    $logger->log($level, $message, $context);
}

// リトライ機能付きログ処理
function logWithRetry($logger, $level, $message, $context = [], $maxRetries = 3) {
    $attempt = 0;
    
    while ($attempt < $maxRetries) {
        try {
            $logger->log($level, $message, $context);
            return true;
        } catch (Exception $e) {
            $attempt++;
            
            if ($attempt >= $maxRetries) {
                // 最終手段:システムログまたはエラーログファイルに記録
                error_log("Monolog failed after {$maxRetries} attempts: " . $e->getMessage());
                return false;
            }
            
            // 指数バックオフで待機
            usleep(pow(2, $attempt) * 100000); // 0.1秒, 0.2秒, 0.4秒...
        }
    }
    
    return false;
}

// バルク処理用ログ関数
function logBulkOperations($logger, $operations) {
    $startTime = microtime(true);
    $successCount = 0;
    $errorCount = 0;
    $errors = [];
    
    foreach ($operations as $operation) {
        try {
            processOperation($operation);
            $successCount++;
        } catch (Exception $e) {
            $errorCount++;
            $errors[] = [
                'operation' => $operation,
                'error' => $e->getMessage()
            ];
            
            $logger->error('Bulk operation failed', [
                'operation_id' => $operation['id'] ?? 'unknown',
                'error' => $e->getMessage(),
                'exception' => $e
            ]);
        }
    }
    
    $duration = microtime(true) - $startTime;
    
    $logger->info('Bulk processing completed', [
        'total_operations' => count($operations),
        'successful' => $successCount,
        'failed' => $errorCount,
        'success_rate' => $successCount / count($operations),
        'duration_seconds' => round($duration, 3),
        'errors' => $errors
    ]);
}

// 使用例
performanceAwareLog($logger, Logger::DEBUG, 'Debug information', [
    'user_id' => 123,
    'include_stack_trace' => true
]);

logWithRetry($logger, Logger::CRITICAL, 'Critical system error', [
    'system' => 'payment',
    'error_code' => 'PAYMENT_001'
]);

フレームワーク統合と実用例

<?php
// Laravel統合例
// config/logging.php での設定
return [
    'default' => env('LOG_CHANNEL', 'stack'),
    
    'channels' => [
        'stack' => [
            'driver' => 'stack',
            'channels' => ['daily', 'slack'],
        ],
        
        'daily' => [
            'driver' => 'daily',
            'path' => storage_path('logs/laravel.log'),
            'level' => 'debug',
            'days' => 14,
        ],
        
        'slack' => [
            'driver' => 'slack',
            'url' => env('LOG_SLACK_WEBHOOK_URL'),
            'username' => 'Laravel Log',
            'emoji' => ':boom:',
            'level' => 'error',
        ],
        
        'custom' => [
            'driver' => 'custom',
            'via' => App\Logging\CustomLoggerFactory::class,
        ],
    ],
];

// Laravelでの使用例
use Illuminate\Support\Facades\Log;

class UserController extends Controller
{
    public function store(Request $request)
    {
        Log::info('User creation started', [
            'request_data' => $request->all(),
            'ip_address' => $request->ip(),
            'user_agent' => $request->userAgent()
        ]);
        
        try {
            $user = User::create($request->validated());
            
            Log::info('User created successfully', [
                'user_id' => $user->id,
                'email' => $user->email
            ]);
            
            return response()->json($user, 201);
            
        } catch (Exception $e) {
            Log::error('User creation failed', [
                'error' => $e->getMessage(),
                'request_data' => $request->all(),
                'exception' => $e
            ]);
            
            return response()->json(['error' => 'User creation failed'], 500);
        }
    }
}
<?php
// Symfony統合例
// config/packages/monolog.yaml
monolog:
    channels: ['app', 'security', 'performance']
    handlers:
        main:
            type: stream
            path: '%kernel.logs_dir%/%kernel.environment%.log'
            level: debug
            channels: ['!event']
            
        security:
            type: stream
            path: '%kernel.logs_dir%/security.log'
            level: info
            channels: [security]
            
        performance:
            type: stream
            path: '%kernel.logs_dir%/performance.log'
            level: info
            channels: [performance]
            formatter: monolog.formatter.json

// Symfonyでの使用例
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class ApiController extends AbstractController
{
    private LoggerInterface $logger;
    private LoggerInterface $securityLogger;
    
    public function __construct(
        LoggerInterface $logger,
        LoggerInterface $securityLogger
    ) {
        $this->logger = $logger;
        $this->securityLogger = $securityLogger;
    }
    
    public function getUserData(Request $request, string $userId): Response
    {
        $this->logger->info('User data request', [
            'user_id' => $userId,
            'request_ip' => $request->getClientIp(),
            'request_time' => time()
        ]);
        
        // セキュリティログ
        $this->securityLogger->info('API access', [
            'endpoint' => '/api/user/' . $userId,
            'ip_address' => $request->getClientIp(),
            'user_agent' => $request->headers->get('User-Agent'),
            'authorization' => $request->headers->has('Authorization')
        ]);
        
        try {
            $userData = $this->userService->getUserData($userId);
            
            $this->logger->info('User data retrieved successfully', [
                'user_id' => $userId,
                'data_size' => strlen(json_encode($userData))
            ]);
            
            return $this->json($userData);
            
        } catch (UserNotFoundException $e) {
            $this->logger->warning('User not found', [
                'user_id' => $userId,
                'requested_by_ip' => $request->getClientIp()
            ]);
            
            return $this->json(['error' => 'User not found'], 404);
            
        } catch (Exception $e) {
            $this->logger->error('Unexpected error in user data retrieval', [
                'user_id' => $userId,
                'error' => $e->getMessage(),
                'exception' => $e
            ]);
            
            return $this->json(['error' => 'Internal server error'], 500);
        }
    }
}
<?php
// カスタムサービスクラスでの利用例
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\RedisHandler;
use Predis\Client as RedisClient;

class PaymentService
{
    private Logger $logger;
    
    public function __construct()
    {
        $this->logger = new Logger('payment');
        
        // 複数ハンドラーの設定
        $this->logger->pushHandler(new StreamHandler('logs/payment.log', Logger::INFO));
        
        // Redis用ハンドラー(リアルタイム監視用)
        $redis = new RedisClient(['host' => 'localhost', 'port' => 6379]);
        $this->logger->pushHandler(new RedisHandler($redis, 'payment_logs'));
    }
    
    public function processPayment(array $paymentData): array
    {
        $transactionId = uniqid('txn_');
        
        $this->logger->info('Payment processing started', [
            'transaction_id' => $transactionId,
            'amount' => $paymentData['amount'],
            'currency' => $paymentData['currency'],
            'payment_method' => $paymentData['method'],
            'user_id' => $paymentData['user_id']
        ]);
        
        try {
            // 支払い処理
            $result = $this->executePayment($paymentData);
            
            $this->logger->info('Payment completed successfully', [
                'transaction_id' => $transactionId,
                'payment_gateway_response' => $result,
                'processing_time_ms' => $result['processing_time']
            ]);
            
            return $result;
            
        } catch (PaymentException $e) {
            $this->logger->error('Payment failed', [
                'transaction_id' => $transactionId,
                'error_code' => $e->getCode(),
                'error_message' => $e->getMessage(),
                'payment_data' => $paymentData,
                'exception' => $e
            ]);
            
            throw $e;
        }
    }
    
    private function executePayment(array $data): array
    {
        // 実際の支払い処理ロジック
        return ['status' => 'success', 'processing_time' => 150];
    }
}