この記事はだいぶ前に書かれたものなので情報が古いかもしれません
CakePHPって、エラーとかあると自動的にログに書き込んでくれるじゃないですか。「error.log」とか「debug.log」とかに、自動的に出力してくれるじゃないですか。

でもあれ、ふとした瞬間に見てみたりすると、結構大変な容量になってたりしますよね。同じファイルに延々とログを書いて行くのだから、当たり前ではあるんですけど。

だから、ファイルの容量が一定を超えたら、適当な名前にリネームして、また0バイトからログを書き出してくれるような機能があったらな〜と思いまして……少なくともCakeさんはそれを自動ではやってくれません。おいしくないです。

今までは、パッと見た時に容量が膨れすぎていたら自分でリネームしてたんですけど、今回はそれの自動化に挑戦してみました。



$this->logを知る

CakePHPでログを出力する関数は、$this->log()です。結構便利なんで僕もよく使いますが、デバッグエラーなどに関しては、Cakeが勝手に$this->log()をやってくれている。

だからこの関数をカスタマイズできれば実現は可能だと思うんですよ。思うっていうか、可能です。

じゃあ$this->log()はどこにあるのかな、どんな処理をしてるのかなってのを追ってみたんですが、最終的に書き込む関数に辿り着くまでに、結構な段階を踏んでることが分かりました。

フローで見てみましょう。

$this->log() ・・・ /cake/libs/object.phpの中にある。CakeLog::write()という関数を呼び出して書き込み処理を行っている。

CakeLog::write() ・・・ /cake/libs/cake_log.phpの中にある。$logger->write()という関数を呼び出して書き込み処理を行っている。

$logger->write() ・・・ /cake/libs/log/file_log.phpの中にある。$log->append()という関数を呼び出して書き込み処理を行っている。

$log->append() ・・・ /cake/libs/file.phpの中にある。$this->write()という関数を呼び出しているだけ。

$this->write() ・・・ /cake/libs/file.phpの中にある。ここで書き込みを行っている。

まあ、何かこんな感じで最終的にログファイルに書き込みを行っているようです。

要はこの辺りのファイルを直接いじってしまえば良いんですが、基本的にcakeフォルダの中身は極力いじらない方が良いと教えられて育ってきたので、何とかapp側でカスタマイズできるようにしたいところです。


で、肝心の$this->log()の中身なんですが、関数の最初でこんな処理をやってるんですね。

if (!class_exists('CakeLog')) {
  require LIBS . 'cake_log.php';
}

CakeLogというクラスが定義されていなかったら、cake_log.phpを読み込みますよ、という意味ですね。

つまり、cake_log.phpよりも先に自分でCakeLogというクラスを定義してしまえば、そっちのwrite()メソッドが呼ばれることになります。



app側でCakeLogを定義する

こんな記事を見つけました。

まさにCakeLogをapp側で定義するというピンポイントな情報でございます。

やり方としては、app側に適当なファイル(ここでは「app_log.php」としていますね)を作成し、それをcake_log.phpよりも先に読み込むようにするというものです。

基本的にはこのサイトに書いてあるやり方をやらせてもらいましたが、若干ファイルの置き場などを変えたので、それを書いてみます。

まず、app_log.phpというファイルを作成します。置き場は/app/configの下にしておきましょう。ファイルの中身は、cake_log.phpをそのままコピーすれば問題ないです。

で、次にconfigの中にあるcore.phpの先頭で、このファイルを読み込む。

//core.php
require_once dirname(__FILE__).'app_log.php';

これで準備はOKです。こうすることで、$this->log()での書き込み処理が発生した場合、cake_log.php側のwrite()メソッドではなく、app_log.phpのwrite()メソッドが呼ばれるようになります。

何で先頭に書くかっていう話なんですが、core.phpって、最初の方でdebugやlogの設定をしてるじゃないですか。こんな感じで。

Configure::write('debug', 2);
Configure::write('log', true);

この処理は/cake/libs/configure.phpのwrite()メソッドでやってるんですけど、ここでcake_log.phpを読み込む処理があるんですね。だから少なくともこの設定よりは先にapp_log.phpを呼んでやらないといけない。だからまあ、先頭に書いとけばとりあえず間違いはないってことです。



write()メソッドを書き換える

cake_log.phpの中にあるwrite()メソッドはこんな感じになってます。

if (!defined('LOG_ERROR')) {
  define('LOG_ERROR', 2);
}
if (!defined('LOG_ERR')) {
  define('LOG_ERR', LOG_ERROR);
}
$levels = array(
  LOG_WARNING => 'warning',
  LOG_NOTICE => 'notice',
  LOG_INFO => 'info',
  LOG_DEBUG => 'debug',
  LOG_ERR => 'error',
  LOG_ERROR => 'error'
);

if (is_int($type) && isset($levels[$type])) {
  $type = $levels[$type];
}
$self =& CakeLog::getInstance();
if (empty($self->_streams)) {
  $self->_autoConfig();
}
$keys = array_keys($self->_streams);
foreach ($keys as $key) {
  $logger =& $self->_streams[$key];
  $logger->write($type, $message);
}
return true;

今回は、ログを書き込む前に一度そのファイルのサイズを見に行き、もしファイルサイズが1MBを超えていたら、そのファイルを任意の名前にリネームしてしまうという処理を入れてみました。

それがこちら。

if (!defined('LOG_ERROR')) {
  define('LOG_ERROR', 2);
}
if (!defined('LOG_ERR')) {
  define('LOG_ERR', LOG_ERROR);
}
$levels = array(
  LOG_WARNING => 'warning',
  LOG_NOTICE => 'notice',
  LOG_INFO => 'info',
  LOG_DEBUG => 'debug',
  LOG_ERR => 'error',
  LOG_ERROR => 'error'
);

if (is_int($type) && isset($levels[$type])) {
  $type = $levels[$type];
}
$self =& CakeLog::getInstance();    
if (empty($self->_streams)) {
  $self->_autoConfig();
}

$keys = array_keys($self->_streams);
foreach ($keys as $key) {
  $logger =& $self->_streams[$key];
  
  if(in_array($type, $levels)) {
    if($type == 'error') {
      $filename = 'error.log';
    } else {
      $filename = 'debug.log';
    }
  } else {
    $filename = $type.'.log';
  }
  
  if(file_exists($logger->_path.$filename)) {
    //ファイルが1MBを超えていたらリネームする
    $filesize = filesize($logger->_path.$filename);
    if($filesize > 1048576) {
      rename($logger->_path.$filename, $logger->_path.$filename.'_'.date('YmdHis').'.log');
    }
  }
  
  $logger->write($type, $message);
}
return true;

やってることは簡単です。これから書き込まれようとしてるファイルがerror.logなのかdebug.logなのかそれ以外の自分で定義したファイルなのかを判定し、そのファイルの現在のサイズを見に行き、1MBを超えているようであれば、ファイル名を書き換えてしまう。それだけです。

こうすれば、例えばerror.logが1MBを超えていた場合、ファイル名が「error_20111021120000.log」みたいな名前に書き変わり、新規にerror.logが作成されると、そういう感じです。



すげーざっくりに話しましたが、どうでしょうか?

もし僕と同じように、ログファイルを適当なタイミングで切り替えたいな〜って思ってる人がいて、その人がこの記事を読んで役に立ったな〜って思ってくれたら、これ幸いでござんす。

それに、もし読んでもよく分からなかったら、もっと良い参考サイトを探すか、質問内容でもコメントで書いて、最後に一言「もっと分かるように書きやがれごるぁ」って添えてもられば、良いんじゃないかな? かな?