Slim研读笔记七乱入篇之Monolog(中)

来源:互联网 发布:windows10 node sass 编辑:程序博客网 时间:2024/05/17 06:20
上节,我们简单介绍了异常和错误的区别和处理,这节我们一起来看处理日志的神器Monolog的具体实现。
// 在容器中注册Monolog日志组件$container['logger'] = function($c) {    // 生成通道为my_logger的记录器    $logger = new \Monolog\Logger('my_logger');    // 生成一个程序处理对象    $file_handler = new \Monolog\Handler\StreamHandler('../logs/app.log');    // 将程序处理对象放入记录器的处理程序堆栈    $logger->pushHandler($file_handler);    return $logger;};
首先,传入日志名称到Logger类中,返回$logger对象,然后新建StreamHandler类对象,通过pushHandler将两个对象关联。返回$logger对象。要想了解这个过程,我们就要了解Monolog的核心概念:
每个记录器实例都是有一个通道(名称)和一个处理程序堆栈。无论何时向记录器添加记录,它都会遍历处理器堆栈。每个处理程序决定它是否需要继续处理记录,如果否,则处理到此为止(停止冒泡)。这就允许我们灵活的设置日志了。比如我们有一个StreamHandler,它在栈的最底部,它会把记录都保存到硬盘上,在它上面有一个MailHandler,它会在错误消息被记录的时候发送邮件。Handlers 都有一个$bubble属性,用来定义当某个处理程序在处理记录的时候是否阻塞处理(阻塞的话,就是这个记录到我这里就算处理完毕了,不要冒泡处理了)。在这个例子中,我们设置MailHandler的$bubble为false,意思就是说记录都会被MailHandler处理,不会冒泡到StreamHandler了。
首先,让我们一起查看Logger类。
/** * Monolog log 通道 * Monolog log channel * 它包括一个处理程序堆栈和一个处理器堆栈,并使用它们来存储添加到它的记录。 * It contains a stack of Handlers and a stack of Processors, * and uses them to store records that are added to it. * * @author Jordi Boggiano <j.boggiano@seld.be> */class Logger implements LoggerInterface{    /**     * 详细的Debug信息     * Detailed debug information     */    const DEBUG = 100;    /**     * 有趣的事件,用户登录,SQL logs     * Interesting events     *     * Examples: User logs in, SQL logs.     */    const INFO = 200;    /**     * 不寻常的事件     * Uncommon events     */    const NOTICE = 250;    /**     * 特殊事件不是错误,例如使用了弃用的API     * Exceptional occurrences that are not errors     *     * Examples: Use of deprecated APIs, poor use of an API,     * undesirable things that are not necessarily wrong.     */    const WARNING = 300;    /**     * 超时错误     * Runtime errors     */    const ERROR = 400;    /**     * 关键条件,譬如应用程序组件不可用,意外的异常。     * Critical conditions     *     * Example: Application component unavailable, unexpected exception.     */    const CRITICAL = 500;    /**     * 严肃的错误,譬如站点宕掉,数据库不能连接     * Action must be taken immediately     *     * Example: Entire website down, database unavailable, etc.     * This should trigger the SMS alerts and wake you up.     */    const ALERT = 550;    /**     * 紧急警报。     * Urgent alert.     */    const EMERGENCY = 600;    /**     * Monolog API版本     * Monolog API version     *     * This is only bumped when API breaks are done and should     * follow the major version of the library     *     * @var int     */    const API = 1;    /**     * 根据RFC 5424协议定义日志等级     * Logging levels from syslog protocol defined in RFC 5424     *     * @var array $levels Logging levels     */    protected static $levels = array(        self::DEBUG     => 'DEBUG',        self::INFO      => 'INFO',        self::NOTICE    => 'NOTICE',        self::WARNING   => 'WARNING',        self::ERROR     => 'ERROR',        self::CRITICAL  => 'CRITICAL',        self::ALERT     => 'ALERT',        self::EMERGENCY => 'EMERGENCY',    );    /**     * @var \DateTimeZone     */    protected static $timezone;    /**     * @var string     */    protected $name;    /**     * 处理程序堆栈     * The handler stack     *     * @var HandlerInterface[]     */    protected $handlers;    /**     * 处理所有日志记录的处理器     * Processors that will process all log records     *     * To process records of a single handler instead, add the processor on that specific handler     *     * @var callable[]     */    protected $processors;    /**     * @var bool     */    protected $microsecondTimestamps = true;    /**     * @param string             $name       The logging channel 记录日志通道     * @param HandlerInterface[] $handlers   Optional stack of handlers, the first one in the array is called first, etc.     * @param callable[]         $processors Optional array of processors     */    public function __construct($name, array $handlers = array(), array $processors = array())    {        // 通道        $this->name = $name;        // 处理程序堆栈        $this->handlers = $handlers;        // 处理器堆栈        $this->processors = $processors;    }    /**     * @return string     */    public function getName()    {        return $this->name;    }    /**     * 返回名称已更改的新克隆实例(复制一个当前类实例对象,更改新对象的名称)     * PS:克隆当前实例对象,这种处理方式值得我们学习哦~     * Return a new cloned instance with the name changed     *     * @return static     */    public function withName($name)    {        $new = clone $this;        $new->name = $name;        return $new;    }    /**     * 追加一个程序处理到handlers堆栈     * Pushes a handler on to the stack.     *     * @param  HandlerInterface $handler     * @return $this     */    public function pushHandler(HandlerInterface $handler)    {        // 追加一个程序处理到handlers数组开头        array_unshift($this->handlers, $handler);        return $this;    }    /**     * 从堆栈中弹出一个处理程序(从数组开头移出)     * Pops a handler from the stack     *     * @return HandlerInterface     */    public function popHandler()    {        if (!$this->handlers) {            throw new \LogicException('You tried to pop from an empty handler stack.');        }        return array_shift($this->handlers);    }    /**     * 设置处理程序,替换所有现有的。     * Set handlers, replacing all existing ones.     *     * If a map is passed, keys will be ignored.     *     * @param  HandlerInterface[] $handlers     * @return $this     */    public function setHandlers(array $handlers)    {        $this->handlers = array();        foreach (array_reverse($handlers) as $handler) {            $this->pushHandler($handler);        }        return $this;    }    /**     * 返回所有处理程序     * @return HandlerInterface[]     */    public function getHandlers()    {        return $this->handlers;    }    /**     * 将处理器添加到堆栈。     * 可以观察到设置的处理程序堆栈和处理器堆栈均是以数组模拟堆栈这种数据结构的     * @param  callable $callback     * @return $this     */    public function pushProcessor($callback)    {        if (!is_callable($callback)) {            throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given');        }        array_unshift($this->processors, $callback);        return $this;    }    /**     * 移除堆栈顶部的处理器。     * Removes the processor on top of the stack and returns it.     *     * @return callable     */    public function popProcessor()    {        if (!$this->processors) {            throw new \LogicException('You tried to pop from an empty processor stack.');        }        return array_shift($this->processors);    }    /**     * @return callable[]     */    public function getProcessors()    {        return $this->processors;    }    /**     * Control the use of microsecond resolution timestamps in the 'datetime'     * member of new records.     *     * Generating microsecond resolution timestamps by calling     * microtime(true), formatting the result via sprintf() and then parsing     * the resulting string via \DateTime::createFromFormat() can incur     * a measurable runtime overhead vs simple usage of DateTime to capture     * a second resolution timestamp in systems which generate a large number     * of log events.     *     * @param bool $micro True to use microtime() to create timestamps     */    public function useMicrosecondTimestamps($micro)    {        $this->microsecondTimestamps = (bool) $micro;    }    /**     * 添加日志记录。     * Adds a log record.     *     * @param  int     $level   The logging level     * @param  string  $message The log message     * @param  array   $context The log context     * @return Boolean Whether the record has been processed     */    public function addRecord($level, $message, array $context = array())    {        // 若没有处理程序,则添加一个默认的处理程序。        // php://stderr是标准错误,默认情况下会发送到用户终端        if (!$this->handlers) {            $this->pushHandler(new StreamHandler('php://stderr', static::DEBUG));        }        // 根据日志等级获取等级名称        $levelName = static::getLevelName($level);        // check if any handler will handle this message so we can return early and save cycles        // 检查是否有处理程序将处理此消息,以便我们可以提前返回并保存周期        $handlerKey = null;        // 将数组内部指针指向第一个单元        reset($this->handlers);        // 返回数组中的当前单元        while ($handler = current($this->handlers)) {            // 检查是否给予的记录是否可以通过这个处理程序进行处理            if ($handler->isHandling(array('level' => $level))) {                // 若可以,则记录该处理程序的key值                $handlerKey = key($this->handlers);                break;            }            next($this->handlers);        }        if (null === $handlerKey) {            return false;        }        if (!static::$timezone) {            // static::$timezone = new \DateTimeZone(date_default_timezone_get() ?: 'UTC');            static::$timezone = new \DateTimeZone('UTC');        }        // php7.1+ always has microseconds enabled, so we do not need this hack        // php7.1之后增加了微妙,所以你可以增加这个扩展。        if ($this->microsecondTimestamps && PHP_VERSION_ID < 70100) {            $ts = \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)), static::$timezone);        } else {            $ts = new \DateTime(null, static::$timezone);        }        $ts->setTimezone(static::$timezone);        // 这里产生一条日志记录        // 类似[2017-12-14 06:47:59] my_logger.INFO: 发生了一件有趣的事,并把它记录了下来 [] []        $record = array(            'message' => (string) $message,            'context' => $context,            'level' => $level,            'level_name' => $levelName,            'channel' => $this->name,            'datetime' => $ts,            'extra' => array(),        );        // 调用处理器对这些日志记录进行处理        foreach ($this->processors as $processor) {            $record = call_user_func($processor, $record);        }        // 使用处理程序处理日志记录        while ($handler = current($this->handlers)) {            if (true === $handler->handle($record)) {                break;            }            next($this->handlers);        }        return true;    }    /**     * 增加一个日志记录到DEBUG等级     * Adds a log record at the DEBUG level.     *     * @param  string  $message The log message     * @param  array   $context The log context     * @return Boolean Whether the record has been processed     */    public function addDebug($message, array $context = array())    {        return $this->addRecord(static::DEBUG, $message, $context);    }
这里使用了堆栈的概念,且堆栈的实现是通过数组来模拟的(array_shift、array_unshift)。这里关于类中实现日志等级的做法也值得我们去学习下。
对于日志记录,处理程序类对象曾使用了handle方法,这个方法到底做了什么呢?让我们基于一个处理程序类来进行这个剖析吧! 
/** * 存储到任何流资源,其继承自AbstractProcessingHandler * Stores to any stream resource * * Can be used to store into php://stderr, remote and local files, etc. * * @author Jordi Boggiano <j.boggiano@seld.be> */class StreamHandler extends AbstractProcessingHandler{    protected $stream;    protected $url;    private $errorMessage;    protected $filePermission;    protected $useLocking;    private $dirCreated;    /**     * @param resource|string $stream     * @param int             $level          The minimum logging level at which this handler will be triggered     * @param Boolean         $bubble         Whether the messages that are handled can bubble up the stack or not     * @param int|null        $filePermission Optional file permissions (default (0644) are only for owner read/write)     * @param Boolean         $useLocking     Try to lock log file before doing any writes     *     * @throws \Exception                If a missing directory is not buildable     * @throws \InvalidArgumentException If stream is not a resource or string     */    // 流  默认最低等级 是否继续冒泡默认true 文件权限默认0644 写日志前锁文件    public function __construct($stream, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false)    {        // 设置日志等级与是否继续冒泡        parent::__construct($level, $bubble);        // 检测变量是否为资源类型,譬如mysql资源符        if (is_resource($stream)) {            $this->stream = $stream;        // 检测变量是否为字符串,譬如文件路径        } elseif (is_string($stream)) {            $this->url = $stream;        } else {            throw new \InvalidArgumentException('A stream must either be a resource or a string.');        }        $this->filePermission = $filePermission;        $this->useLocking = $useLocking;    }...
StreamHandler处理程序类继承自AbstractProcessingHandler,让我们瞅瞅这个抽象类吧。
abstract class AbstractProcessingHandler extends AbstractHandler{    /**     * {@inheritdoc}     */    public function handle(array $record)    {        // 这个记录是否可被处理(当前记录等级>=处理程序可执行的日志等级)        if (!$this->isHandling($record)) {            return false;        }        // 处理该条记录        $record = $this->processRecord($record);        $record['formatted'] = $this->getFormatter()->format($record);        $this->write($record);        // 这一步可阻止继续冒泡。        return false === $this->bubble;    }    /**     * Writes the record down to the log of the implementing handler     *     * @param  array $record     * @return void     */    abstract protected function write(array $record);    /**     * 处理一条记录,通过这里可以知晓该类在写入文件之前可以通过绑定一系列处理程序进行处理的     * Processes a record.     *     * @param  array $record     * @return array     */    protected function processRecord(array $record)    {        if ($this->processors) {            foreach ($this->processors as $processor) {                $record = call_user_func($processor, $record);            }        }        return $record;    }}
如果你认真的将这两个类从头到尾研读一遍的话,那相信也已经明晰Monolog的核心概念了!在这节,我们看到了数组模拟堆栈的实现,类级别常量的设定,以及抽象类的使用,源码阅读真可谓是学习一门语言的最快捷径! 


原创粉丝点击