591 lines
19 KiB
PHP
591 lines
19 KiB
PHP
<?php
|
|
|
|
require_once( 'config.php' );
|
|
|
|
class Logger
|
|
{
|
|
private static $instance;
|
|
|
|
const DEBUG = 1;
|
|
const INFO = 0;
|
|
const WARNING = -1;
|
|
const ERROR = -2;
|
|
const FATAL = -3;
|
|
const PANIC = -4;
|
|
const NOLOG = -5; // Special artificial level to prevent logging
|
|
|
|
private $initialised = false;
|
|
|
|
private $id = "web";
|
|
private $idRoot = "web";
|
|
private $idArgs = "";
|
|
private $useErrorLog = true;
|
|
|
|
private $level = self::INFO;
|
|
private $termLevel = self::NOLOG;
|
|
private $databaseLevel = self::NOLOG;
|
|
private $fileLevel = self::NOLOG;
|
|
private $weblogLevel = self::NOLOG;
|
|
private $syslogLevel = self::NOLOG;
|
|
private $effectiveLevel = self::NOLOG;
|
|
|
|
private $hasTerm = false;
|
|
|
|
private $logPath = ZM_PATH_LOGS;
|
|
private $logFile = "";
|
|
private $logFd = NULL;
|
|
|
|
public static $codes = array(
|
|
self::DEBUG => "DBG",
|
|
self::INFO => "INF",
|
|
self::WARNING => "WAR",
|
|
self::ERROR => "ERR",
|
|
self::FATAL => "FAT",
|
|
self::PANIC => "PNC",
|
|
self::NOLOG => "OFF",
|
|
);
|
|
private static $syslogPriorities = array(
|
|
self::DEBUG => LOG_DEBUG,
|
|
self::INFO => LOG_INFO,
|
|
self::WARNING => LOG_WARNING,
|
|
self::ERROR => LOG_ERR,
|
|
self::FATAL => LOG_ERR,
|
|
self::PANIC => LOG_ERR,
|
|
);
|
|
private static $phpErrorLevels = array(
|
|
self::DEBUG => E_USER_NOTICE,
|
|
self::INFO => E_USER_NOTICE,
|
|
self::WARNING => E_USER_WARNING,
|
|
self::ERROR => E_USER_WARNING,
|
|
self::FATAL => E_USER_ERROR,
|
|
self::PANIC => E_USER_ERROR,
|
|
);
|
|
|
|
private function __construct()
|
|
{
|
|
$this->hasTerm = (php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR']));
|
|
$this->logFile = $this->logPath."/".$this->id.".log";
|
|
}
|
|
|
|
public function __destruct()
|
|
{
|
|
$this->terminate();
|
|
}
|
|
|
|
public function initialise( $options=array() )
|
|
{
|
|
if ( !empty($options['id']) )
|
|
$this->id = $options['id'];
|
|
|
|
//if ( isset($options['useErrorLog']) )
|
|
//$this->useErrorLog = $options['useErrorLog'];
|
|
if ( isset($options['logPath']) )
|
|
{
|
|
$this->logPath = $options['logPath'];
|
|
$tempLogFile = $this->logPath."/".$this->id.".log";
|
|
}
|
|
if ( isset($options['logFile']) )
|
|
$tempLogFile = $options['logFile'];
|
|
else
|
|
$tempLogFile = $this->logPath."/".$this->id.".log";
|
|
if ( !is_null($logFile = $this->getTargettedEnv('LOG_FILE')) )
|
|
$tempLogFile = $logFile;
|
|
|
|
$tempLevel = self::INFO;
|
|
$tempTermLevel = $this->termLevel;
|
|
$tempDatabaseLevel = $this->databaseLevel;
|
|
$tempFileLevel = $this->fileLevel;
|
|
$tempSyslogLevel = $this->syslogLevel;
|
|
$tempWeblogLevel = $this->weblogLevel;
|
|
|
|
if ( isset($options['termLevel']) )
|
|
$tempTermLevel = $options['termLevel'];
|
|
if ( isset($options['databaseLevel']) )
|
|
$tempDatabaseLevel = $options['databaseLevel'];
|
|
else
|
|
$tempDatabaseLevel = ZM_LOG_LEVEL_DATABASE;
|
|
if ( isset($options['fileLevel']) )
|
|
$tempFileLevel = $options['fileLevel'];
|
|
else
|
|
$tempFileLevel = ZM_LOG_LEVEL_FILE;
|
|
if ( isset($options['weblogLevel']) )
|
|
$tempWeblogLevel = $options['weblogLevel'];
|
|
else
|
|
$tempWeblogLevel = ZM_LOG_LEVEL_WEBLOG;
|
|
if ( isset($options['syslogLevel']) )
|
|
$tempSyslogLevel = $options['syslogLevel'];
|
|
else
|
|
$tempSyslogLevel = ZM_LOG_LEVEL_SYSLOG;
|
|
|
|
if ( $value = getenv('LOG_PRINT') )
|
|
$tempTermLevel = $value ? self::DEBUG : self::NOLOG;
|
|
|
|
if ( !is_null($level = $this->getTargettedEnv('LOG_LEVEL')) )
|
|
$tempLevel = $level;
|
|
|
|
if ( !is_null($level = $this->getTargettedEnv('LOG_LEVEL_TERM')) )
|
|
$tempTermLevel = $level;
|
|
if ( !is_null($level = $this->getTargettedEnv('LOG_LEVEL_DATABASE')) )
|
|
$tempDatabaseLevel = $level;
|
|
if ( !is_null($level = $this->getTargettedEnv('LOG_LEVEL_FILE')) )
|
|
$tempFileLevel = $level;
|
|
if ( !is_null($level = $this->getTargettedEnv('LOG_LEVEL_SYSLOG')) )
|
|
$tempSyslogLevel = $level;
|
|
if ( !is_null($level = $this->getTargettedEnv('LOG_LEVEL_WEBLOG')) )
|
|
$tempWeblogLevel = $level;
|
|
|
|
if ( ZM_LOG_DEBUG )
|
|
{
|
|
foreach ( explode( '|', ZM_LOG_DEBUG_TARGET ) as $target )
|
|
{
|
|
if ( $target == $this->id || $target == "_".$this->id || $target == $this->idRoot || $target == "_".$this->idRoot || $target == "" )
|
|
{
|
|
if ( ZM_LOG_DEBUG_LEVEL > self::NOLOG )
|
|
{
|
|
$tempLevel = $this->limit( ZM_LOG_DEBUG_LEVEL );
|
|
if ( ZM_LOG_DEBUG_FILE != "" )
|
|
{
|
|
$tempLogFile = ZM_LOG_DEBUG_FILE;
|
|
$tempFileLevel = $tempLevel;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->logFile( $tempLogFile );
|
|
|
|
$this->termLevel( $tempTermLevel );
|
|
$this->databaseLevel( $tempDatabaseLevel );
|
|
$this->fileLevel( $tempFileLevel );
|
|
$this->syslogLevel( $tempSyslogLevel );
|
|
$this->weblogLevel( $tempWeblogLevel );
|
|
|
|
$this->level( $tempLevel );
|
|
|
|
$this->initialised = true;
|
|
|
|
Debug( "LogOpts: level=".self::$codes[$this->level]."/".self::$codes[$this->effectiveLevel].", screen=".self::$codes[$this->termLevel].", database=".self::$codes[$this->databaseLevel].", logfile=".self::$codes[$this->fileLevel]."->".$this->logFile.", weblog=".self::$codes[$this->weblogLevel].", syslog=".self::$codes[$this->syslogLevel] );
|
|
}
|
|
|
|
private function terminate()
|
|
{
|
|
if ( $this->initialised )
|
|
{
|
|
if ( $this->fileLevel > self::NOLOG )
|
|
$this->closeFile();
|
|
if ( $this->syslogLevel > self::NOLOG )
|
|
$this->closeSyslog();
|
|
}
|
|
$this->initialised = false;
|
|
}
|
|
|
|
private function limit( $level )
|
|
{
|
|
if ( $level > self::DEBUG )
|
|
return( self::DEBUG );
|
|
if ( $level < self::NOLOG )
|
|
return( self::NOLOG );
|
|
return( $level );
|
|
}
|
|
|
|
private function getTargettedEnv( $name )
|
|
{
|
|
$envName = $name."_".$this->id;
|
|
$value = getenv( $envName );
|
|
if ( $value === false && $this->id != $this->idRoot )
|
|
$value = getenv( $name."_".$this->idRoot );
|
|
if ( $value === false )
|
|
$value = getenv( $name );
|
|
return( $value !== false ? $value : NULL );
|
|
}
|
|
|
|
public static function fetch( $initialise=true )
|
|
{
|
|
if ( !isset(self::$instance) )
|
|
{
|
|
$class = __CLASS__;
|
|
self::$instance = new $class;
|
|
if ( $initialise )
|
|
self::$instance->initialise( array( 'id'=>'web_php', 'syslogLevel'=>self::INFO, 'weblogLevel'=>self::INFO ) );
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
public function id( $id=NULL )
|
|
{
|
|
if ( isset($id) && $this->id != $id )
|
|
{
|
|
// Remove whitespace
|
|
$id = preg_replace( '/\S/', '', $id );
|
|
// Replace non-alphanum with underscore
|
|
$id = preg_replace( '/[^a-zA-Z_]/', '_', $id );
|
|
|
|
if ( $this->id != $id )
|
|
{
|
|
$this->id = $this->idRoot = $id;
|
|
if ( preg_match( '/^([^_]+)_(.+)$/', $id, $matches ) )
|
|
{
|
|
$this->idRoot = $matches[1];
|
|
$this->idArgs = $matches[2];
|
|
}
|
|
}
|
|
}
|
|
return( $this->id );
|
|
}
|
|
|
|
public function level( $level )
|
|
{
|
|
if ( isset($level) )
|
|
{
|
|
$lastLevel = $this->level;
|
|
$this->level = $this->limit($level);
|
|
$this->effectiveLevel = self::NOLOG;
|
|
if ( $this->termLevel > $this->effectiveLevel )
|
|
$this->effectiveLevel = $this->termLevel;
|
|
if ( $this->databaseLevel > $this->effectiveLevel )
|
|
$this->effectiveLevel = $this->databaseLevel;
|
|
if ( $this->fileLevel > $this->effectiveLevel )
|
|
$this->effectiveLevel = $this->fileLevel;
|
|
if ( $this->weblogLevel > $this->effectiveLevel )
|
|
$this->effectiveLevel = $this->weblogLevel;
|
|
if ( $this->syslogLevel > $this->effectiveLevel )
|
|
$this->effectiveLevel = $this->syslogLevel;
|
|
if ( $this->effectiveLevel > $this->level )
|
|
$this->effectiveLevel = $this->level;
|
|
if ( !$this->hasTerm )
|
|
{
|
|
if ( $lastLevel < self::DEBUG && $this->level >= self::DEBUG )
|
|
{
|
|
$this->savedErrorReporting = error_reporting( E_ALL );
|
|
$this->savedDisplayErrors = ini_set( 'display_errors', true );
|
|
}
|
|
elseif ( $lastLevel >= self::DEBUG && $this->level < self::DEBUG )
|
|
{
|
|
error_reporting( $this->savedErrorReporting );
|
|
ini_set( 'display_errors', $this->savedDisplayErrors );
|
|
}
|
|
}
|
|
}
|
|
return( $this->level );
|
|
}
|
|
|
|
public function debugOn()
|
|
{
|
|
return( $this->effectiveLevel >= self::DEBUG );
|
|
}
|
|
|
|
public function termLevel( $termLevel )
|
|
{
|
|
if ( isset($termLevel) )
|
|
{
|
|
$termLevel = $this->limit($termLevel);
|
|
if ( $this->termLevel != $termLevel )
|
|
$this->termLevel = $termLevel;
|
|
}
|
|
return( $this->termLevel );
|
|
}
|
|
|
|
public function databaseLevel( $databaseLevel=NULL )
|
|
{
|
|
if ( !is_null($databaseLevel) )
|
|
{
|
|
$databaseLevel = $this->limit($databaseLevel);
|
|
if ( $this->databaseLevel != $databaseLevel )
|
|
{
|
|
$this->databaseLevel = $databaseLevel;
|
|
if ( $this->databaseLevel > self::NOLOG )
|
|
{
|
|
if ( (include_once 'database.php') === FALSE )
|
|
{
|
|
$this->databaseLevel = self::NOLOG;
|
|
Warning( "Unable to write log entries to DB, database.php not found" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return( $this->databaseLevel );
|
|
}
|
|
|
|
public function fileLevel( $fileLevel )
|
|
{
|
|
if ( isset($fileLevel) )
|
|
{
|
|
$fileLevel = $this->limit($fileLevel);
|
|
if ( $this->fileLevel != $fileLevel )
|
|
{
|
|
if ( $this->fileLevel > self::NOLOG )
|
|
$this->closeFile();
|
|
$this->fileLevel = $fileLevel;
|
|
if ( $this->fileLevel > self::NOLOG )
|
|
$this->openFile();
|
|
}
|
|
}
|
|
return( $this->fileLevel );
|
|
}
|
|
|
|
public function weblogLevel( $weblogLevel )
|
|
{
|
|
if ( isset($weblogLevel) )
|
|
{
|
|
$weblogLevel = $this->limit($weblogLevel);
|
|
if ( $this->weblogLevel != $weblogLevel )
|
|
{
|
|
if ( $weblogLevel > self::NOLOG && $this->weblogLevel <= self::NOLOG )
|
|
{
|
|
$this->savedLogErrors = ini_set( 'log_errors', true );
|
|
}
|
|
elseif ( $weblogLevel <= self::NOLOG && $this->weblogLevel > self::NOLOG )
|
|
{
|
|
ini_set( 'log_errors', $this->savedLogErrors );
|
|
}
|
|
$this->weblogLevel = $weblogLevel;
|
|
}
|
|
}
|
|
return( $this->weblogLevel );
|
|
}
|
|
|
|
public function syslogLevel( $syslogLevel )
|
|
{
|
|
if ( isset($syslogLevel) )
|
|
{
|
|
$syslogLevel = $this->limit($syslogLevel);
|
|
if ( $this->syslogLevel != $syslogLevel )
|
|
{
|
|
if ( $this->syslogLevel > self::NOLOG )
|
|
$this->closeSyslog();
|
|
$this->syslogLevel = $syslogLevel;
|
|
if ( $this->syslogLevel > self::NOLOG )
|
|
$this->openSyslog();
|
|
}
|
|
}
|
|
return( $this->syslogLevel );
|
|
}
|
|
|
|
private function openSyslog()
|
|
{
|
|
openlog( $this->id, LOG_PID|LOG_NDELAY, LOG_LOCAL1 );
|
|
}
|
|
|
|
private function closeSyslog()
|
|
{
|
|
closelog();
|
|
}
|
|
|
|
private function logFile( $logFile )
|
|
{
|
|
if ( preg_match( '/^(.+)\+$/', $logFile, $matches ) )
|
|
$this->logFile = $matches[1].'.'.getmypid();
|
|
else
|
|
$this->logFile = $logFile;
|
|
}
|
|
|
|
private function openFile()
|
|
{
|
|
if ( !$this->useErrorLog )
|
|
{
|
|
if ( $this->logFd = fopen( $this->logFile, "a+" ) )
|
|
{
|
|
if ( strnatcmp( phpversion(), '5.2.0' ) >= 0 )
|
|
{
|
|
$error = error_get_last();
|
|
trigger_error( "Can't open log file '$logFile': ".$error['message']." @ ".$error['file']."/".$error['line'], E_USER_ERROR );
|
|
}
|
|
$this->fileLevel = self::NOLOG;
|
|
}
|
|
}
|
|
}
|
|
|
|
private function closeFile()
|
|
{
|
|
if ( $this->logFd )
|
|
fclose( $this->logFd );
|
|
}
|
|
|
|
public function logPrint( $level, $string, $file=NULL, $line=NULL )
|
|
{
|
|
if ( $level <= $this->effectiveLevel )
|
|
{
|
|
$string = preg_replace( '/[\r\n]+$/', '', $string );
|
|
$code = self::$codes[$level];
|
|
|
|
$time = gettimeofday();
|
|
$message = sprintf( "%s.%06d %s[%d].%s [%s]", strftime( "%x %H:%M:%S", $time['sec'] ), $time['usec'], $this->id, getmypid(), $code, $string );
|
|
|
|
if ( is_null( $file) )
|
|
{
|
|
if ( $this->useErrorLog || $this->databaseLevel > self::NOLOG )
|
|
{
|
|
$backTrace = debug_backtrace();
|
|
$file = $backTrace[1]['file'];
|
|
$line = $backTrace[1]['line'];
|
|
if ( $this->hasTerm )
|
|
$rootPath = getcwd();
|
|
else
|
|
$rootPath = $_SERVER['DOCUMENT_ROOT'];
|
|
$file = preg_replace( '/^'.addcslashes($rootPath,'/').'\/?/', '', $file );
|
|
}
|
|
}
|
|
|
|
if ( $this->useErrorLog )
|
|
$message .= " at ".$file." line ".$line;
|
|
else
|
|
$message = $message;
|
|
|
|
if ( $level <= $this->termLevel )
|
|
if ( $this->hasTerm )
|
|
print( $message."\n" );
|
|
else
|
|
print( preg_replace( "/\n/", '<br/>', htmlspecialchars($message) )."<br/>" );
|
|
|
|
if ( $level <= $this->fileLevel )
|
|
if ( $this->useErrorLog )
|
|
{
|
|
if ( !error_log( $message."\n", 3, $this->logFile ) )
|
|
{
|
|
if ( strnatcmp( phpversion(), '5.2.0' ) >= 0 )
|
|
{
|
|
$error = error_get_last();
|
|
trigger_error( "Can't write to log file '".$this->logFile."': ".$error['message']." @ ".$error['file']."/".$error['line'], E_USER_ERROR );
|
|
}
|
|
}
|
|
}
|
|
elseif ( $this->logFd )
|
|
fprintf( $this->logFd, $message."\n" );
|
|
|
|
$message = $code." [".$string."]";
|
|
if ( $level <= $this->syslogLevel )
|
|
syslog( self::$syslogPriorities[$level], $message );
|
|
if ( $level <= $this->databaseLevel )
|
|
{
|
|
$dbFile = is_null($file)?'NULL':"'".dbEscape($file)."'";
|
|
$dbLine = is_null($line)?'NULL':dbEscape($line);
|
|
|
|
try {
|
|
global $dbConn;
|
|
$sql = "INSERT INTO Logs ( TimeKey, Component, Pid, Level, Code, Message, File, Line ) values ( ?, ?, ?, ?, ?, ?, ?, ? )";
|
|
$stmt = $dbConn->prepare( $sql );
|
|
$result = $stmt->execute( array( sprintf( "%d.%06d", $time['sec'], $time['usec'] ), dbEscape($this->id), getmypid(), dbEscape($level), dbEscape($code), dbEscape($string), $dbFile, $dbLine ) );
|
|
} catch(PDOException $ex) {
|
|
$this->databaseLevel = self::NOLOG;
|
|
Fatal( "Can't write log entry '$sql': ". $ex->getMessage() );
|
|
}
|
|
}
|
|
// This has to be last as trigger_error can be fatal
|
|
if ( $level <= $this->weblogLevel )
|
|
{
|
|
if ( $this->useErrorLog )
|
|
error_log( $message, 0 );
|
|
else
|
|
trigger_error( $message, self::$phpErrorLevels[$level] );
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
function logInit( $options=array() )
|
|
{
|
|
$logger = Logger::fetch();
|
|
$logger->initialise( $options );
|
|
set_error_handler( 'ErrorHandler' );
|
|
}
|
|
|
|
function logToDatabase( $level=NULL )
|
|
{
|
|
return( Logger::fetch()->databaseLevel( $level ) );
|
|
}
|
|
|
|
function Mark( $level=Logger::DEBUG, $tag="Mark" )
|
|
{
|
|
Logger::fetch()->logPrint( $level, $tag );
|
|
}
|
|
|
|
function Dump( &$var, $label="VAR" )
|
|
{
|
|
ob_start();
|
|
print( $label." => " );
|
|
print_r( $var );
|
|
Logger::fetch()->logPrint( Logger::DEBUG, ob_get_clean() );
|
|
}
|
|
|
|
function Debug( $string )
|
|
{
|
|
Logger::fetch()->logPrint( Logger::DEBUG, $string );
|
|
}
|
|
|
|
function Info( $string )
|
|
{
|
|
Logger::fetch()->logPrint( Logger::INFO, $string );
|
|
}
|
|
|
|
function Warning( $string )
|
|
{
|
|
Logger::fetch()->logPrint( Logger::WARNING, $string );
|
|
}
|
|
|
|
function Error( $string )
|
|
{
|
|
Logger::fetch()->logPrint( Logger::ERROR, $string );
|
|
}
|
|
|
|
function Fatal( $string )
|
|
{
|
|
Logger::fetch()->logPrint( Logger::FATAL, $string );
|
|
die( $string );
|
|
}
|
|
|
|
function Panic( $string )
|
|
{
|
|
if ( true )
|
|
{
|
|
// Use builtin function
|
|
ob_start();
|
|
debug_print_backtrace();
|
|
$backtrace = "\n".ob_get_clean();
|
|
}
|
|
else
|
|
{
|
|
// Roll your own
|
|
$backtrace = '';
|
|
$frames = debug_backtrace();
|
|
for ( $i = 0; $i < count($frames); $i++ )
|
|
{
|
|
$frame = $frames[$i];
|
|
$backtrace .= sprintf( "\n#%d %s() at %s/%d", $i, $frame['function'], $frame['file'], $frame['line'] );
|
|
}
|
|
}
|
|
Logger::fetch()->logPrint( Logger::PANIC, $string.$backtrace );
|
|
die( $string );
|
|
}
|
|
|
|
function ErrorHandler( $error, $string, $file, $line )
|
|
{
|
|
if ( ! (error_reporting() & $error) )
|
|
{
|
|
// This error code is not included in error_reporting
|
|
return( false );
|
|
}
|
|
|
|
switch ( $error )
|
|
{
|
|
case E_USER_ERROR:
|
|
Logger::fetch()->logPrint( Logger::FATAL, $string, $file, $line );
|
|
break;
|
|
|
|
case E_USER_WARNING:
|
|
Logger::fetch()->logPrint( Logger::ERROR, $string, $file, $line );
|
|
break;
|
|
|
|
case E_USER_NOTICE:
|
|
Logger::fetch()->logPrint( Logger::WARNING, $string, $file, $line );
|
|
break;
|
|
|
|
default:
|
|
Panic( "Unknown error type: [$error] $string" );
|
|
break;
|
|
}
|
|
return( true );
|
|
}
|
|
|
|
?>
|