14
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

posted at

updated at

Organization

Yii ログ出力ツアー

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

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

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

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

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

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

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

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

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

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

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を登録
です。

ここで登録したハンドラは2つともメモリ上に記録したログを実際に出力するために
使用されます。

設定部分は確認できましたので、

2. メモリ上に記録

を見てみます。

ログの記録用メソッドは以下の2通りです。

<?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に渡され、以下の部分でメモリ上に保存されます。

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; 
    } 
}

3. 記録されたログを経路ごとにフィルタを掛けて出力

ここでは1で設定したイベントが実行されるところからです。

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に渡すパラメータが違うだけでほぼ同じですね。

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: プロファイルメッセージをウェブページ最後に表示する

ファイルを例に取るとこんなかんじ

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
14
Help us understand the problem. What are the problem?