欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-12-14 08:57:04
yii页面级日志开启 在 main.php中 log段添加、 下面显示页面日志 array( 'class'=>'cweblogroute', 'levels...

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使用层次的日志处理机制,即日志的收集与日志最终的处理(如显示、保存到文件、保存到数据数)是分离的。
日志信息的收集由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()中当日志消息量过多时,及时将日志刷新写入文件。代码如下:

/**
 * 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配置多个元素,实现多个路由分发。日志信息的过滤,记录均是由最终的日志处理器处理。
日志处理器要完成的任务主要包含以下几点: 从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/目录下定义日志处理器类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',
),

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

上一篇:

下一篇: