file_put_contents()
PHP標準のファイル書き込み関数をロギング目的で使用。最もシンプルなファイルログ実装が可能だが、ログレベル、ローテーション、フォーマット等の機能は手動実装が必要。小規模プロジェクトや学習用途に限定的に適している。
ライブラリ
file_put_contents
概要
file_put_contentsは「PHPにおけるファイル書き込みのためのビルトイン関数」として、シンプルなロギング機能を提供する基本的なツールです。ファイルオープン、データ書き込み、ファイルクローズの処理を一関数で実行し、FILE_APPENDフラグによる追記モード、LOCK_EXによる排他制御をサポート。軽量でデバッグ用途やプロトタイプ開発に適しており、PHPの標準機能のみでログファイル作成と管理を実現できます。
詳細
file_put_contents関数は PHP 5.0 以降で利用可能な標準ライブラリ関数で、単一行でファイル操作を完結できる簡潔さが特徴です。ログ出力においては FILE_APPEND フラグによる既存内容の保持、LOCK_EX フラグによる排他制御で並行アクセス時の競合状態を回避。JSON形式でのログ構造化出力、タイムスタンプ付きエントリ、エラーレベル分類など基本的なログ機能を実装可能。専用ログライブラリほどの高機能性はありませんが、学習コストが低く即座に導入できる利便性があります。
主な特徴
- シンプルな一関数実装: ファイル操作をワンライナーで実行
- 追記モード対応: FILE_APPENDフラグによる既存ログ保持
- 排他制御: LOCK_EXによる並行書き込み競合回避
- フラグ組み合わせ: 複数オプションの同時指定可能
- エラーハンドリング: 戻り値によるファイル操作成功判定
- パフォーマンス: 軽量で高速なファイル書き込み処理
メリット・デメリット
メリット
- PHPビルトイン関数のため追加ライブラリ不要でゼロ依存
- 学習コストが極めて低く即座に実装・活用可能
- FILE_APPEND、LOCK_EXフラグによる実用的なログ機能
- 軽量で高速なファイル書き込み処理によるパフォーマンス
- デバッグやプロトタイプ開発での手軽さと柔軟性
- JSON出力対応による構造化ログとパース容易性
デメリット
- ログレベル、ローテーション等の高度なログ管理機能なし
- 大量ログ出力時のパフォーマンス制約とメモリ使用量
- ファイルパーミッション設定やディレクトリ権限管理の手動対応
- 本格的なログ分析や監視システム統合には機能不足
- エラー処理やリトライ機構などエンタープライズ要件への対応限界
- 複数ファイルやログ集約などスケーラビリティの制約
参考ページ
書き方の例
基本セットアップ
<?php
// 基本的なログ出力
$message = "アプリケーション開始: " . date('Y-m-d H:i:s');
file_put_contents('app.log', $message . PHP_EOL);
// ログディレクトリの確認・作成
$logDir = __DIR__ . '/logs';
if (!is_dir($logDir)) {
mkdir($logDir, 0755, true);
}
// ログファイルパスの設定
$logFile = $logDir . '/application.log';
// パーミッション確認
if (!is_writable(dirname($logFile))) {
throw new Exception('ログディレクトリに書き込み権限がありません');
}
基本的なログ出力
<?php
// 追記モードでのログ出力
$logFile = 'logs/app.log';
$message = '[' . date('Y-m-d H:i:s') . '] INFO: ユーザーログイン成功';
// FILE_APPENDフラグで既存内容を保持
file_put_contents($logFile, $message . PHP_EOL, FILE_APPEND);
// 排他制御付きログ出力(並行アクセス対応)
file_put_contents(
$logFile,
$message . PHP_EOL,
FILE_APPEND | LOCK_EX // 複数フラグの組み合わせ
);
// ログレベル付きメッセージ
function writeLog($level, $message, $logFile = 'logs/app.log') {
$timestamp = date('Y-m-d H:i:s');
$logEntry = "[{$timestamp}] {$level}: {$message}" . PHP_EOL;
$result = file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
if ($result === false) {
error_log("ログファイルへの書き込みに失敗: {$logFile}");
}
return $result !== false;
}
// 使用例
writeLog('INFO', 'アプリケーション開始');
writeLog('WARNING', 'メモリ使用量が閾値に近づいています');
writeLog('ERROR', 'データベース接続エラー');
高度な設定(構造化ログ、エラーハンドリング)
<?php
class SimpleLogger {
private $logFile;
private $dateFormat;
public function __construct($logFile, $dateFormat = 'Y-m-d H:i:s') {
$this->logFile = $logFile;
$this->dateFormat = $dateFormat;
// ログディレクトリの確認・作成
$dir = dirname($logFile);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
}
public function log($level, $message, $context = []) {
$logEntry = [
'timestamp' => date($this->dateFormat),
'level' => strtoupper($level),
'message' => $message,
'context' => $context,
'memory_usage' => memory_get_usage(true),
'process_id' => getmypid()
];
// JSON形式での構造化ログ
$jsonLog = json_encode($logEntry, JSON_UNESCAPED_UNICODE) . PHP_EOL;
$result = file_put_contents(
$this->logFile,
$jsonLog,
FILE_APPEND | LOCK_EX
);
if ($result === false) {
// フォールバック: システムログに出力
error_log("ファイルログ出力失敗: {$this->logFile}");
error_log($message);
}
return $result !== false;
}
public function info($message, $context = []) {
return $this->log('info', $message, $context);
}
public function warning($message, $context = []) {
return $this->log('warning', $message, $context);
}
public function error($message, $context = []) {
return $this->log('error', $message, $context);
}
public function debug($message, $context = []) {
return $this->log('debug', $message, $context);
}
}
// 使用例
$logger = new SimpleLogger('logs/app.log');
$logger->info('ユーザーログイン', ['user_id' => 123, 'ip' => '192.168.1.100']);
$logger->warning('API制限に近づいています', ['current_calls' => 950, 'limit' => 1000]);
$logger->error('支払い処理エラー', [
'order_id' => 'ORD-12345',
'amount' => 9800,
'error_code' => 'PAYMENT_DECLINED'
]);
エラーハンドリング
<?php
// リトライ機能付きログ出力
function writeLogWithRetry($message, $logFile, $maxRetries = 3, $delay = 100000) {
$attempts = 0;
while ($attempts < $maxRetries) {
$result = file_put_contents(
$logFile,
$message . PHP_EOL,
FILE_APPEND | LOCK_EX
);
if ($result !== false) {
return true;
}
$attempts++;
if ($attempts < $maxRetries) {
usleep($delay); // マイクロ秒単位の待機
$delay *= 2; // 指数的バックオフ
}
}
// 最終的に失敗した場合の処理
error_log("ログ書き込み失敗({$maxRetries}回試行後): {$logFile}");
return false;
}
// ディスク容量チェック付きログ出力
function writeLogWithDiskCheck($message, $logFile, $minFreeSpace = 100 * 1024 * 1024) { // 100MB
$freeSpace = disk_free_space(dirname($logFile));
if ($freeSpace < $minFreeSpace) {
error_log("ディスク容量不足: {$freeSpace} bytes");
return false;
}
return file_put_contents(
$logFile,
$message . PHP_EOL,
FILE_APPEND | LOCK_EX
) !== false;
}
// ファイルサイズ制限付きログ出力
function writeLogWithSizeLimit($message, $logFile, $maxSize = 10 * 1024 * 1024) { // 10MB
if (file_exists($logFile) && filesize($logFile) > $maxSize) {
// ログローテーション(簡易版)
$backupFile = $logFile . '.old';
rename($logFile, $backupFile);
}
return file_put_contents(
$logFile,
$message . PHP_EOL,
FILE_APPEND | LOCK_EX
) !== false;
}
// 包括的なエラーハンドリング例
function safeLogWrite($level, $message, $context = []) {
$logFile = 'logs/app.log';
try {
// ログエントリの構築
$logEntry = [
'timestamp' => date('c'), // ISO 8601形式
'level' => $level,
'message' => $message,
'context' => $context,
'request_id' => $_SERVER['HTTP_X_REQUEST_ID'] ?? uniqid()
];
$jsonLog = json_encode($logEntry, JSON_UNESCAPED_UNICODE);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new Exception('JSON encode error: ' . json_last_error_msg());
}
// ディスク容量・ファイルサイズチェック
if (!writeLogWithDiskCheck($jsonLog, $logFile)) {
throw new Exception('Disk space or file size limit exceeded');
}
// リトライ付き書き込み
if (!writeLogWithRetry($jsonLog, $logFile)) {
throw new Exception('Failed to write log after retries');
}
return true;
} catch (Exception $e) {
// フォールバック: syslogに出力
syslog(LOG_ERR, "Log write failed: {$e->getMessage()} - Original: {$message}");
return false;
}
}
// 使用例
safeLogWrite('INFO', 'ユーザー登録完了', ['user_id' => 456]);
safeLogWrite('ERROR', 'データベース接続失敗', ['dsn' => 'mysql:host=localhost', 'code' => 2002]);
実用例(本格的なログ管理)
<?php
// 日付別ログファイル管理クラス
class DailyLogger {
private $logDir;
private $prefix;
public function __construct($logDir = 'logs', $prefix = 'app') {
$this->logDir = rtrim($logDir, '/');
$this->prefix = $prefix;
if (!is_dir($this->logDir)) {
mkdir($this->logDir, 0755, true);
}
}
private function getLogFile($date = null) {
$date = $date ?: date('Y-m-d');
return "{$this->logDir}/{$this->prefix}-{$date}.log";
}
public function log($level, $message, $context = []) {
$logFile = $this->getLogFile();
$logEntry = [
'timestamp' => microtime(true),
'datetime' => date('Y-m-d H:i:s.u'),
'level' => strtoupper($level),
'message' => $message,
'context' => $context,
'backtrace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3)
];
$jsonLog = json_encode($logEntry, JSON_UNESCAPED_UNICODE) . PHP_EOL;
return file_put_contents($logFile, $jsonLog, FILE_APPEND | LOCK_EX) !== false;
}
// ログファイルの自動削除(保存期間管理)
public function cleanup($retentionDays = 30) {
$cutoffDate = date('Y-m-d', strtotime("-{$retentionDays} days"));
$files = glob("{$this->logDir}/{$this->prefix}-*.log");
foreach ($files as $file) {
if (preg_match('/-(\d{4}-\d{2}-\d{2})\.log$/', $file, $matches)) {
if ($matches[1] < $cutoffDate) {
unlink($file);
}
}
}
}
// ログファイルの圧縮(月次)
public function compress($month = null) {
$month = $month ?: date('Y-m');
$files = glob("{$this->logDir}/{$this->prefix}-{$month}-*.log");
if (empty($files)) {
return false;
}
$archiveFile = "{$this->logDir}/{$this->prefix}-{$month}.tar.gz";
$tar = new PharData($archiveFile);
foreach ($files as $file) {
$tar->addFile($file, basename($file));
unlink($file); // 原本削除
}
return true;
}
}
// パフォーマンス監視付きログ出力
class PerformanceLogger extends DailyLogger {
private $startTimes = [];
public function startTimer($label) {
$this->startTimes[$label] = microtime(true);
}
public function endTimer($label, $context = []) {
if (!isset($this->startTimes[$label])) {
$this->log('WARNING', "Timer '{$label}' was not started");
return;
}
$duration = microtime(true) - $this->startTimes[$label];
unset($this->startTimes[$label]);
$context['duration_ms'] = round($duration * 1000, 2);
$context['duration_s'] = round($duration, 4);
$this->log('PERF', "Timer '{$label}' completed", $context);
}
public function logQuery($sql, $params = [], $duration = null) {
$context = [
'sql' => $sql,
'params' => $params,
'duration_ms' => $duration ? round($duration * 1000, 2) : null
];
$this->log('SQL', 'Database query executed', $context);
}
}
// 使用例
$logger = new PerformanceLogger();
$logger->startTimer('user_registration');
// ユーザー登録処理
$logger->logQuery('INSERT INTO users (name, email) VALUES (?, ?)', ['田中太郎', '[email protected]']);
$logger->endTimer('user_registration', ['user_id' => 789]);
// 月次クリーンアップ(cronで実行)
$logger->cleanup(90); // 90日以上の古いログを削除
$logger->compress(); // 当月のログを圧縮