"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/", '
', htmlspecialchars($message) )."
" ); 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 ); } ?>