blob: 40361bc87bba1c8128ec519d52b8997acb9cda17 [file] [log] [blame]
<?php
/**
* File containing the ezcDebug class.
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
* @package Debug
* @version //autogentag//
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
*/
/**
* The ezcDebug class provides functionality to format and store debug messages and timers.
*
* The functionality of the Debug component is two folded:
* - Debug log messages
* - Timers
*
* The log messages are heavily based on the {@link EventLog} log messages. In fact
* internally the EventLog is used with its own log writer. The {@link log()} method
* is almost the same as from the EventLog. The next example demonstrates how to instantiate the
* ezcDebug class and write some log messages:
* <code>
* $debug = ezcDebug::getInstance();
* $debug->log( "Connecting with the paynet server", 2 );
* // ...
* $debug->log( "Connection failed, retrying in 5 seconds", 1 );
* // ...
* $debug->log( "Could not connect with the server", 0 );
* </code>
*
* The second parameter of the log method is the verbosity. This is a number that
* specifies the importance of the log message. That makes it easier to sort out messages of less importance.
* In this example, we assumed the more important the message, the lower the
* verbosity number.
*
* The ezcDebug timer is designed to allow the next two timing methods:
* - Timers, the time between two points in the program.
* - Accumulators, gets the relative time after the script started.
*
* The "Timers" are simply set with the methods {@link startTimer()} and {@link stopTimer()}. The next example
* demonstrates the timing of a simple calculation:
* <code>
* $debug = ezcDebug::getInstance();
* $debug->startTimer( "Simple calculation" );
*
* // Simple calculation
* $result = 4 + 6;
*
* $debug->stopTimer( "Simple calculation" ); // Parameter can be omitted.
* </code>
*
* To get timing points, accumulators, use the {@link switchTimer()} method. This is shown in the next example:
* <code>
* $debug = ezcDebug::getInstance();
* $debug->startTimer( "My script" );
* // ...
* $debug->switchTimer( "Reading ini file" );
* // ...
* $debug->switchTimer( "Initializing template parser" );
* // ...
* $debug->switchTimer( "Parsing" );
* // ...
* $debug->stopTimer();
* </code>
*
* @property ezcDebugOptions $options
* Options to configure the behaviour of ezcDebug, including stack
* trace behaviours.
*
* @package Debug
* @version //autogentag//
* @mainclass
*/
class ezcDebug
{
/**
* Properties.
*
* @var array(string=>mixed)
*/
protected $properties = array();
/**
* Instance of the singleton ezcDebug object.
*
* Use the getInstance() method to retrieve the instance.
*
* @var ezcDebug
*/
private static $instance = null;
/**
* The formatter that generates the debug output.
*
* @var ezcDebugFormatter
*/
private $formatter = null;
/**
* A pointer to the logging system.
*
* @var ezcLog
*/
private $log = null;
/**
* The timing object used to store timing information.
*
* @var ezcDebugTimer
*/
private $timer = null;
/**
* The writer that holds debug output.
*
* @var ezcLogWriter
*/
private $writer = null;
/**
* Constructs a new debug object and attaches it to the log object.
*
* This method is private because the getInstance() should be called.
*/
private function __construct()
{
$this->options = new ezcDebugOptions();
$original = ezcLog::getInstance();
$this->log = clone( $original );
$this->log->reset();
$this->log->setMapper( new ezcLogFilterSet() );
// Set the writer.
$this->writer = new ezcDebugMemoryWriter();
$filter = new ezcLogFilter();
$filter->severity = ezcLog::DEBUG;
$this->log->getMapper()->appendRule( new ezcLogFilterRule( $filter, $this->writer, true ) );
$this->reset();
}
/**
* Property get access.
*
* @throws ezcBasePropertyNotFoundException
* If the given property could not be found.
* @param string $propertyName
* @ignore
*/
public function __get( $propertyName )
{
if ( $this->__isset( $propertyName ) )
{
return $this->properties[$propertyName];
}
throw new ezcBasePropertyNotFoundException( $propertyName );
}
/**
* Property set access.
*
* @throws ezcBasePropertyNotFoundException
* @param string $propertyName
* @param string $propertyValue
* @ignore
*/
public function __set( $propertyName, $propertyValue )
{
switch ( $propertyName )
{
case 'options':
if ( !( $propertyValue instanceof ezcDebugOptions ) )
{
throw new ezcBaseValueException(
$propertyName,
$propertyValue,
'ezcDebugOptions'
);
}
break;
default:
throw new ezcBasePropertyNotFoundException( $propertyName );
}
$this->properties[$propertyName] = $propertyValue;
}
/**
* Property isset access.
*
* @param string $propertyName
* @return bool
* @ignore
*/
public function __isset( $propertyName )
{
return array_key_exists( $propertyName, $this->properties );
}
/**
* Resets the log messages and timer information.
*
* @return void
*/
public function reset()
{
$this->writer->reset();
$this->timer = new ezcDebugTimer();
}
/**
* Returns the instance of this class.
*
* When the ezcDebug instance is created it is automatically added to the instance
* of ezcLog.
*
* @return ezcDebug
*/
public static function getInstance()
{
if ( is_null( self::$instance ))
{
self::$instance = new ezcDebug();
ezcBaseInit::fetchConfig( 'ezcInitDebug', self::$instance );
}
return self::$instance;
}
/**
* Returns the instance of the EventLog used in this class.
*
* The returned instance is not the same as retrieved via the
* ezcLog::getInstance() method.
*
* @return ezcLog
*/
public function getEventLog()
{
return $this->log;
}
/**
* Sets the formatter $reporter for the output.
*
* If no formatter is set {@link ezcDebugHtmlReporter} will be used by default.
*
* @param ezcDebugOutputFormatter $formatter
* @return void
*/
public function setOutputFormatter( ezcDebugOutputFormatter $formatter )
{
$this->formatter = $formatter;
}
/**
* Returns the formatted debug output.
*
* @return string
*/
public function generateOutput()
{
if ( is_null( $this->formatter ) )
$this->formatter = new ezcDebugHtmlFormatter();
return $this->formatter->generateOutput( $this->writer->getStructure(), $this->timer->getTimeData() );
}
/**
* Starts the timer with the identifier $name.
*
* Optionally, a timer group can be given with the $group parameter.
*
* @param string $name
* @param string $group
*/
public function startTimer( $name, $group = null )
{
$this->timer->startTimer( $name, $group );
}
/**
* Stores the time from the running timer, and starts a new timer.
*
* Stores the time for $oldTimer (maybe omitted if only 1 timer is running)
* and starts a new timer with $newName.
*
* @param string $newName
* @param string|bool $oldName
*/
public function switchTimer( $newName, $oldName = false )
{
$this->timer->switchTimer( $newName, $oldName );
}
/**
* Stops the timer identified by $name.
*
* $name can be omitted (false) if only one timer is running.
*
* @param string|bool $name
*/
public function stopTimer( $name = false )
{
$this->timer->stopTimer( $name );
}
/**
* Writes the debug message $message with verbosity $verbosity.
*
* Arbitrary $extraInfo can be submitted. If $stackTrace is set to true, a
* stack trace will be stored at the current program position.
*
* @param string $message
* @param int $verbosity
* @param array(string=>string) $extraInfo
* @param bool $stackTrace
*/
public function log( $message, $verbosity, array $extraInfo = array(), $stackTrace = false )
{
// Add the verbosity
$extraInfo = array_merge( array( "verbosity" => $verbosity ), $extraInfo );
if ( $this->options->stackTrace === true || $stackTrace === true )
{
$extraInfo['stackTrace'] = $this->getStackTrace();
}
$this->log->log( $message, ezcLog::DEBUG, $extraInfo );
}
/**
* Returns a stack trace iterator for the current call.
*
* Returns a
* - {@link ezcDebugXdebugStacktraceIterator} if Xdebug is available
* - {@link ezcDebugPhpStacktraceIterator} otherwise
* representing a stack trace of the current function environment.
*
* @return ezcDebugStacktraceIterator
*/
private function getStackTrace()
{
if ( extension_loaded( 'xdebug' ) )
{
return new ezcDebugXdebugStacktraceIterator(
xdebug_get_function_stack(),
2,
$this->options
);
}
else
{
return new ezcDebugPhpStacktraceIterator(
debug_backtrace(),
2,
$this->options
);
}
}
/**
* Dispatches the message and error type to the correct debug or log
* function.
*
* This function should be used as the set_error_handler from the
* trigger_error function.
*
* Use for example the following code in your application:
*
* <code>
* function debugHandler( $a, $b, $c, $d )
* {
* ezcDebug::debugHandler( $a, $b, $c, $d );
* }
*
* set_error_handler( "debugHandler" );
* </code>
*
* Use trigger_error() to log warning, error, etc:
*
* <code>
* trigger_error( "[Paynet, templates] Cannot load template", E_USER_WARNING );
* </code>
*
* See the PHP documentation of
* {@link http://php.net/trigger_error trigger_error} for more information.
*
* @param int $errno
* @param string $errstr
* @param string $errfile
* @param int $errline
* @return void
*/
public static function debugHandler( $errno, $errstr, $errfile, $errline )
{
$debug = ezcDebug::getInstance();
$log = $debug->getEventLog();
preg_match(
'/^\s*(?:\[([^,\]]*)(?:,\s(.*))?\])?\s*(?:(\d+):)?\s*(.*)$/',
$errstr,
$matches
);
$message = ( $matches[4] === '' ? false : $matches[4] );
$verbosity = ( $matches[3] === '' ? false : $matches[3] );
if ( strlen( $matches[2] ) == 0 )
{
$category = ( $matches[1] === '' ? $log->category : $matches[1] );
$source = $log->source;
}
else
{
$category = $matches[2];
$source = $matches[1];
}
$severity = false;
switch ( $errno )
{
case E_USER_NOTICE:
$severity = ezcLog::NOTICE;
break;
case E_USER_WARNING:
$severity = ezcLog::WARNING;
break;
case E_USER_ERROR:
$severity = ezcLog::ERROR;
break;
}
$debug->log(
$message,
$severity,
array(
'source' => $source,
'category' => $category,
'verbosity' => $verbosity,
'file' => $errfile,
'line' => $errline
)
);
}
}
?>