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