PSR-3 Logger Interface
PHP-FIGによるロギングライブラリの共通インターフェース標準。8つのログレベル(emergency、alert、critical、error、warning、notice、info、debug)を定義。ライブラリ間の相互運用性とコード再利用性を促進する抽象化層。
ライブラリ
PSR-3 Logger Interface
概要
PSR-3 Logger InterfaceはPHP-FIG(PHP Framework Interop Group)により策定されたロギングライブラリの共通インターフェース標準です。8つのログレベル(emergency、alert、critical、error、warning、notice、info、debug)を定義し、異なるロギング実装間の相互運用性とコード再利用性を促進する抽象化層として機能。2025年現在、PHPエコシステムで必須の標準として完全に定着し、フレームワークとライブラリの統一的な基盤として機能しています。
詳細
PSR-3は「ライブラリが Psr\Log\LoggerInterface オブジェクトを受け取り、シンプルで汎用的な方法でログを記録できる」という目標のもと設計されました。RFC 5424に準拠した8つのログレベルメソッドと、任意のレベルを受け取るlogメソッドを提供します。メッセージプレースホルダー機能により、コンテキスト配列の値を使用した動的なログメッセージ生成が可能。実装固有のロギングライブラリに依存しないコードの作成を可能にし、デプロイ時に適切な実装を選択できる柔軟性を提供します。
主な特徴
- 標準インターフェース: RFC 5424準拠の8レベルロギング
- メッセージプレースホルダー: 動的コンテキスト挿入機能
- 実装非依存: 任意のPSR-3準拠ライブラリとの互換性
- 依存性注入対応: クリーンアーキテクチャサポート
- NullLoggerパターン: テスト・開発時の安全なフォールバック
- LoggerAwareTrait: 簡単なロガー統合
メリット・デメリット
メリット
- PHPエコシステム全体での圧倒的な普及と統一性確保
- フレームワーク・ライブラリ間の完全な相互運用性実現
- 実装ライブラリの自由な選択と切り替えが可能
- 依存性注入パターンによるテスタブルなコード設計
- MonologやSymfony Loggerなど豊富な実装選択肢
- 外部モジュールとの統合が極めて容易
デメリット
- インターフェースのみで実際のロギング機能は別途必要
- パフォーマンス最適化は実装ライブラリに依存
- 高度な機能(構造化ログ等)はライブラリ固有機能に限定
- インターフェース抽象化によるわずかなオーバーヘッド
- 学習コストは使用する実装ライブラリの複雑さに依存
- 型安全性はPHP言語機能の制約を受ける
参考ページ
書き方の例
インターフェース実装とセットアップ
<?php
// PSR-3パッケージのインストール
// composer require psr/log
use Psr\Log\LoggerInterface;
use Psr\Log\AbstractLogger;
use Psr\Log\LogLevel;
// 基本的なカスタムロガー実装
class SimpleLogger extends AbstractLogger
{
public function log($level, $message, array $context = [])
{
$timestamp = date('Y-m-d H:i:s');
$interpolated = $this->interpolate($message, $context);
echo sprintf(
"[%s] %s: %s\n",
$timestamp,
strtoupper($level),
$interpolated
);
}
private function interpolate($message, array $context = [])
{
$replace = [];
foreach ($context as $key => $val) {
if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) {
$replace['{'.$key.'}'] = $val;
}
}
return strtr($message, $replace);
}
}
// LoggerAwareTraitの使用
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;
class UserService
{
use LoggerAwareTrait;
public function __construct()
{
// NullLoggerをデフォルトとして設定
$this->logger = new NullLogger();
}
public function createUser(array $userData)
{
$this->logger->info('ユーザー作成開始', ['data' => $userData]);
try {
// ユーザー作成処理
$userId = $this->doCreateUser($userData);
$this->logger->info('ユーザー作成完了', ['user_id' => $userId]);
return $userId;
} catch (Exception $e) {
$this->logger->error('ユーザー作成失敗', [
'exception' => $e,
'user_data' => $userData
]);
throw $e;
}
}
}
ログレベルとコンテキスト使用
<?php
// 8つのログレベルの使用例
class ApplicationLogger
{
private LoggerInterface $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function demonstrateLogLevels()
{
// Emergency: システム使用不可
$this->logger->emergency('データベース接続完全停止', [
'error_code' => 'DB_FATAL',
'timestamp' => time()
]);
// Alert: 即座の対応が必要
$this->logger->alert('ディスク容量が臨界値に達成', [
'disk_usage' => '98%',
'available_space' => '100MB'
]);
// Critical: 深刻な状況
$this->logger->critical('決済処理でクリティカルエラー', [
'payment_id' => 'pay_123456',
'amount' => 15000,
'currency' => 'JPY'
]);
// Error: 実行時エラー(即座の対応不要)
$this->logger->error('ファイルアップロード失敗', [
'file_name' => 'document.pdf',
'file_size' => 2048000,
'error' => 'Permission denied'
]);
// Warning: 例外的だが必ずしもエラーでない事象
$this->logger->warning('API レスポンス時間が閾値超過', [
'endpoint' => '/api/users',
'response_time' => 5200,
'threshold' => 3000
]);
// Notice: 通常だが重要な事象
$this->logger->notice('新規ユーザー登録完了', [
'user_id' => 12345,
'registration_method' => 'email',
'user_ip' => '192.168.1.100'
]);
// Info: 情報メッセージ
$this->logger->info('ユーザー {username} がログインしました', [
'username' => 'tanaka_taro',
'login_time' => date('c'),
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown'
]);
// Debug: デバッグ情報
$this->logger->debug('SQL クエリ実行', [
'query' => 'SELECT * FROM users WHERE active = ?',
'params' => [true],
'execution_time' => 0.045
]);
}
// 汎用logメソッドの使用
public function logWithDynamicLevel(string $level, string $message, array $context = [])
{
$this->logger->log($level, $message, $context);
}
}
フレームワーク統合パターン
<?php
// Symfonyでの使用例
use Symfony\Component\DependencyInjection\ContainerInterface;
use Psr\Log\LoggerInterface;
class SymfonyIntegrationExample
{
private LoggerInterface $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function processOrder(array $orderData)
{
$this->logger->info('注文処理開始', ['order' => $orderData]);
// 注文処理ロジック
return $this->handleOrderProcessing($orderData);
}
}
// services.yaml での設定
/*
services:
App\Service\OrderService:
arguments:
$logger: '@monolog.logger.order'
*/
// LaravelでのPSR-3使用例
use Illuminate\Support\Facades\Log;
use Psr\Log\LoggerInterface;
class LaravelIntegrationExample
{
private LoggerInterface $logger;
public function __construct()
{
// LaravelのLogファサードもPSR-3準拠
$this->logger = Log::channel('application');
}
public function handlePayment(float $amount, string $currency)
{
$this->logger->info('決済処理開始', [
'amount' => $amount,
'currency' => $currency,
'timestamp' => now()->toISOString()
]);
try {
$result = $this->processPayment($amount, $currency);
$this->logger->info('決済処理成功', ['result' => $result]);
return $result;
} catch (PaymentException $e) {
$this->logger->error('決済処理失敗', [
'exception' => $e->getMessage(),
'amount' => $amount,
'currency' => $currency
]);
throw $e;
}
}
}
依存性注入とテストパターン
<?php
use Psr\Log\LoggerInterface;
use Psr\Log\Test\TestLogger;
use PHPUnit\Framework\TestCase;
// プロダクションクラス
class EmailService
{
private LoggerInterface $logger;
private MailerInterface $mailer;
public function __construct(LoggerInterface $logger, MailerInterface $mailer)
{
$this->logger = $logger;
$this->mailer = $mailer;
}
public function sendEmail(string $to, string $subject, string $body): bool
{
$this->logger->info('メール送信開始', [
'to' => $to,
'subject' => $subject
]);
try {
$result = $this->mailer->send($to, $subject, $body);
if ($result) {
$this->logger->info('メール送信成功', ['to' => $to]);
} else {
$this->logger->warning('メール送信失敗', ['to' => $to]);
}
return $result;
} catch (Exception $e) {
$this->logger->error('メール送信エラー', [
'to' => $to,
'exception' => $e->getMessage()
]);
return false;
}
}
}
// テストクラス
class EmailServiceTest extends TestCase
{
public function testSuccessfulEmailSending()
{
// TestLoggerを使用してログ出力をテスト
$logger = new TestLogger();
$mailer = $this->createMock(MailerInterface::class);
$mailer->method('send')->willReturn(true);
$emailService = new EmailService($logger, $mailer);
$result = $emailService->sendEmail(
'[email protected]',
'テストメール',
'これはテストです'
);
$this->assertTrue($result);
// ログ出力の検証
$this->assertTrue($logger->hasInfo('メール送信開始'));
$this->assertTrue($logger->hasInfo('メール送信成功'));
// ログコンテキストの検証
$records = $logger->records;
$this->assertEquals('[email protected]', $records[0]['context']['to']);
$this->assertEquals('テストメール', $records[0]['context']['subject']);
}
public function testEmailSendingWithException()
{
$logger = new TestLogger();
$mailer = $this->createMock(MailerInterface::class);
$mailer->method('send')->willThrowException(new Exception('SMTP エラー'));
$emailService = new EmailService($logger, $mailer);
$result = $emailService->sendEmail('[email protected]', 'テスト', 'テスト');
$this->assertFalse($result);
$this->assertTrue($logger->hasError('メール送信エラー'));
}
}
Monolog実装との組み合わせ
<?php
// Monologを使ったPSR-3実装例
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Processor\IntrospectionProcessor;
use Monolog\Formatter\JsonFormatter;
class MonologIntegrationExample
{
public static function createApplicationLogger(): LoggerInterface
{
$logger = new Logger('application');
// コンソール出力ハンドラー
$consoleHandler = new StreamHandler('php://stdout', Logger::DEBUG);
$logger->pushHandler($consoleHandler);
// ローテーションファイルハンドラー
$fileHandler = new RotatingFileHandler(
'/var/log/application.log',
30, // 30日間保持
Logger::INFO
);
$fileHandler->setFormatter(new JsonFormatter());
$logger->pushHandler($fileHandler);
// プロセッサー追加(ファイル名・行番号情報)
$logger->pushProcessor(new IntrospectionProcessor());
return $logger;
}
public static function createTestLogger(): LoggerInterface
{
// テスト環境用のシンプルなロガー
$logger = new Logger('test');
$handler = new StreamHandler('/tmp/test.log', Logger::DEBUG);
$logger->pushHandler($handler);
return $logger;
}
}
// 使用例
$logger = MonologIntegrationExample::createApplicationLogger();
$userService = new UserService();
$userService->setLogger($logger);
// PSR-3インターフェースを通じて使用
$logger->info('アプリケーション起動', [
'version' => '1.0.0',
'environment' => 'production',
'memory_usage' => memory_get_usage(true)
]);
高度なコンテキスト処理
<?php
// 高度なコンテキスト処理とプレースホルダー
class AdvancedLoggingExample
{
private LoggerInterface $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function processComplexOperation(array $data)
{
$operationId = uniqid('op_');
$startTime = microtime(true);
// 開始ログ(構造化コンテキスト)
$this->logger->info('複雑な処理を開始: {operation_id}', [
'operation_id' => $operationId,
'input_data_count' => count($data),
'start_time' => $startTime,
'memory_before' => memory_get_usage(),
'process_id' => getmypid()
]);
try {
foreach ($data as $index => $item) {
$this->processItem($item, $operationId, $index);
}
$endTime = microtime(true);
$duration = $endTime - $startTime;
// 成功ログ(パフォーマンス情報含む)
$this->logger->info('処理完了: {operation_id}', [
'operation_id' => $operationId,
'duration_seconds' => round($duration, 3),
'items_processed' => count($data),
'memory_peak' => memory_get_peak_usage(),
'memory_after' => memory_get_usage()
]);
} catch (Exception $e) {
$this->logger->error('処理中にエラーが発生: {operation_id}', [
'operation_id' => $operationId,
'exception_class' => get_class($e),
'exception_message' => $e->getMessage(),
'exception_file' => $e->getFile(),
'exception_line' => $e->getLine(),
'stack_trace' => $e->getTraceAsString()
]);
throw $e;
}
}
private function processItem($item, string $operationId, int $index)
{
$this->logger->debug('アイテム処理: {operation_id}[{index}]', [
'operation_id' => $operationId,
'index' => $index,
'item_type' => gettype($item),
'item_size' => is_string($item) ? strlen($item) : null
]);
// アイテム処理ロジック
if (is_array($item) && !empty($item['error'])) {
$this->logger->warning('問題のあるアイテム: {operation_id}[{index}]', [
'operation_id' => $operationId,
'index' => $index,
'error_details' => $item['error']
]);
}
}
}