Yii源码阅读笔记 - 日志组件
2015-03-09 一
By youngsterxyf
使用
Yii框架为开发者提供两个静态方法进行日志记录:
Yii::log($message, $level, $category); Yii::trace($message, $category);
两者的区别在于后者依赖于应用开启调试模式,即定义常量YII_DEBUG:
defined('YII_DEBUG') or define('YII_DEBUG', true);
Yii::log方法的调用需要指定message的level和category。category是格式为“xxx.yyy.zzz”的路径别名字符串,比如日志是在yii/framework/web/CController类中记录的,那么category为“system.web.CController”。level应为以下几种之一:
- trace:Yii::trace方法即是使用的这个level。用于跟踪执行流
- info:记录通用信息日志
- profile:用于性能分析
- warning:用于记录警告日志
- error:用于记录重大错误日志
要想日志真的输出到文件、邮件、web页面等地方,还得为应用添加如下配置:
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', ), ), ), ), )
注册使用名为log的组件,组件对应的类为CLogRouter(见类文件yii/framework/logging/CLogRouter.php),并且需要为组件提供参数routes, 从目录yii/framework/logging可以看到可使用的日志输出目标路由有:CDbLogRoute
(将日志记录到数据库中)、CEmailLogRoute
(将日志发送到邮箱)、CFileLogRoute
(记录到文件中)、CWebLogRoute
(将日志显示在对应的网页中)、CProfileLogRoute
,其中CProfileLogRoute直接继承自CWebLogRoute,其他路由类都直接继承自CLogRoute类。
至于为什么需要对log组件进行preload,即预先实例化,后边再说。
分析
先来看看Yii::log和Yii::trace的实现:
/** * Writes a trace message. * This method will only log a message when the application is in debug mode. * @param string $msg message to be logged * @param string $category category of the message * @see log */ public static function trace($msg,$category='application') { // 得先定义常量YII_DEBUG为true if(YII_DEBUG) // CLogger::LEVEL_TRACE self::log($msg,CLogger::LEVEL_TRACE,$category); } /** * Logs a message. * Messages logged by this method may be retrieved via {@link CLogger::getLogs} * and may be recorded in different media, such as file, email, database, using * {@link CLogRouter}. * @param string $msg message to be logged * @param string $level level of the message (e.g. 'trace', 'warning', 'error'). It is case-insensitive. * @param string $category category of the message (e.g. 'system.web'). It is case-insensitive. */ public static function log($msg,$level=CLogger::LEVEL_INFO,$category='application') { if(self::$_logger===null) self::$_logger=new CLogger; // 注意这里的常量YII_TRACE_LEVEL,如果想日志中含有对应文件名、对应行,那么应该定义YII_TRACE_LEVEL大于0, // 这个常量的意思应该是日志追踪的深度 if(YII_DEBUG && YII_TRACE_LEVEL>0 && $level!==CLogger::LEVEL_PROFILE) { $traces=debug_backtrace(); $count=0; foreach($traces as $trace) { if(isset($trace['file'],$trace['line']) && strpos($trace['file'],YII_PATH)!==0) { $msg.="\nin ".$trace['file'].' ('.$trace['line'].')'; if(++$count>=YII_TRACE_LEVEL) break; } } } // 调用的是CLogger类的log方法 self::$_logger->log($msg,$level,$category); }
CLogger类的log方法实现如下所示:
/** * Logs a message. * Messages logged by this method may be retrieved back via {@link getLogs}. * @param string $message message to be logged * @param string $level level of the message (e.g. 'Trace', 'Warning', 'Error'). It is case-insensitive. * @param string $category category of the message (e.g. 'system.web'). It is case-insensitive. * @see getLogs */ public function log($message,$level='info',$category='application') { $this->_logs[]=array($message,$level,$category,microtime(true)); $this->_logCount++; // autoFlush的默认值为10000,即只有当日志的条数达到10000(或请求处理结束时),才会flush到输出,否则一直将日志存放在内存中 if($this->autoFlush>0 && $this->_logCount>=$this->autoFlush && !$this->_processing) { $this->_processing=true; // autoDump默认为false $this->flush($this->autoDump); $this->_processing=false; } }
CLogger类的flush方法实现如下所示:
/** * Removes all recorded messages from the memory. * This method will raise an {@link onFlush} event. * The attached event handlers can process the log messages before they are removed. * @param boolean $dumpLogs whether to process the logs immediately as they are passed to log route * @since 1.1.0 */ public function flush($dumpLogs=false) { // 事件对象中会带有当前的CLogger对象,作为事件的发送者 // 但在类CLogRouter的方法collectLogs和processLogs中并没有使用这个CLogger对象, // 而是通过Yii::getLogger()来得到同一个CLogger对象,为什么不直接使用呢? $this->onFlush(new CEvent($this, array('dumpLogs'=>$dumpLogs))); // 清空重置 $this->_logs=array(); $this->_logCount=0; } /** * Raises an <code>onFlush</code> event. * @param CEvent $event the event parameter * @since 1.1.0 */ public function onFlush($event) { // 抛出onFlush事件 // raiseEvent方法定义在CComponent类中,CLogger类继承自CComponent类 $this->raiseEvent('onFlush', $event); }
CComponent类的raiseEvent方法实现如下所示:
/** * Raises an event. * This method represents the happening of an event. It invokes * all attached handlers for the event. * @param string $name the event name * @param CEvent $event the event parameter * @throws CException if the event is undefined or an event handler is invalid. */ public function raiseEvent($name,$event) { $name=strtolower($name); if(isset($this->_e[$name])) { // 逐个执行与事件$name绑定的$handler foreach($this->_e[$name] as $handler) { if(is_string($handler)) // 会将事件对象(CEvent对象)传到$handler中 call_user_func($handler,$event); elseif(is_callable($handler,true)) { if(is_array($handler)) { // an array: 0 - object, 1 - method name list($object,$method)=$handler; if(is_string($object)) // static method call call_user_func($handler,$event); elseif(method_exists($object,$method)) $object->$method($event); else throw new CException(Yii::t('yii','Event "{class}.{event}" is attached with an invalid handler "{handler}".', array('{class}'=>get_class($this), '{event}'=>$name, '{handler}'=>$handler[1]))); } else // PHP 5.3: anonymous function call_user_func($handler,$event); } else throw new CException(Yii::t('yii','Event "{class}.{event}" is attached with an invalid handler "{handler}".', array('{class}'=>get_class($this), '{event}'=>$name, '{handler}'=>gettype($handler)))); // stop further handling if param.handled is set true if(($event instanceof CEvent) && $event->handled) return; } } elseif(YII_DEBUG && !$this->hasEvent($name)) throw new CException(Yii::t('yii','Event "{class}.{event}" is not defined.', array('{class}'=>get_class($this), '{event}'=>$name))); }
看到这里,你可能会很奇怪怎么都没看到真正记录日志的代码呢?在与事件绑定的各个$handler里呢,这也是log组件的routes参数配置成数组的原因,$handle就是该数组中对应类的实例化对象的日志记录方法。
那么这些$handle是在哪里绑定事件的呢?
既然log组件对应的是CLogRouter类,那么来看看其实现。
类CLogRouter继承自类CApplicationComponent。根据Yii源码阅读笔记 - 组件集成一文,可知组件初始化时实例化对象会调用init方法来完成一些初始化操作,类CLogRouter的init方法实现如下所示:
/** * Initializes this application component. * This method is required by the IApplicationComponent interface. */ public function init() { parent::init(); // 实例化配置的routes参数中指定的日志路由类列表 foreach($this->_routes as $name=>$route) { $route=Yii::createComponent($route); $route->init(); $this->_routes[$name]=$route; } // 将当前对象的collectLogs方法绑定到事件onFlush Yii::getLogger()->attachEventHandler('onFlush',array($this,'collectLogs')); // 将当前对象的processLogs方法绑定到事件onEndRequest(表示请求处理结束?) Yii::app()->attachEventHandler('onEndRequest',array($this,'processLogs')); }
而类CLogRouter的方法collectLogs和processLogs实现如下所示:
/** * Collects log messages from a logger. * This method is an event handler to the {@link CLogger::onFlush} event. * @param CEvent $event event parameter */ public function collectLogs($event) { $logger=Yii::getLogger(); $dumpLogs=isset($event->params['dumpLogs']) && $event->params['dumpLogs']; // 遍历所有日志路由对象,执行其方法collectLogs foreach($this->_routes as $route) { // 属性enabled默认值为true if($route->enabled) // $dumpLogs默认为false,由事件对象传过来 $route->collectLogs($logger,$dumpLogs); } } /** * Collects and processes log messages from a logger. * This method is an event handler to the {@link CApplication::onEndRequest} event. * @param CEvent $event event parameter * @since 1.1.0 */ public function processLogs($event) { $logger=Yii::getLogger(); // 遍历所有日志路由对象,执行其方法collectLogs foreach($this->_routes as $route) { if($route->enabled) // 注意这里参数$dumpLogs参数值始终为true $route->collectLogs($logger,true); } }
以类CWebLogRoute为例来看看日志路由类的方法collectLogs,该方法定义于类CLogRoute中,实现如下所示:
/** * Retrieves filtered log messages from logger for further processing. * @param CLogger $logger logger instance * @param boolean $processLogs whether to process the logs after they are collected from the logger */ // 事件onFlush触发时,传入的$processLogs参数值默认为false,事件onEndRequest触发时为true public function collectLogs($logger, $processLogs=false) { // 从类CLogger实例化对象的_logs属性值中过滤得到目标日志记录 $logs=$logger->getLogs($this->levels,$this->categories,$this->except); $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()) // 调用实际route的processLogs方法 $this->processLogs($this->logs); $this->logs=array(); } }
而类CWebLogRoute的processLogs方法实现如下所示:
/** * Displays the log messages. * @param array $logs list of log messages */ public function processLogs($logs) { $this->render('log',$logs); } /** * Renders the view. * @param string $view the view name (file name without extension). The file is assumed to be located under framework/data/views. * @param array $data data to be passed to the view */ protected function render($view,$data) { $app=Yii::app(); $isAjax=$app->getRequest()->getIsAjaxRequest(); $isFlash=$app->getRequest()->getIsFlashRequest(); // 用firebug来显示日志信息的话? if($this->showInFireBug) { // do not output anything for ajax and/or flash requests if needed if($isAjax && $this->ignoreAjaxInFireBug || $isFlash && $this->ignoreFlashInFireBug) return; $view.='-firebug'; if(($userAgent=$app->getRequest()->getUserAgent())!==null && preg_match('/msie [5-9]/i',$userAgent)) { echo '<script type="text/javascript">'; echo file_get_contents(dirname(__FILE__).'/../vendors/console-normalizer/normalizeconsole.min.js'); echo "</script>\n"; } } elseif(!($app instanceof CWebApplication) || $isAjax || $isFlash) return; // 渲染yii/framework/views/log.php(log-firebug.php),将结果放在实际请求页面内容的下面 $viewFile=YII_PATH.DIRECTORY_SEPARATOR.'views'.DIRECTORY_SEPARATOR.$view.'.php'; include($app->findLocalizedFile($viewFile,'en')); }
从上述代码可以知道日志路由类是将日志信息按照一定格式显示在实际请求页面内容的下方。
再来看看方法init中调用的两个attachEventHandler,它们的定义是同一个,定义在类CComponent中(类CLogger直接继承自类CComponent),实现如下所示:
public function attachEventHandler($name,$handler) { // 将事件处理器$handler加到处理器列表中,在事件发生时会逐个处理器触发执行 $this->getEventHandlers($name)->add($handler); }
其中方法EventHandlers的实现如下所示:
/** * Returns the list of attached event handlers for an event. * @param string $name the event name * @return CList list of attached event handlers for the event * @throws CException if the event is not defined */ public function getEventHandlers($name) { if($this->hasEvent($name)) { $name=strtolower($name); if(!isset($this->_e[$name])) $this->_e[$name]=new CList; return $this->_e[$name]; } else throw new CException(Yii::t('yii','Event "{class}.{event}" is not defined.', array('{class}'=>get_class($this), '{event}'=>$name))); }
从上述分析可以知道所谓事件系统,其实就是将处理函数/对象方法放到与事件ID对应的一个列表中,然后在事件触发时,逐个调用执行这些函数/对象方法。
Yii框架基于事件系统,可以做到同时将日志信息写到多个目标输出中。
回到之前提到的那个问题:为什么需要对log组件进行preload?
这是因为:对于日志组件的使用并不是通过Yii::app()->db
这种形式来调用的(如果基于这种形式,那么就可以在首次调用时再做组件实例化,以实现组件延迟加载),而是通过触发事件来间接调用,但这就需要在事件触发之间将相关的处理函数/对象方法绑定到事件,这个绑定操作又是在日志log组件的init方法中执行的,一般组件类实例化时才会调用其init方法,所以需要对log组件进行预加载。
参考
- Yii - topics - Logging
转载于:https://www.cnblogs.com/sunscheung/p/4864179.html
Yii源码阅读笔记 - 日志组件相关推荐
- CI框架源码阅读笔记4 引导文件CodeIgniter.php
到了这里,终于进入CI框架的核心了.既然是"引导"文件,那么就是对用户的请求.参数等做相应的导向,让用户请求和数据流按照正确的线路各就各位.例如,用户的请求url: http:// ...
- libreCAD源码阅读笔记1
libreCAD源码阅读笔记1 一 前言: 正如官网(https://www.librecad.org)所说,libreCAD是一个开源的CAD制图软件,可以运行在Windows.Apple.Linu ...
- Transformers包tokenizer.encode()方法源码阅读笔记
Transformers包tokenizer.encode()方法源码阅读笔记_天才小呵呵的博客-CSDN博客_tokenizer.encode
- 源码阅读笔记 BiLSTM+CRF做NER任务 流程图
源码阅读笔记 BiLSTM+CRF做NER任务(二) 源码地址:https://github.com/ZhixiuYe/NER-pytorch 本篇正式进入源码的阅读,按照流程顺序,一一解剖. 一.流 ...
- 代码分析:NASM源码阅读笔记
NASM源码阅读笔记 NASM(Netwide Assembler)的使用文档和代码间的注释相当齐全,这给阅读源码 提供了很大的方便.按作者的说法,这是一个模块化的,可重用的x86汇编器, 而且能够被 ...
- AQS源码阅读笔记(一)
AQS源码阅读笔记 先看下这个类张非常重要的一个静态内部类Node.如下: static final class Node {//表示当前节点以共享模式等待锁static final Node SHA ...
- 12 哈希表相关类——Live555源码阅读(一)基本组件类
12 哈希表相关类--Live555源码阅读(一)基本组件类 这是Live555源码阅读的第一部分,包括了时间类,延时队列类,处理程序描述类,哈希表类这四个大类. 本文由乌合之众 lym瞎编,欢迎转载 ...
- 【Flink】Flink 源码阅读笔记(20)- Flink 基于 Mailbox 的线程模型
1.概述 转载:Flink 源码阅读笔记(20)- Flink 基于 Mailbox 的线程模型 相似文章:[Flink]Flink 基于 MailBox 实现的 StreamTask 线程模型 Fl ...
- 【Flink】Flink 源码阅读笔记(18)- Flink SQL 中的流和动态表
1.概述 转载:Flink 源码阅读笔记(18)- Flink SQL 中的流和动态表
最新文章
- php中怎么使用table,thinkphp中的table方法怎样使用?
- python基础教程第二版答案-《Python基础教程》(第2版修订版)中要注意的地方...
- 在Windows XP里,设置USB只读
- 搭建Keras,TensorFlow运行环境
- 机器学习实战-PCA算法-26
- LOAD DATA INFILE句法
- c#.net多线程编程教学(2):Thread类
- 网站SEO优化过程中的几个注意事项
- ASP.NET MVC呼叫WCF Service的方法
- 沙盘模拟软件_3D电子心理沙盘系统
- OO’s Sequence
- Jmeter查看结果树
- [js高手之路]设计模式系列课程-委托模式实战微博发布功能
- 使用PowerPoint
- AngularJS初识
- Graph Neural Network(GAE,GVAE,ARGA)
- js 入门基础(一)
- 电气图纸关于号码管的命名规则
- 用PS制作燃烧的火焰人物
- 声音侦测灵敏度、阈值、分贝的关系
热门文章
- 读书笔记:交易型系统设计的一般原则
- javascript的性能优化tips
- Loadrunner中socket协议中的三个关联函数
- Cocos2d-x v3.1 坐标系统(五)
- ASP.NET Hashtable输出JSON格式数据 - 贵源网络 - 博客园
- 最新Butterknife集成 全部方法(完整版)
- Android 手势解锁 GestureLock的使用和简单修复
- listview刷新,延迟加载,用单行刷新取代notifyDataSetChanged
- android SharedPreferences保存list数据
- vs项目中头文件(.h)静态库(.lib)和 动态库(.dll )的路径和配置