CakePHP2 ログローテーションの重複実行の回避

事情によりCakePHP2を使っているのですが、アクセスが増えた時に、レスポンス遅延が発生しました。 どうも警告が大量に出力されたり、そもそもログ出力が多かったりして、ログローテーションが重複実行されて サーバが不安定になったことがあり、CakePHP2のファイルログのソースコード(lib/Cake/Log/Engine/FileLog.php)を見たところ、 ログ出力メソッド内で、ファイルサイズをチェックして、renameしたりunlinkしていくという、かなり愚直な実装になっていました。 そして、重複実行をブロックするような処理はありませんでした。 CakePHP2を使ってPVが多いサイトを運用してきた人達もいっぱいいると思うんですが、大丈夫なんですかね?問題なかったんだろうか。

CakePHP2のFileLogを拡張して、ログローテーションにロック機能を付けてみました。

app/Log/Engine/AppFileLog.php

Cakeのソースである FileLog.php の _rotateFile メソッドをオーバーライドすることでログローテーションにロック機能を 付けてみました。

<?php
App::uses('FileLog', 'Log/Engine');
 
class AppFileLog extends FileLog {
    /**
     * ローテイトする必要があればローテイトを行なうメソッド
     *
     * ローテイトにロック機能を付与して、重複してローテイト処理が行なわれるのを回避します。
     * ログディレクトリに、ロックファイル(rotate.lockディレクトリ)を作成することでロックします。
     * 10分以上前に作成されたロックファイルは、期限切れとして削除します。
     *
     * @param string $filename Log file name
     * @return mixed True if rotated successfully or false in case of error, otherwise null.
     *   Void if file doesn't need to be rotated.
     */
    protected function _rotateFile($filename) {
        $filepath = $this->_path . $filename;
        if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
            clearstatcache(true, $filepath);
        } else {
            clearstatcache();
        }
 
        if (!file_exists($filepath) ||
            filesize($filepath) < $this->_size
        ) {
            return null;
        }
 
        //ローテイトロックの確認
        $rotate_lock = sprintf('%s/rotate.lock', $this->_path);
        $rotating = file_exists($rotate_lock);
        if ($rotating) {
            $expire = strtotime('10 minutes ago');
            $created = filemtime($rotate_lock);
            if ($created < $expire){
                rmdir($rotate_lock); //10分前のロックファイルは既に期限切れとして削除する。
                $rotating = false;    //ロック解除する。
            }
        }
        if ($rotating) {
            //ローテイト中なら終了する。重複して処理しない。
            return;
        }
        //ロックファイル作成
        mkdir($rotate_lock);
 
        if ($this->_config['rotate'] === 0) {
            $result = unlink($filepath);
        } else {
            $result = rename($filepath, $filepath . '.' . time());
        }
 
        $files = glob($filepath . '.*');
        if ($files) {
            $filesToDelete = count($files) - $this->_config['rotate'];
            while ($filesToDelete > 0) {
                unlink(array_shift($files));
                $filesToDelete--;
            }
        }
 
        rmdir($rotate_lock); //ロックファイルを削除する。
        return $result;
    }
}

app/Config/bootstrap.php

bootstrap.php で、engine に AppFile を指定することで、追加した AppFileLog.php を使うようにしています。

<?php
CakeLog::config('debug', array(
    'engine' => 'AppFile',
    'types' => array('notice', 'info', 'debug'),
    'file' => 'debug' . '_' . date('Ymd'),
));
CakeLog::config('error', array(
    'engine' => 'AppFile',
    'types' => array('warning', 'error', 'critical', 'alert', 'emergency'),
    'file' => 'error' . '_' . date('Ymd'),
));

新しいシステムなら、ログはfluentdとかに纏めるんだろうなぁ。

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.