Version 4.1.19

This commit is contained in:
Neo committed 2025-12-19 05:38:56 -08:00
1 parent 28bd025b35
commit 2bd5025018
2674 files changed
+233518 -77766

No files matched your search

+218 -234
View File
@@ -2,10 +2,10 @@
/**
* @brief Log Class
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @since 07 Nov 2013
* @package IPS Community Suite
* @since 29 Mar 2016
* @version SVN_VERSION_NUMBER
*/
@@ -21,276 +21,260 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
/**
* Log Class
*/
abstract class _Log
class _Log extends \IPS\Patterns\ActiveRecord
{
/**
* @brief Multitons
* @brief [ActiveRecord] Multiton Store
*/
protected static $instances = array();
protected static $multitons;
/**
* @brief Configuration
* @brief [ActiveRecord] Database Table
*/
protected static $configuration = NULL;
public static $databaseTable = 'core_log';
/**
* Log
* Set Default Values
*
* Severity constants
* @li LOG_EMERG system is unusable
* @li LOG_ALERT action must be taken immediately
* @li LOG_CRIT critical conditions
* @li LOG_ERR error conditions
* @li LOG_WARNING warning conditions
* @li LOG_NOTICE normal, but significant, condition
* @li LOG_INFO informational message
* @li LOG_DEBUG debug-level message
*
* LOG_METHOD example JSON object
* [
* {
* 'levels': [0,1,2,3,4,5],
* 'method': 'disk'
* },
* {
* 'levels': [6,7],
* 'method': 'email',
* 'config': { 'to': 'sales@invisionpower.com', 'subject': 'New error log from My Community' }
* }
* ]
*
* Acceptable methods are "disk", "email" and "syslog".
*
* Caveats:
* @li Email method falls back to PHPs 'mail' command if using the framework fails (critical DB error, etc). If your server cannot use
* PHPs mail, then do not use the email method.
*
* Any missing levels (between 0 and 6) are populated by 'disk' method. 7 is debug and can be left blank
*
* @param int|string $key PHP syslog constant (int) or method name (string)
* @return object \IPS\Log
*
* @see <a href='http://tools.ietf.org/html/rfc5424'>RFC 5424</a>
* @see <a href='http://php.net/syslog'>PHP's syslog</a>
*/
public static function i( $key )
{
if ( is_string( $key ) )
{
$severity = static::getSeverityFromMethod( $key );
}
else
{
$severity = intval( $key );
}
$config = static::getConfiguration();
if ( !isset( static::$instances[ $severity ] ) )
{
$classname = '\IPS\Log\\' . ucfirst( $config[ $severity ]['method'] );
static::$instances[ $severity ] = new $classname( $config[ $severity ]['config'], $severity );
}
/* One method can have multiple severities, so lets set the current severity for this call */
static::$instances[ $severity ]->setSeverity( $severity );
return static::$instances[ $severity ];
}
/**
* Return a severity level based on method name (disk, syslog, etc)
*
* @param string $method Method name (disk, syslog, etc)
* @return int
*/
public static function getSeverityFromMethod( $method )
{
foreach( static::getUsedMethods() as $method => $levels )
{
$severity = array_pop( $levels );
return $severity;
}
return LOG_DEBUG;
}
/**
* Return an array of used methods with the severity levels assigned
*
* @return array
*/
public static function getUsedMethods()
{
$config = static::getConfiguration();
$methods = array();
foreach( $config as $level => $data )
{
$methods[ $data['method'] ][] = $level;
}
return $methods;
}
/**
* Returns the configuration from LOG_METHOD parsed and checked
*
* @return array
*/
protected static function getConfiguration()
{
if ( static::$configuration === NULL )
{
$json = @json_decode( LOG_METHOD, TRUE );
if ( is_array( $json ) )
{
foreach( $json as $row )
{
if ( isset( $row['levels'] ) AND isset( $row['method'] ) AND is_array( $row['levels'] ) )
{
foreach( $row['levels'] as $level )
{
static::$configuration[ $level ]['method'] = $row['method'];
static::$configuration[ $level ]['config'] = ( isset( $row['config'] ) ) ? $row['config'] : NULL;
}
}
}
}
/* Ensure everything except DEBUG has a value */
foreach( range( 0, 6 ) as $level )
{
if ( ! isset( static::$configuration[ $level ] ) )
{
static::$configuration[ $level ]['method'] = 'disk';
static::$configuration[ $level ]['config'] = NULL;
}
}
/* And then make sure DEBUG has a value set to None if not already set */
if ( ! isset( static::$configuration[ 7 ] ) )
{
static::$configuration[ 7 ]['method'] = 'none';
static::$configuration[ 7 ]['config'] = NULL;
}
}
return static::$configuration;
}
/**
* Prune logs where possible
*
* @param int $days Older than (days) to prune
* @return void
*/
public static function pruneLogs( $days )
{
if ( ! $days )
{
return;
}
foreach( static::getUsedMethods() as $method => $levels )
{
\IPS\Log::i( $method )->prune( $days );
}
}
/* @Brief Configuration array */
protected $config = NULL;
/* @Brief Severity level */
protected $severity = NULL;
/**
* The constructor
*
* @param array|null $config
* @param int $severity
* @return void
*/
public function __construct( $config, $severity )
public function setDefaultValues()
{
$this->config = $config;
$this->setSeverity( $severity );
$this->time = time();
}
/**
* Store the severity for this call
*
* @param int $severity Severity
* @return \IPS\Log
* @brief Track if we are currently logging to prevent recursion for database issues
*/
public function setSeverity( $severity )
{
$this->severity = $severity;
return $this;
}
static protected $currentlyLogging = FALSE;
/**
* @brief IP address to use or NULL to retrieve from Request class
* Write a log message
*
* @param \Exception|string $message An Exception object or a generic message to log
* @param string|null $category An optional string identifying the type of log (for example "upgrade")
* @return \IPS\Log|null
*/
protected $ipAddress = NULL;
/**
* Set the IP address
*
* @param string $ip IP address
* @return \IPS\Log
* @note This method is designed so that we can bypass issues with \IPS\Settings not loaded yet for instance when logging cache-related calls (since settings are stored in cache)
*/
public function setIpAddress( $ip )
public static function log( $message, $category = NULL )
{
$this->ipAddress = $ip;
return $this;
}
/* Anything to log? */
if( !$message )
{
return NULL;
}
/**
* Get the IP address
*
* @return string
* @note This call will reset the stored IP address every time so that other log requests use the correct IP
*/
public function getIpAddress()
{
$result = ( $this->ipAddress !== NULL ) ? $this->ipAddress : \IPS\Request::i()->ipAddress();
$this->ipAddress = NULL;
return $result;
/* Try to log it to the database */
try
{
if( static::$currentlyLogging === TRUE )
{
throw new \RuntimeException;
}
static::$currentlyLogging = TRUE;
$log = new static;
if ( $message instanceof \Exception )
{
$log->exception_class = get_class( $message );
$log->exception_code = $message->getCode();
if ( method_exists( $message, 'extraLogData' ) )
{
$log->message = $message->extraLogData() . "\n" . $message->getMessage();
}
else
{
$log->message = $message->getMessage();
}
$log->backtrace = $message->getTraceAsString();
}
else
{
if ( is_array( $message ) )
{
$message = var_export( $message, TRUE );
}
$log->message = $message;
$log->backtrace = ( new \Exception )->getTraceAsString();
}
/* If this is an actual request and not command line-invoked */
if( \IPS\Dispatcher::hasInstance() AND mb_strpos( php_sapi_name(), 'cli' ) !== 0 )
{
$log->url = \IPS\Request::i()->url();
$log->member_id = \IPS\Member::loggedIn()->member_id ?: 0;
}
$log->category = $category;
$log->save();
static::$currentlyLogging = FALSE;
return $log;
}
/* If that fails, log to disk */
catch ( \Exception $e )
{
if ( !\IPS\NO_WRITES and !\IPS\CIC )
{
/* What are we writing? */
$date = date('r');
if ( $message instanceof \Exception )
{
$messageToLog = $date . "\n" . get_class( $message ) . '::' . $message->getCode() . "\n" . $message->getMessage() . "\n" . $message->getTraceAsString();
}
else
{
if ( is_array( $message ) )
{
$message = var_export( $message, TRUE );
}
$messageToLog = $date . "\n" . $message . "\n" . ( new \Exception )->getTraceAsString();
}
/* Where are we writing it? */
$dir = str_replace( '{root}', \IPS\ROOT_PATH, \IPS\LOG_FALLBACK_DIR );
if ( !is_dir( $dir ) )
{
if ( !@mkdir( $dir ) or !@chmod( $dir, \IPS\IPS_FOLDER_PERMISSION ) )
{
return;
}
}
/* Write it */
$header = "<?php exit; ?>\n\n";
$file = $dir . '/' . date( 'Y' ) . '_' . date( 'm' ) . '_' . date('d') . '_' . ( $category ?: 'nocategory' ) . '.php';
if ( file_exists( $file ) )
{
@\file_put_contents( $file, "\n\n-------------\n\n" . $messageToLog, FILE_APPEND );
}
else
{
@\file_put_contents( $file, $header . $messageToLog );
}
@chmod( $file, \IPS\IPS_FILE_PERMISSION );
}
}
return NULL;
}
/**
* Get the most recent log titles
*
* @param int $limit
* @return array array( array( 'date' => unixTimeStamp, 'title' => title, 'suffix' => suffix ), ... );
* Write a debug message
*
* @param \Exception|string $message An Exception object or a generic message to log
* @param string|null $category An optional string identifying the type of log (for example "upgrade")
* @return \IPS\Log|null
*/
public function getMostRecentLogsTitles( $limit=50 )
public static function debug( $message, $category = NULL )
{
return array();
if ( defined('\IPS\DEBUG_LOG') and \IPS\DEBUG_LOG )
{
return static::log( $message, $category );
}
return NULL;
}
/**
* Get latest logs
*
* @param \IPS\DateTime $date Limit only to logs after this date
* @return array array( array( 'date' => unixTimeStamp, 'title' => title, 'suffix' => suffix ), ... );
* Get fallback directory
*
* @return string|null
*/
public function getLatestTitles( \IPS\DateTime $date )
public static function fallbackDir()
{
return array();
if ( \IPS\CIC )
{
return NULL;
}
return str_replace( '{root}', \IPS\ROOT_PATH, \IPS\LOG_FALLBACK_DIR );
}
/**
* Prune logs
*
*
* @param int $days Older than (days) to prune
* @return void
*/
public function prune( $days )
public static function pruneLogs( $days )
{
\IPS\Db::i()->delete( static::$databaseTable, array( 'time<?', \IPS\DateTime::create()->sub( new \DateInterval( 'P' . $days . 'D' ) )->getTimestamp() ) );
if ( !\IPS\NO_WRITES )
{
$dir = static::fallbackDir();
if ( is_dir( $dir ) )
{
try
{
$it = new \DirectoryIterator( $dir );
}
catch ( Exception $e )
{
return;
}
foreach( $it as $file )
{
try
{
if( $file->isDot() or !$file->isFile() )
{
continue;
}
if( preg_match( "#.cgi$#", $file->getFilename(), $matches ) or ( preg_match( "#.php$#", $file->getFilename(), $matches ) and $file->getMTime() < ( time() - ( 60 * 60 * 24 * (int) $days ) ) ) )
{
@unlink( $file->getPathname() );
}
}
catch ( Exception $e ) { }
}
}
}
}
/* !Legacy */
/**
* @brief The latest severity set by i()
* @deprecated
*/
static protected $severity = LOG_NOTICE;
/**
* This method is deprecated but provided for code that may still use \IPS\Log::i()->write()
*
* @deprecated
* @param int $key Severity level
* @return \IPS\Log
*/
public static function i( $key )
{
static::$severity = $key;
return new static;
}
/**
* This method is deprecated but provided for code that may still use \IPS\Log::i()->write()
*
* @deprecated
* @param string $message Message to log
* @param string $suffix Category
* @return void
*/
public function write( $message, $suffix=NULL )
{
if ( static::$severity === LOG_DEBUG )
{
static::debug( $message, $suffix );
}
else
{
static::log( $message, $suffix );
}
}
}