膨大なログ見て何思う

この記事はだいぶ前に書かれたものなので情報が古いかもしれません

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が作成されると、そういう感じです。



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

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

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

通りすがり 2013年04月01日 18:15:20
linux環境なら loglotate 機能つかった方が簡単じゃないかな。

ところで、素敵なサイトのデザインですね。
和風で良い感じ。
まっち~(管理人) 2013年04月02日 11:59:58
>通りすがりさん
loglotateっていうと、アクセスログとかエラーログとかのあれですかね……?
知識が浅はかですんません。
最近は仕事でクラウドサーバーとか使うようになったんで、エラーログの設定とかもようやくやるようになりました。
今まではそういうのがあることすらよく知らなかったんですけどね^^;

ってか、サイトのデザインを初めて褒められた。めっちゃ嬉しいです。ありがとうございます!
ウェブフォントがもうちょい速く表示できるようになると、もっと良いんですけどね。
フランス製 2015年05月30日 15:53:55
ちょっとが存在し、あなたのためにあなたに感謝情報 - 私がした 間違いなく |右ここから新しい何か何かをピックアップ。 |この使用して|技術的な点の問題 サイト、として私はリロードする経験 正しく|私の前}回の多くは、それが適切に{ロードする得ることができます。 ウェブホスティング OKであなたの場合、私は疑問に思っていましたか?ないこと私は文句が、スローローディングインスタンス時間はしばしばますグーグルでの配置に影響を与えると可能性があなた高品質を損傷とマーケティング| 広告場合-quality得点。 とにかく 私は私にこのRSS追加電子メールとでした |魅力|興味深いあなたのそれぞれの興味深いの多くをするために外を見ますコンテンツ。 非常にすぐにすぐにあなたは再びこれを更新| ていることを確認していることを確認します。
まっち~(管理人) 2015年06月02日 15:42:41
>フランス製さん
私は感謝した、間違いなく。コメントいただけたことに。それはこの記事が、ウェブのテクノロジーのテクニカルのインフォメーションのアウトプットにより、何らかの役に立てたと考える(無駄な情報も多く含まれる中で)、その証拠となり得ると判断できると判断します。
もしかしたら何か関連しているかも?