Yii 1.1: Real Time Logging

18 followers

I've seen a lot of people asking about the logging facilities in Yii and thought I'd share a nice little class I wrote that provides near real-time logging.

This is really handy while you're developing a site as sometimes, as you probably know, when certain fatal errors occur, Yii pukes and doesn't record the buffered log entries.

My design of this class utilizes the error_log function in PHP to write to the log file. I figured that it must be pretty optimized since it's written in C and is a core PHP function.

So, the class is called CPSLiveLogRoute and is part of my Yii Extension library but has no dependencies on my extensions. You can use it in any project. Change the name if you'd like, I don't care.

To use this class:

  1. Copy the class to your protected/extensions or protected/components directory
  2. Import the class if you're not already including the destination in your imports section.
  3. Add a log route for it in your configuration file:
'log' => array(
            'class' => 'CLogRouter',
            'routes' => array(
                array(
                    'class' => 'CPSLiveLogRoute',
                    'levels' => 'error, warning, info, trace',
                    'maxFileSize' => '10240',
                    'logFile' => 'my_app_log_file_name',
                                        //  Optional excluded category
                                        'excludeCategories' => array(
                                                'system.db.CDbCommand',
                                        ),
                ),
            ),
        ),
  1. Enjoy!

Better Formatting

I change the output format of the logging through this component as well. While the Yii stock logging is adequate, this format is easier on the eyes and let's you pull out what you're looking for more easily.

The new format is:

Mmm dd hh:mm:ss [category fixed 30 chars] : <L> LOG_ENTRY

Where:

Mmm dd hh:mm:ss looks like this: Feb 11 12:34:56 and <L> is the first letter of the log level (E,W,I, or T).

Here's an example:

Feb 07 15:00:02 [system.CModule                ] : <T> Loading "log" application component
Feb 07 15:00:02 [system.CModule                ] : <T> Loading "coreMessages" application component 
Feb 07 15:00:02 [system.CModule                ] : <T> Loading "request" application component
Feb 07 15:00:02 [system.CModule                ] : <T> Loading "db" application component
Feb 07 15:00:02 [system.db.CDbConnection       ] : <T> Opening DB connection

Excluding Categories

One feature I added was category exclusion. I like to use Yii::trace() for informational debug logging. However, when turning on trace level output, you get a lot of crap that, frankly, doesn't really do anything but prolong the time it takes for me to find relevant entries.

Excluding these categories (like system.db.CDbCommand) is simple with this log router. You can configure the excluded categories in your configuration file or at runtime.

The class exposes a property called excludeCategories and is defined as an array. It will take literal strings or regular expressions (regex patterns must be enclosed in slashes (i.e. /^pattern$/) to match. It checks the literal first, then the regex for performance.

CPSLiveLogRoute.php

And, without further ado, here is the class.

<?php
/**
 * This file is part of the psYiiExtensions package.
 * 
 * @copyright Copyright &copy; 2009-2011 Pogostick, LLC
 * @link http://www.pogostick.com Pogostick, LLC.
 * @license http://www.pogostick.com/licensing
 * @package psYiiExtensions
 * @subpackage logging
 * @filesource
 * @version $Id$
 */
 
/**
 * CPSLiveLogRoute utilizes PHP's {@link error_log} function to write logs in real time
 * 
 * @author Jerry Ablan <jablan@pogostick.com>
 * @since v1.1.0
 */
class CPSLiveLogRoute extends CFileLogRoute
{
    //********************************************************************************
    //* Private Members
    //********************************************************************************
 
    /**
     * @property array $excludeCategories An array of categories to exclude from logging. Regex pattern matching is supported via {@link preg_match}
     */
    protected $_excludeCategories = array();
    public function getExcludeCategories() { return $this->_excludeCategories; }
    public function setExcludeCategories( $value ) { $this->_excludeCategories = $value; }
 
    //********************************************************************************
    //* Public Methods
    //********************************************************************************
 
    /**
     * Initialize component
     */
    public function init()
    {
        parent::init();
 
        //  Write each line out to disk
        Yii::getLogger()->autoFlush = 1;
    }
 
    /**
     * 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. ALWAYS TRUE NOW!
     */
    public function collectLogs( $logger, $processLogs = false /* ignored */ )
    {
        parent::collectLogs( $logger, true );
    }
 
    //********************************************************************************
    //* Private Methods
    //********************************************************************************
 
    /**
     * Writes log messages in files.
     * @param array $logs list of log messages
     */
    protected function processLogs( $logs = array() )
    {
        try
        {
            $_logFile = $this->getLogPath() . DIRECTORY_SEPARATOR . $this->getLogFile();
 
            if ( @filesize( $_logFile ) > $this->getMaxFileSize() * 1024 )
                $this->rotateFiles();
 
            //  Write out the log entries
            foreach ( $logs as $_log )
            {
                $_exclude = false;
 
                //  Check out the exclusions
                if ( ! empty( $this->_excludeCategories ) )
                {
                    foreach ( $this->_excludeCategories as $_category )
                    {
                        //  If found, we skip
                        if ( trim( strtolower( $_category ) ) == trim( strtolower( $_log[2] ) ) )
                        {
                            $_exclude = true;
                            break;
                        }
 
                        //  Check for regex
                        if ( '/' == $_category[0] && 0 != @preg_match( $_category, $_log[2] ) )
                        {
                            $_exclude = true;
                            break;
                        }
                    }
                }
 
                /**
                 *  Use {@link error_log} facility to write out log entry
                 */
                if ( ! $_exclude )
                    error_log( $this->formatLogMessage( $_log[0], $_log[1], $_log[2], $_log[3] ), 3, $_logFile );
            }
 
            //  Processed, clear!
            $this->logs = null;
        }
        catch ( Exception $_ex )
        {
            error_log( __METHOD__ . ': Exception processing application logs: ' . $_ex->getMessage() );
        }
    }
 
    /**
     * Formats a log message given different fields.
     * @param string $message message content
     * @param integer $level message level
     * @param string $category message category
     * @param integer $time timestamp
     * @return string formatted message
     */
    protected function formatLogMessage( $message, $level = 'I', $category = null, $time = null )
    {
        if ( null === $time )
            $time = time();
 
        $level = strtoupper( $level[0] );
 
        return @date( 'M d H:i:s', $time ) . ' [' . sprintf( '%-30s', $category ) . '] ' . ': <' . $level . '> ' . $message . PHP_EOL;
    }
}

Total 3 comments

#17150 report it
Roman Solomatin at 2014/05/07 03:42pm
Just what I needed!

Thank you!

This is just what I needed for my shell commands.

#14223 report it
Stepan Selyuk at 2013/07/27 06:52pm
Thanks

It's simple. Just add rows below in top of your config file:

// realtime logging
Yii::getLogger()->autoDump = true;
Yii::getLogger()->autoFlush = 1;
 
return array(
  //...Here config data
);
#10790 report it
Jonas at 2012/11/23 04:47pm
Thanks

Hi, Thanks for this extension. I tried to do some sort of real time logging myself but got stuck somehow. So this comes in quite handy . :-) Btw: works fine in 1.1.12

Leave a comment

Please to leave your comment.

Write new article