blob: 6887036cc89f45fa5d5c08ac8da27c7421af16d9 [file] [log] [blame]
<?php
/**
* File containing the ezcLog class.
*
* @package EventLog
* @version //autogentag//
* @copyright Copyright (C) 2005-2010 eZ Systems AS. All rights reserved.
* @license http://ez.no/licenses/new_bsd New BSD License
*/
/**
* The ezcLog class records log messages and audit trails to one or multiple
* writers.
*
* Available writers are:
* - {@link ezcLogUnixFileWriter Unix File} writer
* - {@link ezcLogDatabaseWriter Database} writer
*
* Extra writers can be added by implementing the {@link ezcLogWriter} interface.
*
* Use the {@link getMapper()} method to get an instance of the ezcLogMapper.
* The ezcLogMapper classes specifies incoming log messages with the {@link ezcLogFilter}.
* Log messages that are accepted, match with the filter, are sent to the
* {@link ezcLogWriter}.
*
* The following example demonstrates how all log messages, except for the
* audit trailing and debug messages, are written to a file.
* <code>
* $filter = new ezcLogFilter();
* $filter->severity = ezcLog::INFO | ezcLog::NOTICE | ezcLog::WARNING | ezcLog::ERROR | ezcLog::FATAL;
*
* $log = ezcLog::getInstance();
* $log->getMapper()->appendRule( new ezcLogFilterRule( $filter, new ezcLogUnixFileWriter( "/tmp/logs/", "error.log" ), true ) );
* </code>
*
* The log messages with the severity: INFO, NOTICE, WARNING, ERROR, and FATAL will
* be written to the file: "/tmp/logs/error.log". See {@link ezcLogUnixFileWriter} for
* the description of the file format.
*
* The following example will write the audit trails to the database:
* <code>
* $filter = new ezcLogFilter();
* $filter->severity = ezcLog::SUCCESS_AUDIT | ezcLog::FAILED_AUDIT;
*
* $log = ezcLog::getInstance();
* $log->getMapper()->appendRule( new ezcLogFilterRule( $filter, new ezcLogDatabaseWriter( "audits" ), true ) );
* </code>
*
* The audit trails will be stored in the table "audits". See {@link ezcLogDatabaseWriter}
* for creating the appropriate tables and setting up the database. See the {@link ezcLogFilter}
* for more details.
*
* Use the {@link log()} method to log messages at the specified writers. This
* method expects a:
* - Message, contains a single log message.
* - Severity, indicates the level of importance.
* - Extra attributes (optional).
*
* Although the interpretation of the severity levels are up to the programmer,
* the most common interpretations are:
* - DEBUG: Records information about the progress in the program and references
* source code functions. Knowledge of the source code is needed to interpret
* this log message.
* - INFO: Informative logging at a detailed level. This logging method produces a
* high level of logging, which is unmanageable on a production environment.
* Usually INFO logging is only enabled to help by analysing a problem.
* - NOTICE: Informative logging at a lower detail level than INFO logging.
* Only major stages are recorded and is useful to monitor a low volume system.
* - WARNING: Something unexpected happened, but did not cause any loss of service.
* - ERROR: An error occured, which may cause partial loss of service. Usually the
* system can recover.
* - FATAL: An serious error occured and the system is unlikely to recover.
* - SUCCESS_AUDIT: Informative logging about a successful completion of work by
* a module completed. Useful to trace system changes directly or indirectly
* done by a user.
* - FAILED_AUDIT: Informative logging about an action from a module
* with a negative result. A failed login will most likely added to this severity.
*
* The next example logs a fatal error and has no extra attributes:
* <code>
* ezcLog::getInstance()->log( "Cannot open ini file: <$file>", ezcLog::FATAL );
* </code>
*
* The log message will get by default the category and source: "default". The
* default values can be modified by changing, respectively, the properties
* $category and $source.
*
* An example of a Payment checker is as follows:
* <code>
* // The start of the Payment module.
* $log = ezcLog::getInstance();
* $log->source = "Payment checker"; // Change the default source.
*
* $log->log( "Checking the received amount", ezcLog::INFO, array( "shop" ) );
*
* if ( !$eZPay->receivedAmount() != $requiredAmount )
* {
* $log->log( "Received amount: <".$eZPay->receivedAmount()."> expected: <$requiredAmount>.",
* ezcLog::DEBUG,
* array( "category" => "shop", "file" => __FILE__, "line" => __LINE )
* );
*
* $log->log( "Insufficient amount.",
* ezcLog::FAILED_AUDIT,
* array( "UserName" => getCurrentUser(), category => "Payment" )
* )
*
* $log->log( "Rollback amount not implemented, cannot recover, ezcLog::FATAL );
* exit();
* }
* </code>
*
* Sometimes information repeats for specific severities or categories. For example that
* for the audit trails an username is required. Convenience methods like:
* {@link setSeverityAttributes()} and {@link setSourceAttributes()} exist to append
* information automatically to the log message.
*
* The ezcLog class provides a {@link trigger_error()} log handler: {@link ezcLog::logHandler()}.
* Using the trigger_error method makes your code less Log package dependent and
* produces less overhead when logging is disabled.
*
* See the {@link ezcLog::logHandler()} method for more information about how to set up the
* trigger_error functionality.
*
* See the {@link ezcDebug} package for more detailed information about writing DEBUG
* messages.
*
* @property string $source
* Definition of the global location where the log message comes
* from. Some examples are: module, source file, extension, etc. The
* source depends also on the severity of the message. For DEBUG
* messages is the source file more important whereas for a FATAL
* error the module is sufficient.
* @property string $category
* Definition of the message group. Again the category is related to
* the severity. The non audit trails can group the log messages
* like: Database (or even the database types), Templates, etc. For
* audit trails it makes much sense to categorize the actions. For
* example: security, modified content, published content, shop, etc.
*
* @package EventLog
* @version //autogentag//
* @mainclass
*/
class ezcLog
{
/**
* Debug severity constant.
*/
const DEBUG = 1;
/**
* Success audit severity constant.
*/
const SUCCESS_AUDIT = 2;
/**
* Failed audit severity constant.
*/
const FAILED_AUDIT = 4;
/**
* Info severity constant.
*/
const INFO = 8;
/**
* Notice severity constant.
*/
const NOTICE = 16;
/**
* Warning severity constant.
*/
const WARNING = 32;
/**
* Error severity constant.
*/
const ERROR = 64;
/**
* Fatal severity constant.
*/
const FATAL = 128;
/**
* Holds the properties of this class.
*
* @var array(string=>mixed)
*/
private $properties = array();
/**
* Contains the logic of mapping an incoming log message to the writer.
*
* @var ezcLogFilterSet
*/
protected $writers;
/**
* Stores the attributes from the eventTypes and eventSources.
*
* $var ezcLogContext
*/
protected $context;
/**
* Stores the instance of this class.
*
* @var ezcLog
*/
private static $instance = null;
/**
* Stores the setting whether writer exceptions should be thrown.
*
* @var bool
*/
private $throwWriterExceptions = true;
/**
* Constructs an empty ezcLog instance.
*
* This constructor is private as this class should be used as a
* singleton. Use the getInstance() method instead to get an ezcLog instance.
*/
private function __construct()
{
$this->reset();
}
/**
* Returns the instance of the class.
*
* @return ezcLog
*/
public static function getInstance()
{
if ( is_null( self::$instance ) )
{
self::$instance = new self();
ezcBaseInit::fetchConfig( 'ezcInitLog', self::$instance );
}
return self::$instance;
}
/**
* Sets the property $name to $value.
*
* @throws ezcBasePropertyNotFoundException
* If the property $name does not exist
* @param string $name
* @param mixed $value
* @ignore
*/
public function __set( $name, $value )
{
switch ( $name )
{
case "source":
case "category":
$this->properties[$name] = $value;
return;
}
throw new ezcBasePropertyNotFoundException( $name );
}
/**
* Returns the property $name.
*
* @throws ezcBasePropertyNotFoundException
* If the property $name does not exist
* @param string $name
* @return mixed
* @ignore
*/
public function __get( $name )
{
switch ( $name )
{
case "source":
case "category":
return $this->properties[$name];
}
throw new ezcBasePropertyNotFoundException( $name );
}
/**
* Returns true if the property $name is set, otherwise false.
*
* @param string $name
* @return bool
* @ignore
*/
public function __isset( $name )
{
switch ( $name )
{
case 'source':
case 'category':
return isset( $this->properties[$name] );
default:
return false;
}
}
/**
* Resets the log instance to its initial state.
*
* All sourceAttributes, severityAttributes, and writers will be removed.
* The default source and category are also reset.
*/
public function reset()
{
$this->writers = new ezcLogFilterSet();
$this->context = new ezcLogContext();
$this->setDefaults();
}
/**
* Sets the given ezcLogMapper $mapper as the log message to writer map.
*
* By default the ezcLogFilterSet is the default writer map. The default
* ezcLogMapper can be replaced with this method.
*
* @param ezcLogMapper $mapper
*/
public function setMapper( ezcLogMapper $mapper )
{
$this->writers = $mapper;
}
/**
* Returns an instance of the current ezcLogMapper.
*
* @return ezcLogMapper
*/
public function getMapper()
{
return $this->writers;
}
/**
* Sets the source and category defaults to "default".
*/
protected function setDefaults()
{
$this->properties['source'] = "default";
$this->properties['category'] = "default";
}
/**
* Enables or disables writer exceptions with the boolean $enable.
*
* Typically you want to have exceptions enabled while developing your application
* in order to catch potential problems. A live server however, should not throw
* a deadly exception when a relatively unimportant debug message could not be written to
* the log file. For these setups you can disable writer exceptions.
*
* @param bool $enable
*/
public function throwWriterExceptions( $enable )
{
$this->throwWriterExceptions = $enable;
}
/**
* Write the message $message with additional information to one or multiple log writers.
*
* The log message $message, severity $severity, and extra attributes $attributes are sent to
* the writers that matches with the {@link ezcLogFilter}. The following parameters are
* taken in the comparation with the ezcLogFilter:
* - $severity: the severity of the log message.
* - $attributes[ "source" ]: the source from where the log message comes.
* - $attributes[ "category" ]: the category of the log message.
*
* See for more information about filter matching the classes {@link ezcLog} and
* {@link ezcLogFilter}.
*
* The message $message describes what happened. The severity $severity is one of the ezcLog constants:
* - DEBUG: Records information about the progress in the program and references
* source code functions. Knowledge of the source code is needed to interpret
* this log message.
* - INFO: Informative logging at a detailed level. This logging method produces a
* high level of logging, which is unmanageable on a production environment.
* Usually INFO logging is only enabled to help by analysing a problem.
* - NOTICE: Informative logging at a lower detail level than INFO logging.
* Only major stages are recorded and is useful to monitor a low volume system.
* - WARNING: Something unexpected happened, but did not cause any loss of service.
* - ERROR: An error occured, which may cause partial loss of service. Usually the
* system can recover.
* - FATAL: An serious error occured and the system is unlikely to recover.
* - SUCCESS_AUDIT: Informative logging about a successful completion of work by
* a module completed. Useful to trace system changes directly or indirectly
* done by a user.
* - FAILED_AUDIT: Informative logging about an action from a module
* with a negative result. A failed login will most likely added to this severity.
*
* The attributes array $attributes can have one or multiple attributes that will
* be added to the log. If source and category are given, they will override the default
* source or category given as property to this object. Further more it is up to the
* application what to include in the log. It may be useful to add the
* file and linenumber to the attributes array. Use the magic PHP constants: {@link __FILE__}
* and {@link __LINE__} for this purpose. The next example adds an warning to the log.
*
* <code>
* ezcLog::getInstance()->source = "templateEngine"; // Set the default source.
* ezcLog::getInstance()->log( "ezcPersistentObject <$obj> does not exist.",
* ezcLog::WARNING,
* array( "category" => "Database", "line" => __LINE__, "file" => __FILE__, "code" => 123 )
* );
* </code>
*
* The methods {@link setSeverityAttributes()} and {@link setSourceAttributes()} can automatically
* add attributes to log messages based on, respectively, the severity and source.
*
* See also {@link logHandler()} on how to use {@link trigger_error()} to write log messages.
*
* @throws ezcLogWriterException if {@link throwWriterExceptions} are enabled and a log entry
* could not be written.
*
* @param string $message
* @param int $severity One of the following severity constants:
* DEBUG, SUCCES_AUDIT, FAIL_AUDIT, INFO, NOTICE, WARNING, ERROR, or FATAL.
* @param array(string=>string) $attributes
*/
public function log( $message, $severity, array $attributes = array() )
{
$source = ( isset( $attributes["source"] ) ? $attributes["source"] : $this->properties["source"] );
$category = ( isset( $attributes["category"] ) ? $attributes["category"] : $this->properties["category"] );
unset( $attributes["source"] );
unset( $attributes["category"] );
$attributes = array_merge( $this->context->getContext( $severity, $source ), $attributes );
$writers = $this->writers->get( $severity, $source, $category );
foreach ( $writers as $writer )
{
try
{
$writer->writeLogMessage( $message, $severity, $source, $category, $attributes );
}
catch ( ezcLogWriterException $e )
{
if ( $this->throwWriterExceptions )
{
throw $e;
}
}
}
}
/**
* Sets the attributes $attributes for a group of severities $severityMask.
*
* The severities are specified with a bit mask. These attributes will be
* added to the log message when the log severity is the same as specified
* here.
*
* Example:
* <code>
* ezcLog::getInstance()->setSeverityAttributes(
* ezcLog::SUCCESS_AUDIT | ezcLog::FAILED_AUDIT
* array( "username" => "Jan K. Doodle" )
* );
* </code>
*
* Every log message that has the severity SUCCESS_AUDIT or FAILED_AUDIT
* includes the user name: "Jan K. Doodle".
*
* @param integer $severityMask Multiple severities are specified with a logic-or.
* @param array(string=>string) $attributes
*/
public function setSeverityAttributes( $severityMask, $attributes )
{
$this->context->setSeverityContext( $severityMask, $attributes );
}
/**
* Sets the attributes $attributes for a group of sources $sources.
*
* The sources are specified in an array. These attributes will be added to the
* log message when it matches with the given $sources.
*
* Example:
* <code>
* ezcLog::getInstance()->setSourceAttributes(
* array( "Paynet", "Bibit", "Paypal" ),
* array( "MerchantID" => $merchantID )
* );
* </code>
*
* Every log message that comes from the payment module: Paynet, Bibit, or Paypal
* includes the Merchant ID.
*
* @param array(string) $sources
* @param array(string=>string) $attributes
*/
public function setSourceAttributes ( $sources, $attributes )
{
$this->context->setSourceContext( $sources, $attributes );
}
/**
* This method can be set as error_handler to log using {@link trigger_error()}.
*
* This method can be assigned with the {@link set_error_handler()} to handle the
* trigger_error calls. This method will get the log instance and forward the
* message. But includes the following information:
* - The file and linenumber are automatically added.
* - Source and category can be 'encoded' in the message.
*
* The message format is as follows:
* <pre>
* [ source, category ] Message
* </pre>
*
* When one name is given between the brackets, the category will be set and the message has a default source:
* <pre>
* [ category ] Message
* </pre>
*
* Without any names between the brackets, the default category and source are used:
* <pre>
* Message
* </pre>
*
* The following example creates manually an error handler and forwards the
* ERROR, WARNING and NOTICE severities.
* <code>
* function myLogHandler($errno, $errstr, $errfile, $errline)
* {
* switch ($errno)
* {
* case E_USER_ERROR:
* case E_USER_WARNING:
* case E_USER_NOTICE:
* if ( $loggingEnabled )
* { // Forward the message to the log handler.
* ezcLog::LogHandler( $errno, $errstr, $errfile, $errline );
* }
* break;
*
* default:
* print( "$errstr in $errfile on line $errline\n" );
* break;
* }
* }
*
* // Register myLogHandler
* set_error_handler( "myLogHandler" );
*
* // Write an warning to the log.
* trigger_error( "[paynet, transaction] Didn't get a callback from the Paynet service", E_USER_WARNING );
*
* // Add a notice.
* trigger_error( "Getting paynet status information", E_USER_NOTICE );
*
* </code>
*
* Notice that the ezcLog component is not loaded at all when the logging is disabled.
*
* @param int $errno
* @param int $errstr
* @param string $errfile
* @param int $errline
*/
public static function logHandler( $errno, $errstr, $errfile, $errline )
{
$log = ezcLog::getInstance();
$lm = new ezcLogMessage( $errstr, $errno, $log->source, $log->category );
$log->log(
$lm->message, $lm->severity,
array( "source" => $lm->source, "category" => $lm->category, "file" => $errfile, "line" => $errline )
);
}
/**
* Translates the severity constant to a string and returns this.
*
* Null is returned when the severity constant is invalid.
*
* @param int $severity
* @return string
*/
public static function translateSeverityName( $severity )
{
switch ( $severity )
{
case self::DEBUG: return "Debug";
case self::SUCCESS_AUDIT: return "Success audit";
case self::FAILED_AUDIT: return "Failed audit";
case self::INFO: return "Info";
case self::NOTICE: return "Notice";
case self::WARNING: return "Warning";
case self::ERROR: return "Error";
case self::FATAL: return "Fatal";
default: return null;
}
}
}
?>