zoneminder/web/includes/logger.php

588 lines
18 KiB
PHP
Raw Normal View History

<?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 )
{
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 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 );
}
?>