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