file_put_contents()

PHP標準のファイル書き込み関数をロギング目的で使用。最もシンプルなファイルログ実装が可能だが、ログレベル、ローテーション、フォーマット等の機能は手動実装が必要。小規模プロジェクトや学習用途に限定的に適している。

ファイル書き込み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(); // 当月のログを圧縮