全面解讀PHP的Yii架構中的日誌功能_php技巧

來源:互聯網
上載者:User

Yii頁面級日誌開啟
在 Main.php中 log段添加、
下面顯示頁面日誌 array( 'class'=>'CWebLogRoute', 'levels'=>'trace', //層級為trace 'categories'=>'system.db.*' //只顯示關於資料庫資訊,包括資料庫連接,資料庫執行語句 ),
完整如下:

'log'=>array(    'class'=>'CLogRouter',    'routes'=>array(      array(        'class'=>'CFileLogRoute',        'levels'=>'error, warning',      ),              // 下面顯示頁面日誌               array(                'class'=>'CWebLogRoute',                'levels'=>'trace',  //層級為trace                'categories'=>'system.db.*' //只顯示關於資料庫資訊,包括資料庫連接,資料庫執行語句               ),       // uncomment the following to show log messages on web pages      /*      array(        'class'=>'CWebLogRoute',      ),      */    ),  ),

    
擴充 Yii2 內建的日誌組件
   

 <?php/** * author   : forecho <caizhenghai@gmail.com> * createTime : 2015/12/22 18:13 * description: */namespace common\components;use Yii;use yii\helpers\FileHelper;class FileTarget extends \yii\log\FileTarget{  /**   * @var bool 是否啟用日誌首碼 (@app/runtime/logs/error/20151223_app.log)   */  public $enableDatePrefix = false;  /**   * @var bool 啟用日誌等級目錄   */  public $enableCategoryDir = false;  private $_logFilePath = '';  public function init()  {    if ($this->logFile === null) {      $this->logFile = Yii::$app->getRuntimePath() . '/logs/app.log';    } else {      $this->logFile = Yii::getAlias($this->logFile);    }    $this->_logFilePath = dirname($this->logFile);    // 啟用日誌首碼    if ($this->enableDatePrefix) {      $filename = basename($this->logFile);      $this->logFile = $this->_logFilePath . '/' . date('Ymd') . '_' . $filename;    }    if (!is_dir($this->_logFilePath)) {      FileHelper::createDirectory($this->_logFilePath, $this->dirMode, true);    }    if ($this->maxLogFiles < 1) {      $this->maxLogFiles = 1;    }    if ($this->maxFileSize < 1) {      $this->maxFileSize = 1;    }  }}

在設定檔中這樣使用:

'components' => [  'log' => [    'traceLevel' => YII_DEBUG ? 3 : 0,    'targets' => [      /**       * 錯誤層級日誌:當某些需要立馬解決的致命問題發生的時候,調用此方法記錄相關資訊。       * 使用方法:Yii::error()       */      [        'class' => 'common\components\FileTarget',        // 日誌等級        'levels' => ['error'],        // 被收集記錄的額外資料        'logVars' => ['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION','_SERVER'],        // 指定日誌儲存的檔案名稱        'logFile' => '@app/runtime/logs/error/app.log',        // 是否開啟日誌 (@app/runtime/logs/error/20151223_app.log)        'enableDatePrefix' => true,        'maxFileSize' => 1024 * 1,        'maxLogFiles' => 100,      ],      /**       * 警告層級日誌:當某些期望之外的事情發生的時候,使用該方法。       * 使用方法:Yii::warning()       */      [        'class' => 'common\components\FileTarget',        // 日誌等級        'levels' => ['warning'],        // 被收集記錄的額外資料        'logVars' => ['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION','_SERVER'],        // 指定日誌儲存的檔案名稱        'logFile' => '@app/runtime/logs/warning/app.log',        // 是否開啟日誌 (@app/runtime/logs/warning/20151223_app.log)        'enableDatePrefix' => true,        'maxFileSize' => 1024 * 1,        'maxLogFiles' => 100,      ],      /**       * info 層級日誌:在某些位置記錄一些比較有用的資訊的時候使用。       * 使用方法:Yii::info()       */      [        'class' => 'common\components\FileTarget',        // 日誌等級        'levels' => ['info'],        // 被收集記錄的額外資料        'logVars' => ['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION','_SERVER'],        // 指定日誌儲存的檔案名稱        'logFile' => '@app/runtime/logs/info/app.log',        // 是否開啟日誌 (@app/runtime/logs/info/20151223_app.log)        'enableDatePrefix' => true,        'maxFileSize' => 1024 * 1,        'maxLogFiles' => 100,      ],      /**       * trace 層級日誌:記錄關於某段代碼啟動並執行相關訊息。主要是用於開發環境。       * 使用方法:Yii::trace()       */      [        'class' => 'common\components\FileTarget',        // 日誌等級        'levels' => ['trace'],        // 被收集記錄的額外資料        'logVars' => ['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION','_SERVER'],        // 指定日誌儲存的檔案名稱        'logFile' => '@app/runtime/logs/trace/app.log',        // 是否開啟日誌 (@app/runtime/logs/trace/20151223_app.log)        'enableDatePrefix' => true,        'maxFileSize' => 1024 * 1,        'maxLogFiles' => 100,      ],    ],  ],],

yii日誌的邏輯
Yii使用層次的Tlog機制,即日誌的收集與日誌最終的處理(如顯示、儲存到檔案、儲存到資料數)是分離的。
日誌資訊的收集由CLogger(日誌記錄器)完成,而日誌資訊的分發處理,則在CLogRouter的調度(稱為日誌路由管理器)下,分發給處理對象(如CFileLogRoute以及logging目錄下繼承自CLogRoute的類, 稱為Tlog器),經過反覆閱讀其原始碼,我更是為Yii的設計思想所折服,如此的分層處理,使得其易於靈活擴充。
而日誌資訊有層級之分,如普通的info, profile, trace, warning, error層級,可以在日誌路由中設定過慮條件,如設定CFileRoute的levels屬性,即可只處理指定層級的日誌資訊。
如在程式中調用:

Yii::log($message,CLogger::LEVEL_ERROR,$category);

對應的流程可能如下:

  • 產生CLogger執行個體
  • 如果YII_DEBUG , YII_TRACE_LEVEL都已經定義為有效值,並且記錄層級不是profile, 則產生調用回溯資訊, 並追加到日誌資訊上。
  • 調用CLogger:: log($msg,$level,$category) 收集日誌,實際上這時日誌並沒有寫入檔案,僅僅是暫存於記憶體之中。

問題:日誌是在何時被寫入檔案的?
經過反覆跟蹤,我發現在CLogRouter類的init方法中為Application對象的OnEndRequest事件綁定處理器CLogRouter::processLogs()。同時也給Yii::$_logger的onFlush事件綁定事件處理器CLogRouter::collectLogs方法,用於在Yii::log()中當日誌訊息量過多時,及時將日誌重新整理寫入檔案。代碼如下:

/** * Initializes this application component. * This method is required by the IApplicationComponent interface.  */ 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'));}

而在CApplication::run()方法中定義了:

 if($this->hasEventHandler('onEndRequest')) { $this->onEndRequest(new CEvent($this)); }

到這裡我們可以理解CLogger (Yii::$_logger)僅僅是將日誌進行收集(記錄到內容結構之中),然後在程式結束時,由$app對象調用CLogRouter的processLogs進行日誌的處理。Yii支援日誌多道路由,比如:同一份日誌即可寫入至檔案,又可顯示到頁面上,甚至同時以電子郵件發送,更甚至同時記錄到資料庫中,這是由設定檔中的log:routes配置實現的,為log:routes配置多個元素,實現多個路由分發。日誌資訊的過濾,記錄均是由最終的Tlog器處理。
Tlog器要完成的任務主要包含以下幾點: 從CLogger中取得所有日誌,並進行過濾(主要是levels, categories兩項定義log:routes:levels/categories)

先進行過濾參考CFileLogRoute::collectLogs()中的邏輯:

 $logs=$logger->getLogs($this->levels,$this->categories); //執行過濾,只得到期望資訊

日誌過濾已經完成接下來就要對日誌進行最終處理(如寫入到檔案,記錄至資料庫等)

 CFileLogRoute::processLogs($logs);

但這個函數之中,有個小bug, 只判斷日誌目錄是否可寫,沒有判斷記錄檔本身是否可寫.CFileLogRoute實現了類似Linux的日誌輪換功能(LogRoate), 並規定了記錄檔的大小,考慮得很周到,很完善! 我也要向其學習並吸收其思想!
protected/config/main.php中的配置:

'preload'=>array('log'),components => array(       'log'=>array(         'class'=>'CLogRouter',         'routes'=>array(          array(            'class'=>'CFileLogRoute',            'levels'=>'error, warning,trace',          ),         )        )       )

定義log組件需要積極式載入(執行個體化)。配置使用CLogRouter作為日誌路由管理器,並設定了其日誌路由處理器(routes屬性)及其配置屬性。而preload, log屬性的定義,均要應用到CWebApplication對象上(請參閱CApplication::__construct中的configure調用, configure從CModule繼承而來)。而在CWebApplication的建構函式中執行preloadComponents(),就建立了log對象(即CLogRouter的執行個體)。
建立並初始化一個組件時,實際上調用的是CModule::getComponent, 這個調用中使用YiiBase::createComponent建立組件對象,並再調用組件的init初始化之。
再閱讀CLogRouter::init()過程,在這裡有兩個關鍵之處,一是建立日誌路由處理器(即決定日誌的最終處理方式:寫入檔案,郵件發送等等),二是給應用程式物件綁定onEndRequest事件處理CLogRouter::processLogs()。而在CApplication::run()確實有相關代碼用於運行onEndRequest事件處理控制代碼:

 if($this->hasEventHandler('onEndRequest')) {  $this->onEndRequest(new CEvent($this)); }

也就是說,日誌的最終處理(比如寫入檔案,系統日誌,發送郵件)是發生在應用程式運行完畢之後的。Yii使用事件機制,巧妙地實現了事件與處理控制代碼的關聯。
也就是說,當應用程式運行完畢,將執行CLogRouter::processLogs,對日誌進行處理,。CLogRouter被稱之為日誌路由管理器。每個日誌路由處理器從CLooger對象中取得相應的日誌(使用過濾機制),作最終處理。
具體而言Yii的日誌系統,分為以下幾個層次:

日誌寄件者,即程式中調用Yii::log($msg, $level, $category),將日誌發送給CLogger對象
CLogger對象負責將日誌記錄暫存於記憶體之中程式運行結束後,log組件(日誌路由管理器CLogRoute)的processLogs方法被啟用執行,由其逐個調用日誌路由器,作日誌的最後處理。

更為詳細的大致過程如下:

  • CApplication::__construct()中調用preloadComponents, 這導致log組件(CLogRoute)被執行個體化,並被調用init方法初始化。
  • log組件(CLogRoute)的init方法中,其是初始化日誌路由,並給CApplication對象onEndRequest事件綁定處理流程processLogs。給CLooger組件的onFlush事件綁定處理流程collectLogs。
  • 應用程式的其它部分通過調用Yii::log()向CLogger組件發送日誌資訊,CLogger組件將日誌資訊暫存到記憶體中。
  • CApplication執行完畢(run方法中),會啟用onEndRequest事件,綁定的事件處理器processLogs被執行,日誌被寫入檔案之中。 Yii的日誌路由機制,給日誌系統擴充帶來了無限的靈活。並且其多道路由處理機制,可將同一份日誌資訊進行多種方式處理。

這裡舉出一個案例:發生error層級的資料庫錯誤時,及時給相關維護人員寄送電子郵件,並同時將這些日誌記錄到檔案之中。規劃思路,發送郵件和手機簡訊是兩個不同的功能,Yii已經帶了日誌郵件發送組件(logging/CEmailLogRoute.php),但這個組件中卻使用了php內建的mail函數,使用mail函數需要配置php.ini中的smtp主機,並且使用非驗證發送方式,這種方式在目前的實際情況下已經完全不可使用。代替地我們需要使用帶驗證功能的smtp發送方式。在protected/components/目錄下定義Tlog器類myEmailLogRoute,並讓其繼承自CEmailLogRoute,最主要的目的是重寫CEmailLogRoute::sendEmail()方法  ,其中,SMTP的處理細節請自行完善(本文的重點是放在如何處理日誌上,而不是發送郵件上)。
接下來,我們就可以定義日誌路由處理,編輯protected/config/main.php, 在log組件的routes組件添加新的路由配置:

'log'=>array('class'=>'CLogRouter','routes'=>array(array('class'=>'CFileLogRoute','levels'=>'error, warning,trace',),array('class' => 'myEmailLogRoute','levels' => 'error', #所有異常的錯誤層級均為error, 'categories' => 'exception.CDbException', #資料庫產生錯誤時,均會產生CDbException異常。'host' => 'mail.163.com','port' => 25,'user' => 'jeff_yu','password' => 'you password','timeout' => 30,'emails' => 'jeff_yu@gmail.com', #日誌接收人。'sentFrom' => 'jeff_yu@gmail.com',),

經過以上處理,即可使之實現我們的目的,當然你可以根據自己的需要進一步擴充之。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.