Yii ログ出力ツアー

Yii Frameworkのログ出力を公式ガイドで見ると、

何やら便利なのは分かったけど結局出力してるのどこ？
という感じだったので、
『ログはなぜ出力されるのか』的な感じで追ってみました。 
コードリーディングメモ的な記事になってます。

公式ガイド
http://www.yiiframework.com/doc/guide/1.1/ja/topics.logging

Yiiでは、ざっくり分けると以下の様な形でログを扱うようです。

1. ログの経路・フィルタを設定
2. メモリ上に記録
3. 記録されたログを経路ごとにフィルタを掛けて出力

出力先には、DBや,ファイル、メール、webページの最後などがあります。

## １. ログの経路・フィルタを設定

ツアーの最初はリクエストを受け取ったところからです。

公式ガイドにあるように、以下の様な設定でCLogRouterをプリロードしておくと、
ここのinitで設定ファイルに記載した経路（CLogRouteを継承したRouteクラス）
を設定してくれます。

```php:protected/config/main.php
<?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:CLogRouter.php
<?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を登録
です。

ここで登録したハンドラは２つともメモリ上に記録したログを実際に出力するために
使用されます。
 
設定部分は確認できましたので、
## ２. メモリ上に記録
を見てみます。
 
ログの記録用メソッドは以下の２通りです。

```
<?php

Yii::log($message, $level, $category);
Yii::trace($message, $category);
```
違いはtraceの方のコードを見ると、わかります。

```YiiBase.php
<?php

public static function trace($msg,$category='application') 
{ 
    if(YII_DEBUG) 
        self::log($msg,CLogger::LEVEL_TRACE,$category); 
}
```

アプリケーションがデバックモードで
稼働している時のみ記録されるようになっていますね。
検証・デバッグ時はtraceにして、
本番に仕込む時はlogというような使い分けをすればよさそうです。

ここで記録したデータはYiiBaseのlogから
CLoggerに渡され、以下の部分でメモリ上に保存されます。

```php:CLogger.php
<?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; 
    } 
}

```


## ３. 記録されたログを経路ごとにフィルタを掛けて出力
ここでは１で設定したイベントが実行されるところからです。

```php:CLogRouter.php
<?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:CLogRoute.php
<?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:CFileLogRoute.php

<?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初心者なので
突っ込みどころあれば教えていただければと思います！！