【Laravel】ログを容量でローテートするためのコマンドを作る

  • 2024年5月16日
  • 2024年5月16日
  • Laravel

 この記事で扱っているコードを試したLaravelのバージョンは10です。

 LaravelはPHPのフレームワークでログを残すための機能が備わっています。このログ機能には日付単位でファイルを削除する機能があります。15日前のログファイルを削除すること常に14日分のログを残すとかそんな感じです。この機能は便利なのですが、ログファイルが巨大になると日付単位のローテートでは間に合わないくらいマシンの容量を占有してしまう危険性が残っています。マシンの空き容量がなくなると様々な機能が停止します。特にLinuxはファイルで様々なものを管理しているためファイルを追加できなくなると致命傷になりやすいです。そこでログの占めている容量を調べ、容量に合わせてログを自動削除するコマンドを作りたくなります。

 ログを容量で削除するコマンドの実装例は次です。

<?php

namespace App\Console\Commands\Log;

use App\Console\BaseCommand;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;

class RotateOldFiles extends BaseCommand
{
    protected $signature = 'file:rotate-old-files
                            {--max-size-mb=4000 : 最大容量(MB)}
                            {--path=/storage/logs : 削除対象ディレクトリのパス}
                            {--pattern : 削除対象ファイルの名前パターン。デフォルトは*.log}
                            {--force : 確認なしでファイルを削除する}
                            ';
    protected $description = 'あるディレクトリ以下のファイルを古い順に削除して容量を減らす';

    public function handle(): int
    {
        $tgtDirectory = base_path($this->option('path'));
        $maxSizeByte = $this->option('max-size-mb') * 1024 * 1024;
        if(!$this->option('force') && !$this->confirm("{$tgtDirectory} のファイルを {$this->option('max-size-mb')}MB 以下になるように古い順で削除しますか?")) {
            return 0;
        }

        $removedFiles = $this->cleanOldFiles($tgtDirectory, $maxSizeByte);
        if(empty($removedFiles)) {
            $this->info('削除されたファイルはありませんでした');
            return 0;
        }

        $this->info('以下のファイルを削除しました');
        foreach ($removedFiles as $file) {
            $this->warn($file);
        }

        return 0;
    }

    /**
     * ディレクトリのサイズをバイト単位で取得する
     * @param string $directory
     * @return int
     */
    function getDirectorySizeByte(string $directory): int
    {
        $size = 0;

        // ディレクトリ以下を再帰的に回すイテレータを作成
        $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory));
        // ディレクトリ以下のファイル全てのサイズ見て合計する
        foreach ($files as $file) {
            if ($file->isFile()) {
                $size += $file->getSize();
            }
        }

        return $size;
    }

    /**
     * @param string $directory
     * @param int $maxSizeByte
     * @return string[] 削除したファイルのパス
     */
    private function cleanOldFiles(string $directory, int $maxSizeByte): array
    {
        $pattern = $this->option('pattern') ?: '*.log';
        // ディレクトリ以下を再帰的に回すイテレータを配列化する
        $files = iterator_to_array(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory)));
        // パターンにマッチするファイルのみに絞り込む
        $files = array_filter($files, fn ($file) => fnmatch($pattern, $file->getFilename()));
        // ファイルを更新日時の昇順でソート
        usort($files, fn ($a, $b) => $a->getMTime() - $b->getMTime());

        // ディレクトリの現在のサイズを取得
        $currentSize = $this->getDirectorySizeByte($directory);

        // ファイルを更新日時が古い順に削除していく
        $removedFiles = [];
        foreach ($files as $file) {
            if(!is_file($file)) {
                continue;
            }
            if ($currentSize <= $maxSizeByte) {
                // 最大容量より小さくなって入れば終了
                break;
            }
            // ファイルを削除してサイズを更新&削除したファイルを記録
            $currentSize -= $file->getSize();
            $removedFiles[] = $file->getRealPath();
            unlink($file->getRealPath());
        }
        return $removedFiles;
    }
}

 これを次のように手動実行すると古いログファイルを削除してくれます。

$ php artisan file:rotate-old-files --max-size-mb=10 --path=storage/logs
 /var/www/html/storage/logs のファイルを 10MB 以下になるように古い順で削除しますか? (yes/no) [no]:
 > yes

以下のファイルを削除しました
/var/www/html/storage/logs/req_res-www-data-2024-05-14.log

 次のようにLaravelのスケジューラに登録して定期実行させることもできます。

<?php

namespace App\Console;

use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    protected function schedule(Schedule $schedule): void
    {
        $schedule->command('file:rotate-old-files', [
            '--max-size-mb' => '4000',
            '--path' => '/storage/logs',
            '--force',
        ])->everyMinute(); // とりあえず毎分
    }
}

 このようにするとログファイルが不意に巨大化しても、すぐにログファイルが削除されるため問題が起きにくくなります。

>株式会社シーポイントラボ

株式会社シーポイントラボ

TEL:053-543-9889
営業時間:9:00~18:00(月〜金)
住所:〒432-8003
   静岡県浜松市中央区和地山3-1-7
   浜松イノベーションキューブ 315
※ご来社の際はインターホンで「316」をお呼びください

CTR IMG