当前位置:首页 > CMS教程 > 其它CMS > 列表

全面解读PHP的Yii框架中的日志功能

发布:smiling 来源: PHP粉丝网  添加日期:2021-07-15 11:30:11 浏览: 评论:0 

这篇文章主要介绍了PHP的Yii框架中的日志,对日志的分析是日常网站维护中的基础,Yii提供了较为强大的日志功能,需要的朋友可以参考下。

Yii页面级日志开启

在 Main.php中 log段添加、

下面显示页面日志 array( 'class'=>'CWebLogRoute', 'levels'=>'trace', //级别为trace 'categories'=>'system.db.*' //只显示关于数据库信息,包括数据库连接,数据库执行语句 ),

完整如下:

  1. 'log'=>array
  2.     'class'=>'CLogRouter'
  3.     'routes'=>array
  4.       array
  5.         'class'=>'CFileLogRoute'
  6.         'levels'=>'error, warning'
  7.  
  8.       ), 
  9.               // 下面显示页面日志  
  10.               array(  
  11.                'class'=>'CWebLogRoute',  
  12.                'levels'=>'trace',  //级别为trace  
  13.                'categories'=>'system.db.*' //只显示关于数据库信息,包括数据库连接,数据库执行语句  
  14.               ),  
  15.       // uncomment the following to show log messages on web pages 
  16.       /* 
  17.       array( 
  18.         'class'=>'CWebLogRoute', 
  19.       ), 
  20.       */ 
  21.     ), 
  22.   ), 

扩展 Yii2 自带的日志组件

  1. <?php 
  2.  
  3. /** 
  4.  * author   : forecho <caizhenghai@gmail.com> 
  5.  * createTime : 2015/12/22 18:13 
  6.  * description: 
  7.  */ 
  8. namespace common\components; 
  9.  
  10. use Yii; 
  11. use yii\helpers\FileHelper; 
  12.  
  13. class FileTarget extends \yii\log\FileTarget 
  14.   /** 
  15.    * @var bool 是否启用日志前缀 (@app/runtime/logs/error/20151223_app.log) 
  16.    */ 
  17.   public $enableDatePrefix = false; 
  18.  
  19.   /** 
  20.    * @var bool 启用日志等级目录 
  21.    */ 
  22.   public $enableCategoryDir = false; 
  23.  
  24.   private $_logFilePath = ''
  25.  
  26.   public function init() 
  27.   { 
  28.     if ($this->logFile === null) { 
  29.       $this->logFile = Yii::$app->getRuntimePath() . '/logs/app.log'
  30.     } else { 
  31.       $this->logFile = Yii::getAlias($this->logFile); 
  32.     } 
  33.     $this->_logFilePath = dirname($this->logFile); 
  34.  
  35.     // 启用日志前缀 
  36.     if ($this->enableDatePrefix) { 
  37.       $filename = basename($this->logFile); 
  38.       $this->logFile = $this->_logFilePath . '/' . date('Ymd') . '_' . $filename
  39.     } 
  40.  
  41.     if (!is_dir($this->_logFilePath)) { 
  42.       FileHelper::createDirectory($this->_logFilePath, $this->dirMode, true); 
  43.     } 
  44.  
  45.     if ($this->maxLogFiles < 1) { 
  46.       $this->maxLogFiles = 1; 
  47.     } 
  48.     if ($this->maxFileSize < 1) { 
  49.       $this->maxFileSize = 1; 
  50.     } 
  51.  
  52.   } 

在配置文件中这样使用:

  1. 'components' => [ 
  2.   'log' => [ 
  3.     'traceLevel' => YII_DEBUG ? 3 : 0, 
  4.     'targets' => [ 
  5.       /** 
  6.        * 错误级别日志:当某些需要立马解决的致命问题发生的时候,调用此方法记录相关信息。 
  7.        * 使用方法:Yii::error() 
  8.        */ 
  9.       [ 
  10.         'class' => 'common\components\FileTarget'
  11.         // 日志等级 
  12.         'levels' => ['error'], 
  13.         // 被收集记录的额外数据 
  14.         'logVars' => ['_GET''_POST''_FILES''_COOKIE''_SESSION','_SERVER'], 
  15.         // 指定日志保存的文件名 
  16.         'logFile' => '@app/runtime/logs/error/app.log'
  17.         // 是否开启日志 (@app/runtime/logs/error/20151223_app.log) 
  18.         'enableDatePrefix' => true, 
  19.         'maxFileSize' => 1024 * 1, 
  20.         'maxLogFiles' => 100, 
  21.       ], 
  22.       /** 
  23.        * 警告级别日志:当某些期望之外的事情发生的时候,使用该方法。 
  24.        * 使用方法:Yii::warning() 
  25.        */ 
  26.       [ 
  27.         'class' => 'common\components\FileTarget'
  28.         // 日志等级 
  29.         'levels' => ['warning'], 
  30.         // 被收集记录的额外数据 
  31.         'logVars' => ['_GET''_POST''_FILES''_COOKIE''_SESSION','_SERVER'], 
  32.         // 指定日志保存的文件名 
  33.         'logFile' => '@app/runtime/logs/warning/app.log'
  34.         // 是否开启日志 (@app/runtime/logs/warning/20151223_app.log) 
  35.         'enableDatePrefix' => true, 
  36.         'maxFileSize' => 1024 * 1, 
  37.         'maxLogFiles' => 100, 
  38.       ], 
  39.       /** 
  40.        * info 级别日志:在某些位置记录一些比较有用的信息的时候使用。 
  41.        * 使用方法:Yii::info() 
  42.        */ 
  43.       [ 
  44.         'class' => 'common\components\FileTarget'
  45.         // 日志等级 
  46.         'levels' => ['info'], 
  47.         // 被收集记录的额外数据 
  48.         'logVars' => ['_GET''_POST''_FILES''_COOKIE''_SESSION','_SERVER'], 
  49.         // 指定日志保存的文件名 
  50.         'logFile' => '@app/runtime/logs/info/app.log'
  51.         // 是否开启日志 (@app/runtime/logs/info/20151223_app.log) 
  52.         'enableDatePrefix' => true, 
  53.         'maxFileSize' => 1024 * 1, 
  54.         'maxLogFiles' => 100, 
  55.       ], 
  56.       /** 
  57.        * trace 级别日志:记录关于某段代码运行的相关消息。主要是用于开发环境。 
  58.        * 使用方法:Yii::trace() 
  59.        */ 
  60.       [ 
  61.         'class' => 'common\components\FileTarget'
  62.         // 日志等级 
  63.         'levels' => ['trace'], 
  64.         // 被收集记录的额外数据 
  65.         'logVars' => ['_GET''_POST''_FILES''_COOKIE''_SESSION','_SERVER'], 
  66.         // 指定日志保存的文件名 
  67.         'logFile' => '@app/runtime/logs/trace/app.log'
  68.         // 是否开启日志 (@app/runtime/logs/trace/20151223_app.log) 
  69.         'enableDatePrefix' => true, 
  70.         'maxFileSize' => 1024 * 1, 
  71.         'maxLogFiles' => 100, 
  72.       ], 
  73.     ], 
  74.   ], 
  75. ], 

yii日志的逻辑

Yii使用层次的日志处理机制,即日志的收集与日志最终的处理(如显示、保存到文件、保存到数据数)是分离的。

日志信息的收集由CLogger(日志记录器)完成,而日志信息的分发处理,则在CLogRouter的调度(称为日志路由管理器)下,分发给处理对象(如CFileLogRoute以及logging目录下继承自CLogRoute的类, 称为日志处理器),经过反复阅读其源代码,我更是为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()中当日志消息量过多时,及时将日志刷新写入文件。代码如下:

  1. /** 
  2.  * Initializes this application component. 
  3.  * This method is required by the IApplicationComponent interface.   
  4. */ 
  5.  public function init(){  
  6.   parent::init();  
  7.   foreach($this->_routes as $name=>$route) {  
  8.     $route=Yii::createComponent($route);   
  9.     $route->init();   
  10.     $this->_routes[$name]=$route;  
  11.   }  
  12.   Yii::getLogger()->attachEventHandler('onFlush',array($this,'collectLogs'));  
  13.   Yii::app()->attachEventHandler('onEndRequest',array($this,'processLogs'));} 

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

  1. if($this->hasEventHandler('onEndRequest')) { 
  2. $this->onEndRequest(new CEvent($this)); 

到这里我们可以理解CLogger (Yii::$_logger)仅仅是将日志进行收集(记录到内容结构之中),然后在程序结束时,由$app对象调用CLogRouter的processLogs进行日志的处理。Yii支持日志多道路由,比如:同一份日志即可写入至文件,又可显示到页面上,甚至同时以电子邮件发送,更甚至同时记录到数据库中,这是由配置文件中的log:routes配置实现的,为log:routes配置多个元素,实现多个路由分发。日志信息的过滤,记录均是由最终的日志处理器处理。

日志处理器要完成的任务主要包含以下几点: 从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中的配置:

  1. 'preload'=>array('log'), 
  2. components => array
  3.        'log'=>array
  4.          'class'=>'CLogRouter'
  5.          'routes'=>array
  6.           array
  7.             'class'=>'CFileLogRoute'
  8.             'levels'=>'error, warning,trace'
  9.           ), 
  10.          ) 
  11.         ) 
  12.        ) 

定义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事件处理句柄:

  1. if($this->hasEventHandler('onEndRequest')) { 
  2.  $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/目录下定义日志处理器类myEmailLogRoute,并让其继承自CEmailLogRoute,最主要的目的是重写CEmailLogRoute::sendEmail()方法  ,其中,SMTP的处理细节请自行完善(本文的重点是放在如何处理日志上,而不是发送邮件上)。

接下来,我们就可以定义日志路由处理,编辑protected/config/main.php, 在log组件的routes组件添加新的路由配置:

  1. 'log'=>array
  2. 'class'=>'CLogRouter'
  3. 'routes'=>array
  4. array
  5. 'class'=>'CFileLogRoute'
  6. 'levels'=>'error, warning,trace'
  7. ), 
  8. array
  9. 'class' => 'myEmailLogRoute'
  10. 'levels' => 'error', #所有异常的错误级别均为error,  
  11. 'categories' => 'exception.CDbException', #数据库产生错误时,均会产生CDbException异常。 
  12. 'host' => 'mail.163.com'
  13. 'port' => 25, 
  14. 'user' => 'jeff_yu'
  15. 'password' => 'you password'
  16. 'timeout' => 30, 
  17. 'emails' => 'jeff_yu@gmail.com', #日志接收人。 
  18. 'sentFrom' => 'jeff_yu@gmail.com'
  19. ), 

经过以上处理,即可使之实现我们的目的,当然你可以根据自己的需要进一步扩展之。

Tags: Yii框架 PHP日志功能

分享到: