mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-01-14 17:18:41 -05:00
* Allow API authentication using the `auth` query parameter containing an auth. hash. Fixes #1827 The same auth. hash for zms is used here. This allows consumers to use the API without sending the password in the query string and not require forging logins via the login form. * Move logger.php's global Debug function to Logger::Debug to avoid polluting globals This avoids a conflict with CakePHP when logger.php gets included indrectly from API code. * Protect action=login when ZM_ENABLE_CSRF_MAGIC is enabled
588 lines
18 KiB
PHP
588 lines
18 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;
|
|
|
|
Logger::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 static function Debug( $string )
|
|
{
|
|
Logger::fetch()->logPrint( Logger::DEBUG, $string );
|
|
}
|
|
|
|
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 )
|
|
{
|
|
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'] ), $this->id, getmypid(), $level, $code, $string, $file, $line ) );
|
|
} 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 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( htmlentities($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 );
|
|
}
|
|
|
|
?>
|