KLogger

シンプルで軽量なPSR-3準拠ロギングライブラリ。Monologより軽量で基本的なファイルロギング機能を提供。設定が簡単で、小規模プロジェクトや軽量なログ要件に適している。依存関係を最小限に抑えたい場面で選択される。

ロギングライブラリPHPPSR-3軽量シンプルファイルログ

ライブラリ

KLogger

概要

KLoggerは、シンプルで軽量なPSR-3準拠のPHPロギングライブラリとして、「プロジェクトに素早く組み込んで即座に使用開始できる」ことを重視した設計により、小規模プロジェクトや軽量なログ要件に最適化されています。Monologよりもオーバーヘッドを抑えた基本的なファイルロギング機能を提供し、複雑な設定プロセスなしで迅速なログ実装を実現。依存関係を最小限に抑えたい場面や学習目的でのPSR-3ロギング導入において価値の高い選択肢です。

詳細

KLoggerは2025年でもPHP小規模アプリケーションでの軽量ロギングソリューションとして継続採用されています。PSR-3完全準拠により標準ロギングインターフェースとの互換性を保証し、8つの標準ログレベル(emergency、alert、critical、error、warning、notice、info、debug)をサポート。シンプルなファイルベースログ記録を中心とした機能により、設定の複雑さを排除し即座の利用開始を可能にします。JSON形式ログ出力、ログレベルしきい値設定、カスタムフォーマット機能など、基本的ながら実用的な機能セットを提供しています。

主な特徴

  • PSR-3完全準拠: 標準ロギングインターフェースによる互換性保証
  • 軽量設計: Monologより軽量でリソース使用量を最小化
  • 即座の利用開始: 複雑な設定なしで迅速なログ実装
  • ファイルベースログ: シンプルで信頼性の高いファイル出力機能
  • ログレベル制御: しきい値設定による出力レベル調整
  • カスタマイズ可能: ログフォーマットと出力設定の柔軟性

メリット・デメリット

メリット

  • PSR-3準拠により他のライブラリとの互換性と交換可能性を保証
  • Monologより軽量でリソース制約環境での使用に適している
  • 設定が簡単で学習コストが低く迅速な導入が可能
  • 小規模プロジェクトに適したシンプルで分かりやすいAPI
  • 依存関係を最小限に抑えサードパーティライブラリとの衝突リスクが低い
  • JSON形式出力によりログ解析ツールとの統合が容易

デメリット

  • 高度なログ管理機能(ハンドラー、プロセッサー、フォーマッター)が制限的
  • 複数出力先やリモートログ送信などの企業レベル機能の不足
  • Monologと比較してエコシステムと拡張機能の豊富さで劣る
  • 大規模アプリケーションでの詳細なログ制御機能が不十分
  • 非同期ログ処理やバッファリング機能の欠如
  • 複雑なログフィルタリングや条件分岐処理の実装が困難

参考ページ

書き方の例

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

# Composerによるインストール
composer require katzgrau/klogger

# 依存関係確認
composer show katzgrau/klogger

# プロジェクトでの依存関係(composer.json)
{
    "require": {
        "katzgrau/klogger": "^1.2"
    }
}
<?php
require 'vendor/autoload.php';

use Katzgrau\KLogger\Logger;
use Psr\Log\LogLevel;

// 基本的な初期化
$logger = new Logger('/var/log/app');

// ログレベル設定付き初期化
$logger = new Logger('/var/log/app', LogLevel::WARNING);

// オプション設定付き初期化
$logger = new Logger(
    '/var/log/app',           // ログディレクトリ
    LogLevel::INFO,           // 最小ログレベル
    [
        'extension' => 'log',         // ファイル拡張子
        'prefix' => 'app_',          // ファイル名プレフィックス
        'logFormat' => '[{date}] [{level}] {message}',  // ログフォーマット
        'appendContext' => true       // コンテキスト情報の付加
    ]
);

// PSR-3インターフェース確認
echo "PSR-3 Logger Interface: " . ($logger instanceof \Psr\Log\LoggerInterface ? 'Yes' : 'No') . "\n";
?>

基本的なログレベル使用(8レベル対応)

<?php
use Katzgrau\KLogger\Logger;
use Psr\Log\LogLevel;

// ロガー初期化
$logger = new Logger('/var/log/myapp', LogLevel::DEBUG);

// 8つのPSR-3ログレベル使用例
$logger->emergency('システム全体が使用不可能な状態です');
$logger->alert('即座の対応が必要: データベースサーバーダウン');
$logger->critical('クリティカルエラー: 決済処理システム障害');
$logger->error('エラー: ユーザー認証に失敗しました');
$logger->warning('警告: API呼び出し制限に近づいています');
$logger->notice('通知: 新しいユーザーが登録されました');
$logger->info('情報: バッチ処理が正常完了しました');
$logger->debug('デバッグ: 変数値の確認 - $userId = 12345');

// コンテキスト情報付きログ
$context = [
    'user_id' => 12345,
    'action' => 'login_attempt',
    'ip_address' => '192.168.1.100',
    'user_agent' => 'Mozilla/5.0...'
];

$logger->info('ユーザーログイン試行', $context);
$logger->error('ログイン失敗', array_merge($context, ['reason' => 'invalid_password']));

// ビジネスロジックでのログ使用例
class UserService {
    private $logger;
    
    public function __construct(Logger $logger) {
        $this->logger = $logger;
    }
    
    public function authenticateUser($username, $password) {
        $this->logger->info('ユーザー認証開始', ['username' => $username]);
        
        try {
            if (empty($username) || empty($password)) {
                $this->logger->warning('認証失敗: 空の認証情報', ['username' => $username]);
                return false;
            }
            
            $user = $this->findUserByUsername($username);
            if (!$user) {
                $this->logger->notice('認証失敗: ユーザーが見つかりません', ['username' => $username]);
                return false;
            }
            
            if ($this->verifyPassword($password, $user['password_hash'])) {
                $this->logger->info('認証成功', [
                    'user_id' => $user['id'],
                    'username' => $username,
                    'last_login' => date('c')
                ]);
                return true;
            } else {
                $this->logger->warning('認証失敗: パスワード不一致', ['username' => $username]);
                return false;
            }
            
        } catch (Exception $e) {
            $this->logger->error('認証処理でエラー発生', [
                'username' => $username,
                'error' => $e->getMessage(),
                'file' => $e->getFile(),
                'line' => $e->getLine()
            ]);
            return false;
        }
    }
}

// 使用例
$logger = new Logger('/var/log/userservice', LogLevel::INFO);
$userService = new UserService($logger);
$result = $userService->authenticateUser('[email protected]', 'password123');
?>

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

<?php
use Katzgrau\KLogger\Logger;
use Psr\Log\LogLevel;

// 1. デフォルトフォーマット
$defaultLogger = new Logger('/var/log/default');
$defaultLogger->info('デフォルトフォーマットのログメッセージ');
// 出力例: [2025-06-24 10:30:45.123456] [info] デフォルトフォーマットのログメッセージ

// 2. カスタムフォーマット
$customLogger = new Logger('/var/log/custom', LogLevel::DEBUG, [
    'logFormat' => '[{date}] [{level}] {message} | Context: {context}',
    'dateFormat' => 'Y-m-d H:i:s T'
]);
$customLogger->info('カスタムフォーマットのテスト', ['key' => 'value']);

// 3. JSON形式ログ
$jsonLogger = new Logger('/var/log/json', LogLevel::DEBUG, [
    'logFormat' => false,  // デフォルトフォーマットを無効化
    'extension' => 'json'
]);

// JSONログの手動実装
class JsonKLogger extends Logger {
    public function log($level, $message, array $context = []) {
        $logData = [
            'timestamp' => date('c'),
            'level' => $level,
            'message' => $message,
            'context' => $context,
            'pid' => getmypid(),
            'memory_usage' => memory_get_usage(),
            'request_id' => $_SERVER['HTTP_X_REQUEST_ID'] ?? uniqid()
        ];
        
        $this->writeToFile(json_encode($logData) . PHP_EOL);
    }
    
    private function writeToFile($content) {
        $logFile = $this->getLogDirectory() . '/' . date('Y-m-d') . '.json';
        file_put_contents($logFile, $content, FILE_APPEND | LOCK_EX);
    }
    
    private function getLogDirectory() {
        // KLoggerの内部ディレクトリ取得方法
        return '/var/log/json-logs';
    }
}

// 4. 複数ファイル出力
class MultiFileLogger {
    private $loggers = [];
    
    public function __construct($baseDir) {
        $this->loggers = [
            'error' => new Logger($baseDir . '/errors', LogLevel::ERROR),
            'info' => new Logger($baseDir . '/info', LogLevel::INFO),
            'debug' => new Logger($baseDir . '/debug', LogLevel::DEBUG)
        ];
    }
    
    public function log($level, $message, $context = []) {
        // エラーレベルのログは専用ファイルへ
        if (in_array($level, [LogLevel::ERROR, LogLevel::CRITICAL, LogLevel::ALERT, LogLevel::EMERGENCY])) {
            $this->loggers['error']->log($level, $message, $context);
        }
        
        // 情報レベルのログ
        if (in_array($level, [LogLevel::INFO, LogLevel::NOTICE])) {
            $this->loggers['info']->log($level, $message, $context);
        }
        
        // デバッグログ
        if ($level === LogLevel::DEBUG) {
            $this->loggers['debug']->log($level, $message, $context);
        }
    }
    
    public function error($message, $context = []) {
        $this->log(LogLevel::ERROR, $message, $context);
    }
    
    public function info($message, $context = []) {
        $this->log(LogLevel::INFO, $message, $context);
    }
    
    public function debug($message, $context = []) {
        $this->log(LogLevel::DEBUG, $message, $context);
    }
}

// 使用例
$multiLogger = new MultiFileLogger('/var/log/myapp');
$multiLogger->error('データベース接続エラー', ['host' => 'localhost', 'port' => 3306]);
$multiLogger->info('ユーザーログイン', ['user_id' => 123]);
$multiLogger->debug('変数ダンプ', ['data' => $someData]);

// 5. ログローテーション付きLogger
class RotatingKLogger extends Logger {
    private $maxFiles;
    private $maxSize;
    
    public function __construct($dir, $level = LogLevel::DEBUG, $options = [], $maxFiles = 7, $maxSize = 10485760) {
        parent::__construct($dir, $level, $options);
        $this->maxFiles = $maxFiles;
        $this->maxSize = $maxSize;
    }
    
    public function log($level, $message, array $context = []) {
        $this->rotateLogsIfNeeded();
        parent::log($level, $message, $context);
    }
    
    private function rotateLogsIfNeeded() {
        $logFile = $this->getLogDirectory() . '/' . date('Y-m-d') . '.log';
        
        if (file_exists($logFile) && filesize($logFile) > $this->maxSize) {
            $this->rotateLogFiles($logFile);
        }
    }
    
    private function rotateLogFiles($currentLog) {
        $baseLog = $currentLog;
        
        // 既存のローテーションファイルを移動
        for ($i = $this->maxFiles - 1; $i >= 1; $i--) {
            $oldFile = $baseLog . '.' . $i;
            $newFile = $baseLog . '.' . ($i + 1);
            
            if (file_exists($oldFile)) {
                if ($i == $this->maxFiles - 1) {
                    unlink($oldFile);  // 最古のファイルを削除
                } else {
                    rename($oldFile, $newFile);
                }
            }
        }
        
        // 現在のログファイルをローテーション
        if (file_exists($currentLog)) {
            rename($currentLog, $currentLog . '.1');
        }
    }
}

// 使用例
$rotatingLogger = new RotatingKLogger('/var/log/rotating', LogLevel::INFO, [], 5, 1048576); // 1MB毎にローテーション
$rotatingLogger->info('ローテーション機能付きログのテスト');
?>

パフォーマンス監視とデバッグ活用

<?php
use Katzgrau\KLogger\Logger;
use Psr\Log\LogLevel;

// パフォーマンス監視Logger
class PerformanceLogger {
    private $logger;
    private $timers = [];
    
    public function __construct($logDir) {
        $this->logger = new Logger($logDir . '/performance', LogLevel::DEBUG, [
            'extension' => 'perf'
        ]);
    }
    
    public function startTimer($operation) {
        $this->timers[$operation] = [
            'start_time' => microtime(true),
            'start_memory' => memory_get_usage()
        ];
        
        $this->logger->debug("Performance timer started", [
            'operation' => $operation,
            'memory_start' => $this->formatMemory($this->timers[$operation]['start_memory'])
        ]);
    }
    
    public function endTimer($operation, $additionalContext = []) {
        if (!isset($this->timers[$operation])) {
            $this->logger->warning("Timer not found", ['operation' => $operation]);
            return;
        }
        
        $timer = $this->timers[$operation];
        $endTime = microtime(true);
        $endMemory = memory_get_usage();
        
        $duration = ($endTime - $timer['start_time']) * 1000; // ms
        $memoryUsed = $endMemory - $timer['start_memory'];
        
        $context = array_merge([
            'operation' => $operation,
            'duration_ms' => round($duration, 2),
            'memory_used' => $this->formatMemory($memoryUsed),
            'memory_peak' => $this->formatMemory(memory_get_peak_usage()),
            'memory_current' => $this->formatMemory($endMemory)
        ], $additionalContext);
        
        // パフォーマンス警告
        if ($duration > 1000) { // 1秒以上
            $this->logger->warning("Slow operation detected", $context);
        } elseif ($duration > 500) { // 0.5秒以上
            $this->logger->info("Operation completed (slow)", $context);
        } else {
            $this->logger->debug("Operation completed", $context);
        }
        
        unset($this->timers[$operation]);
    }
    
    public function measureMemoryUsage($operation, $callable) {
        $this->startTimer($operation);
        
        try {
            $result = $callable();
            $this->endTimer($operation, ['status' => 'success']);
            return $result;
        } catch (Exception $e) {
            $this->endTimer($operation, [
                'status' => 'error',
                'error' => $e->getMessage()
            ]);
            throw $e;
        }
    }
    
    private function formatMemory($bytes) {
        $units = ['B', 'KB', 'MB', 'GB'];
        $bytes = max($bytes, 0);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);
        
        $bytes /= pow(1024, $pow);
        return round($bytes, 2) . ' ' . $units[$pow];
    }
}

// 使用例
$perfLogger = new PerformanceLogger('/var/log/app');

// 手動計測
$perfLogger->startTimer('database_query');
// データベース処理
sleep(1); // シミュレーション
$perfLogger->endTimer('database_query', ['query' => 'SELECT * FROM users']);

// 自動計測
$result = $perfLogger->measureMemoryUsage('heavy_calculation', function() {
    // 重い処理のシミュレーション
    $data = [];
    for ($i = 0; $i < 100000; $i++) {
        $data[] = md5($i);
    }
    return count($data);
});

// デバッグ用ロガー
class DebugLogger {
    private $logger;
    private $debugMode;
    
    public function __construct($logDir, $debugMode = true) {
        $this->logger = new Logger($logDir . '/debug', LogLevel::DEBUG);
        $this->debugMode = $debugMode;
    }
    
    public function dumpVariable($variable, $label = 'Variable Dump') {
        if (!$this->debugMode) return;
        
        $this->logger->debug($label, [
            'type' => gettype($variable),
            'value' => $this->serializeVariable($variable),
            'memory_usage' => memory_get_usage(),
            'backtrace' => $this->getBacktrace()
        ]);
    }
    
    public function logFunctionCall($functionName, $args = [], $result = null) {
        if (!$this->debugMode) return;
        
        $this->logger->debug("Function call", [
            'function' => $functionName,
            'arguments' => $this->serializeVariable($args),
            'result' => $this->serializeVariable($result),
            'execution_time' => $this->getCurrentTime()
        ]);
    }
    
    public function logDatabaseQuery($query, $params = [], $executionTime = null) {
        $this->logger->info("Database query", [
            'query' => $query,
            'parameters' => $params,
            'execution_time_ms' => $executionTime ? round($executionTime * 1000, 2) : null,
            'memory_usage' => memory_get_usage()
        ]);
    }
    
    private function serializeVariable($variable) {
        if (is_object($variable)) {
            return '[Object ' . get_class($variable) . ']';
        } elseif (is_array($variable)) {
            return array_map([$this, 'serializeVariable'], $variable);
        } elseif (is_resource($variable)) {
            return '[Resource ' . get_resource_type($variable) . ']';
        } else {
            return $variable;
        }
    }
    
    private function getBacktrace() {
        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
        return array_map(function($frame) {
            return [
                'file' => $frame['file'] ?? 'unknown',
                'line' => $frame['line'] ?? 'unknown',
                'function' => $frame['function'] ?? 'unknown'
            ];
        }, array_slice($trace, 1));
    }
    
    private function getCurrentTime() {
        return date('Y-m-d H:i:s.u');
    }
}

// デバッグLogger使用例
$debugLogger = new DebugLogger('/var/log/app', true);

$userData = [
    'id' => 123,
    'name' => '田中太郎',
    'email' => '[email protected]'
];

$debugLogger->dumpVariable($userData, 'User Data');
$debugLogger->logFunctionCall('calculateTotal', [$userData], 1500.00);
$debugLogger->logDatabaseQuery(
    'SELECT * FROM users WHERE id = ?',
    [123],
    0.025 // 25ms
);
?>

エラーハンドリングと実用的な統合

<?php
use Katzgrau\KLogger\Logger;
use Psr\Log\LogLevel;

// アプリケーション統合Logger
class ApplicationLogger {
    private $logger;
    private $errorLogger;
    private $auditLogger;
    
    public function __construct($baseLogDir, $environment = 'production') {
        $logLevel = $environment === 'production' ? LogLevel::INFO : LogLevel::DEBUG;
        
        $this->logger = new Logger($baseLogDir . '/app', $logLevel);
        $this->errorLogger = new Logger($baseLogDir . '/errors', LogLevel::WARNING);
        $this->auditLogger = new Logger($baseLogDir . '/audit', LogLevel::INFO);
    }
    
    // 包括的エラーハンドリング
    public function handleException(Exception $e, $context = []) {
        $errorContext = array_merge([
            'exception_type' => get_class($e),
            'message' => $e->getMessage(),
            'file' => $e->getFile(),
            'line' => $e->getLine(),
            'trace' => $e->getTraceAsString(),
            'request_uri' => $_SERVER['REQUEST_URI'] ?? '',
            'http_method' => $_SERVER['REQUEST_METHOD'] ?? '',
            'user_ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
            'timestamp' => date('c')
        ], $context);
        
        $this->errorLogger->error('Unhandled exception', $errorContext);
        
        // クリティカルエラーの場合は別途記録
        if ($e instanceof CriticalException) {
            $this->errorLogger->critical('Critical system error', $errorContext);
        }
    }
    
    // セキュリティ監査ログ
    public function logSecurityEvent($event, $severity = 'info', $context = []) {
        $auditContext = array_merge([
            'event_type' => 'security',
            'event' => $event,
            'user_id' => $_SESSION['user_id'] ?? 'anonymous',
            'session_id' => session_id(),
            'ip_address' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown',
            'timestamp' => date('c')
        ], $context);
        
        switch ($severity) {
            case 'critical':
                $this->auditLogger->critical($event, $auditContext);
                break;
            case 'warning':
                $this->auditLogger->warning($event, $auditContext);
                break;
            default:
                $this->auditLogger->info($event, $auditContext);
        }
    }
    
    // ビジネスロジックログ
    public function logBusinessEvent($event, $data = []) {
        $this->logger->info($event, array_merge($data, [
            'event_category' => 'business',
            'timestamp' => date('c')
        ]));
    }
    
    // API呼び出しログ
    public function logApiCall($endpoint, $method, $responseCode, $duration = null, $context = []) {
        $logContext = array_merge([
            'category' => 'api',
            'endpoint' => $endpoint,
            'method' => $method,
            'response_code' => $responseCode,
            'duration_ms' => $duration ? round($duration * 1000, 2) : null,
            'timestamp' => date('c')
        ], $context);
        
        if ($responseCode >= 500) {
            $this->errorLogger->error('API server error', $logContext);
        } elseif ($responseCode >= 400) {
            $this->logger->warning('API client error', $logContext);
        } else {
            $this->logger->info('API call', $logContext);
        }
    }
}

// グローバルエラーハンドラー設定
$appLogger = new ApplicationLogger('/var/log/myapp', 'production');

// 例外ハンドラー
set_exception_handler(function($exception) use ($appLogger) {
    $appLogger->handleException($exception);
});

// エラーハンドラー
set_error_handler(function($severity, $message, $filename, $lineno) use ($appLogger) {
    $errorLevels = [
        E_ERROR => 'error',
        E_WARNING => 'warning',
        E_NOTICE => 'notice',
        E_USER_ERROR => 'error',
        E_USER_WARNING => 'warning',
        E_USER_NOTICE => 'notice'
    ];
    
    $level = $errorLevels[$severity] ?? 'info';
    $context = [
        'severity' => $severity,
        'file' => $filename,
        'line' => $lineno
    ];
    
    if ($level === 'error') {
        $appLogger->handleException(new ErrorException($message, 0, $severity, $filename, $lineno), $context);
    } else {
        $appLogger->logger->$level($message, $context);
    }
});

// 実用例
class UserController {
    private $logger;
    
    public function __construct(ApplicationLogger $logger) {
        $this->logger = $logger;
    }
    
    public function login($username, $password) {
        $startTime = microtime(true);
        
        try {
            $this->logger->logSecurityEvent('login_attempt', 'info', ['username' => $username]);
            
            if ($this->authenticateUser($username, $password)) {
                $duration = microtime(true) - $startTime;
                
                $this->logger->logBusinessEvent('user_login_success', [
                    'username' => $username,
                    'duration_ms' => round($duration * 1000, 2)
                ]);
                
                $this->logger->logSecurityEvent('login_success', 'info', ['username' => $username]);
                
                return true;
            } else {
                $this->logger->logSecurityEvent('login_failure', 'warning', [
                    'username' => $username,
                    'reason' => 'invalid_credentials'
                ]);
                
                return false;
            }
            
        } catch (Exception $e) {
            $this->logger->handleException($e, [
                'context' => 'user_login',
                'username' => $username
            ]);
            return false;
        }
    }
    
    public function callExternalAPI($endpoint, $data) {
        $startTime = microtime(true);
        
        try {
            // API呼び出しシミュレーション
            $response = $this->makeHttpRequest($endpoint, $data);
            $duration = microtime(true) - $startTime;
            
            $this->logger->logApiCall(
                $endpoint,
                'POST',
                $response['status_code'],
                $duration,
                ['request_size' => strlen(json_encode($data))]
            );
            
            return $response;
            
        } catch (Exception $e) {
            $duration = microtime(true) - $startTime;
            
            $this->logger->logApiCall(
                $endpoint,
                'POST',
                0,
                $duration,
                ['error' => $e->getMessage()]
            );
            
            throw $e;
        }
    }
    
    private function authenticateUser($username, $password) {
        // 認証ロジック
        return $username === 'admin' && $password === 'password';
    }
    
    private function makeHttpRequest($endpoint, $data) {
        // HTTP リクエストシミュレーション
        return [
            'status_code' => 200,
            'data' => ['result' => 'success']
        ];
    }
}

// 使用例
$controller = new UserController($appLogger);
$result = $controller->login('admin', 'password');
$apiResponse = $controller->callExternalAPI('/api/users', ['name' => 'test']);
?>