Yii Frameworkのログ出力を公式ガイドで見ると、
何やら便利なのは分かったけど結局出力してるのどこ?
という感じだったので、
『ログはなぜ出力されるのか』的な感じで追ってみました。
コードリーディングメモ的な記事になってます。
公式ガイド
http://www.yiiframework.com/doc/guide/1.1/ja/topics.logging
Yiiでは、ざっくり分けると以下の様な形でログを扱うようです。
- ログの経路・フィルタを設定
- メモリ上に記録
- 記録されたログを経路ごとにフィルタを掛けて出力
出力先には、DBや,ファイル、メール、webページの最後などがあります。
1. ログの経路・フィルタを設定
ツアーの最初はリクエストを受け取ったところからです。
公式ガイドにあるように、以下の様な設定でCLogRouterをプリロードしておくと、
ここのinitで設定ファイルに記載した経路(CLogRouteを継承したRouteクラス)
を設定してくれます。
<?php
array(
......
'preload'=>array('log'),
'components'=>array(
......
'log'=>array(
'class'=>'CLogRouter',
'routes'=>array(
array(
'class'=>'CFileLogRoute',
'levels'=>'trace, info',
'categories'=>'system.*',
),
array(
'class'=>'CEmailLogRoute',
'levels'=>'error, warning',
'emails'=>'admin@example.com',
),
),
),
),
)
呼び出されるinit
<?php
public function init()
{
parent::init();
foreach($this->_routes as $name=>$route)
{
$route=Yii::createComponent($route);
$route->init();
$this->_routes[$name]=$route;
}
Yii::getLogger()->attachEventHandler('onFlush',array($this,'collectLogs'));
Yii::app()->attachEventHandler('onEndRequest',array($this,'processLogs'));
}
イベント登録してますが、ここでやっていることは
CLoggerのonflushに対してCLogRouterのcollectLogsを登録
CApplicationのonEndRequestに対してCLogRouterのprocessLogsを登録
です。
ここで登録したハンドラは2つともメモリ上に記録したログを実際に出力するために
使用されます。
設定部分は確認できましたので、
2. メモリ上に記録
を見てみます。
ログの記録用メソッドは以下の2通りです。
<?php
Yii::log($message, $level, $category);
Yii::trace($message, $category);
違いはtraceの方のコードを見ると、わかります。
<?php
public static function trace($msg,$category='application')
{
if(YII_DEBUG)
self::log($msg,CLogger::LEVEL_TRACE,$category);
}
アプリケーションがデバックモードで
稼働している時のみ記録されるようになっていますね。
検証・デバッグ時はtraceにして、
本番に仕込む時はlogというような使い分けをすればよさそうです。
ここで記録したデータはYiiBaseのlogから
CLoggerに渡され、以下の部分でメモリ上に保存されます。
<?php
public function log($message,$level='info',$category='application')
{
$this->_logs[]=array($message,$level,$category,microtime(true));
$this->_logCount++;
if($this->autoFlush>0 && $this->_logCount>=$this->autoFlush && !$this->_processing)
{
$this->_processing=true;
$this->flush($this->autoDump);
$this->_processing=false;
}
}
3. 記録されたログを経路ごとにフィルタを掛けて出力
ここでは1で設定したイベントが実行されるところからです。
<?php
public function collectLogs($event)
{
$logger=Yii::getLogger();
$dumpLogs=isset($event->params['dumpLogs']) && $event->params['dumpLogs'];
foreach($this->_routes as $route)
{
if($route->enabled)
$route->collectLogs($logger,$dumpLogs);
}
}
public function processLogs($event)
{
$logger=Yii::getLogger();
foreach($this->_routes as $route)
{
if($route->enabled)
$route->collectLogs($logger,true);
}
}
各経路を定義したCLogRouteのcollectLogsに渡すパラメータが違うだけでほぼ同じですね。
<?php
public function collectLogs($logger, $processLogs=false)
{
$logs=$logger->getLogs($this->levels,$this->categories);
$this->logs=empty($this->logs) ? $logs : array_merge($this->logs,$logs);
if($processLogs && !empty($this->logs))
{
if($this->filter!==null)
Yii::createComponent($this->filter)->filter($this->logs);
if($this->logs!==array())
$this->processLogs($this->logs);
$this->logs=array();
}
}
collectLogsでフィルタを適用し、設定通りのデータが
$this->logs
に入ったところでprocessLogsを読んでます。
CLogRoute.php自体のprocessLogsはAbstractなので、
出力したい先によってCLogRoute.phpを継承したクラスを用意すればいいですね。
Yiiが標準で用意しているのは以下のとおり
- CDbLogRoute: データベースに保存する
- CEmailLogRoute: メールアドレスに送信する
- CFileLogRoute: ファイルとして出力する
- CWebLogRoute: htmlとして出力する
- CProfileLogRoute: プロファイルメッセージをウェブページ最後に表示する
ファイルを例に取るとこんなかんじ
<?php
protected function processLogs($logs)
{
$logFile=$this->getLogPath().DIRECTORY_SEPARATOR.$this->getLogFile();
if(@filesize($logFile)>$this->getMaxFileSize()*1024)
$this->rotateFiles();
$fp=@fopen($logFile,'a');
@flock($fp,LOCK_EX);
foreach($logs as $log)
@fwrite($fp,$this->formatLogMessage($log[0],$log[1],$log[2],$log[3]));
@flock($fp,LOCK_UN);
@fclose($fp);
}
ファイルに出力するならローテートとかいるよなー
とおもったらなんだかやってくれそうな雰囲気ですね(笑)
ところどころハショッてますが、そんなこんなでツアー終了です!
まだまだYii初心者なので
突っ込みどころあれば教えていただければと思います!!