N°3731 Log deprecated calls (#193)

* New DeprecatedCallsLog logger enabled directly in bootstrap so that we can use it in the most situations
* Default Log level is now DEBUG for dev env, ERROR for others
* Logs the caller and the deprecated method called, like :
`2021-04-06 10:52:25 | Warning | AjaxPage::output L2857 calling WebPage:outputCollapsibleSectionInit | deprecated-method`
* Sample file consumer
* Sample php consumer
This commit is contained in:
Pierre Goiffon
2021-04-13 13:55:25 +02:00
committed by GitHub
parent f3cf154969
commit f6d9d0f08b
4 changed files with 202 additions and 62 deletions

View File

@@ -50,18 +50,19 @@ if (function_exists('microtime')) {
$fItopStarted = 1000 * time(); $fItopStarted = 1000 * time();
} }
if (! isset($GLOBALS['bBypassAutoload']) || $GLOBALS['bBypassAutoload'] == false) if (!isset($GLOBALS['bBypassAutoload']) || $GLOBALS['bBypassAutoload'] == false) {
{
require_once APPROOT.'/lib/autoload.php'; require_once APPROOT.'/lib/autoload.php';
} }
require_once APPROOT.'core/log.class.inc.php';
DeprecatedCallsLog::Enable();
// //
// Maintenance mode // Maintenance mode
// //
// Use 'maintenance' parameter to bypass maintenance mode // Use 'maintenance' parameter to bypass maintenance mode
if (!isset($bBypassMaintenance)) if (!isset($bBypassMaintenance)) {
{
$bBypassMaintenance = isset($_REQUEST['maintenance']) ? boolval($_REQUEST['maintenance']) : false; $bBypassMaintenance = isset($_REQUEST['maintenance']) ? boolval($_REQUEST['maintenance']) : false;
} }

View File

@@ -3,4 +3,6 @@
* This file is only here for compatibility issues. Will be removed in iTop 3.1.0 (N°3664) * This file is only here for compatibility issues. Will be removed in iTop 3.1.0 (N°3664)
* *
* @deprecated 3.0.0 N°3663 Exception classes were moved to `/application/exceptions`, use autoloader instead of require ! * @deprecated 3.0.0 N°3663 Exception classes were moved to `/application/exceptions`, use autoloader instead of require !
*/ */
require_once '../approot.inc.php';
DeprecatedCallsLog::NotifyDeprecatedFile('Classes were moved to /application/exceptions');

View File

@@ -535,30 +535,33 @@ class FileLog
abstract class LogAPI abstract class LogAPI
{ {
const CHANNEL_DEFAULT = ''; public const CHANNEL_DEFAULT = '';
const LEVEL_ERROR = 'Error'; public const LEVEL_ERROR = 'Error';
const LEVEL_WARNING = 'Warning'; public const LEVEL_WARNING = 'Warning';
const LEVEL_INFO = 'Info'; public const LEVEL_INFO = 'Info';
const LEVEL_OK = 'Ok'; public const LEVEL_OK = 'Ok';
const LEVEL_DEBUG = 'Debug'; public const LEVEL_DEBUG = 'Debug';
const LEVEL_TRACE = 'Trace'; public const LEVEL_TRACE = 'Trace';
/** /**
* @var string default log level, can be overrided * @var string default log level
* @see GetMinLogLevel * @used-by GetLevelDefault
* @since 2.7.1 N°2977 * @since 2.7.1 N°2977
*/ */
const LEVEL_DEFAULT = self::LEVEL_OK; public const LEVEL_DEFAULT = self::LEVEL_OK;
protected static $aLevelsPriority = array( protected static $aLevelsPriority = array(
self::LEVEL_ERROR => 400, self::LEVEL_ERROR => 400,
self::LEVEL_WARNING => 300, self::LEVEL_WARNING => 300,
self::LEVEL_INFO => 200, self::LEVEL_INFO => 200,
self::LEVEL_OK => 200, self::LEVEL_OK => 200,
self::LEVEL_DEBUG => 100, self::LEVEL_DEBUG => 100,
self::LEVEL_TRACE => 50, self::LEVEL_TRACE => 50,
); );
/**
* @var \Config attribute allowing to mock config in the tests
*/
protected static $m_oMockMetaModelConfig = null; protected static $m_oMockMetaModelConfig = null;
public static function Enable($sTargetFile) public static function Enable($sTargetFile)
@@ -567,7 +570,7 @@ abstract class LogAPI
static::$m_oFileLog = new FileLog($sTargetFile); static::$m_oFileLog = new FileLog($sTargetFile);
} }
public static function MockStaticObjects($oFileLog, $oMetaModelConfig=null) public static function MockStaticObjects($oFileLog, $oMetaModelConfig = null)
{ {
static::$m_oFileLog = $oFileLog; static::$m_oFileLog = $oFileLog;
static::$m_oMockMetaModelConfig = $oMetaModelConfig; static::$m_oMockMetaModelConfig = $oMetaModelConfig;
@@ -603,85 +606,115 @@ abstract class LogAPI
static::Log(self::LEVEL_TRACE, $sMessage, $sChannel, $aContext); static::Log(self::LEVEL_TRACE, $sMessage, $sChannel, $aContext);
} }
/**
* @throws \ConfigException if log wrongly configured
*/
public static function Log($sLevel, $sMessage, $sChannel = null, $aContext = array()) public static function Log($sLevel, $sMessage, $sChannel = null, $aContext = array())
{ {
if (! static::$m_oFileLog) if (!static::$m_oFileLog) {
{
return; return;
} }
if (! isset(self::$aLevelsPriority[$sLevel])) if (!isset(self::$aLevelsPriority[$sLevel])) {
{
IssueLog::Error("invalid log level '{$sLevel}'"); IssueLog::Error("invalid log level '{$sLevel}'");
return; return;
} }
if (is_null($sChannel)) if (is_null($sChannel)) {
{
$sChannel = static::CHANNEL_DEFAULT; $sChannel = static::CHANNEL_DEFAULT;
} }
$sMinLogLevel = self::GetMinLogLevel($sChannel); if (!static::IsLogLevelEnabled($sLevel, $sChannel)) {
if ($sMinLogLevel === false || $sMinLogLevel === 'false')
{
return; return;
} }
if (is_string($sMinLogLevel))
{
if (! isset(self::$aLevelsPriority[$sMinLogLevel]))
{
throw new Exception("invalid configuration for log_level '{$sMinLogLevel}' is not within the list: ".implode(',', array_keys(self::$aLevelsPriority)));
}
elseif (self::$aLevelsPriority[$sLevel] < self::$aLevelsPriority[$sMinLogLevel])
{
//priority too low regarding the conf, do not log this
return;
}
}
static::$m_oFileLog->$sLevel($sMessage, $sChannel, $aContext); static::$m_oFileLog->$sLevel($sMessage, $sChannel, $aContext);
} }
/** /**
* @param $sChannel * @throws \ConfigException if log wrongly configured
* * @uses GetMinLogLevel
* @return string one of the LEVEL_* const value
* @uses \LogAPI::LEVEL_DEFAULT
*/ */
private static function GetMinLogLevel($sChannel) final public static function IsLogLevelEnabled(string $sLevel, string $sChannel): bool
{ {
$oConfig = (static::$m_oMockMetaModelConfig !== null) ? static::$m_oMockMetaModelConfig : \MetaModel::GetConfig(); $sMinLogLevel = self::GetMinLogLevel($sChannel);
if (!$oConfig instanceof Config)
{ if ($sMinLogLevel === false || $sMinLogLevel === 'false') {
return static::LEVEL_DEFAULT; return false;
}
if (!is_string($sMinLogLevel)) {
return false;
}
if (!isset(self::$aLevelsPriority[$sMinLogLevel])) {
throw new ConfigException("invalid configuration for log_level '{$sMinLogLevel}' is not within the list: ".implode(',', array_keys(self::$aLevelsPriority)));
} elseif (self::$aLevelsPriority[$sLevel] < self::$aLevelsPriority[$sMinLogLevel]) {
return false;
}
return true;
}
/**
* @param string $sChannel
*
* @return string one of the LEVEL_* const value : the one configured it if exists, otherwise default log level for this channel
* Config can be done globally : `'log_level_min' => LogAPI::LEVEL_TRACE,`
* Or per channel : `'log_level_min' => ['InlineImage' => LogAPI::LEVEL_TRACE, 'UserRequest' => LogAPI::LEVEL_TRACE],`
*
* @uses \LogAPI::GetConfig()
* @uses \LogAPI::GetLevelDefault
*/
protected static function GetMinLogLevel($sChannel)
{
$oConfig = static::GetConfig();
if (!$oConfig instanceof Config) {
return static::GetLevelDefault();
} }
$sLogLevelMin = $oConfig->Get('log_level_min'); $sLogLevelMin = $oConfig->Get('log_level_min');
if (empty($sLogLevelMin)) if (empty($sLogLevelMin)) {
{ return static::GetLevelDefault();
return static::LEVEL_DEFAULT;
} }
if (!is_array($sLogLevelMin)) if (!is_array($sLogLevelMin)) {
{
return $sLogLevelMin; return $sLogLevelMin;
} }
if (isset($sLogLevelMin[$sChannel])) if (isset($sLogLevelMin[$sChannel])) {
{
return $sLogLevelMin[$sChannel]; return $sLogLevelMin[$sChannel];
} }
if (isset($sLogLevelMin[static::CHANNEL_DEFAULT])) if (isset($sLogLevelMin[static::CHANNEL_DEFAULT])) {
{
return $sLogLevelMin[$sChannel]; return $sLogLevelMin[$sChannel];
} }
return static::LEVEL_DEFAULT; return static::GetLevelDefault();
} }
/**
* @uses m_oMockMetaModelConfig if defined
* @uses \MetaModel::GetConfig()
*/
protected static function GetConfig(): ?Config
{
return static::$m_oMockMetaModelConfig ?? \MetaModel::GetConfig();
}
/**
* A method to override if default log level needs to be computed. Otherwise simply override the {@see LEVEL_DEFAULT} constant
*
* @used-by GetMinLogLevel
* @uses \LogAPI::LEVEL_DEFAULT
*
* @since 3.0.0 N°3731
*/
protected static function GetLevelDefault(): string
{
return static::LEVEL_DEFAULT;
}
} }
class SetupLog extends LogAPI class SetupLog extends LogAPI
@@ -765,10 +798,113 @@ class DeadLockLog extends LogAPI
} }
/**
* @since 3.0.0 N°3731
*/
class DeprecatedCallsLog extends LogAPI
{
public const ENUM_CHANNEL_PHP_METHOD = 'deprecated-php-method';
public const ENUM_CHANNEL_FILE = 'deprecated-file';
public const CHANNEL_DEFAULT = self::ENUM_CHANNEL_PHP_METHOD;
public const LEVEL_DEFAULT = self::LEVEL_ERROR;
/** @var \FileLog we want our own instance ! */
protected static $m_oFileLog = null;
public static function Enable($sTargetFile = null): void
{
if (empty($sTargetFile)) {
$sTargetFile = APPROOT.'log/deprecated-calls.log';
}
parent::Enable($sTargetFile);
}
protected static function GetLevelDefault(): string
{
if (utils::IsDevelopmentEnvironment()) {
return static::LEVEL_DEBUG;
}
return static::LEVEL_DEFAULT;
}
/**
* @throws \ConfigException
* @link https://www.php.net/debug_backtrace
* @uses \debug_backtrace()
*/
public static function NotifyDeprecatedFile(?string $sAdditionalMessage = null): void
{
if (!static::IsLogLevelEnabled(self::LEVEL_WARNING, self::ENUM_CHANNEL_FILE)) {
return;
}
$aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$sDeprecatedFile = $aStack[0]['file'];
if (array_key_exists(1, $aStack)) {
$sCallerFile = $aStack[1]['file'];
$sCallerLine = $aStack[1]['line'];
} else {
$sCallerFile = 'N/A';
$sCallerLine = 'N/A';
}
$sMessage = "{$sCallerFile} L{$sCallerLine} including/requiring {$sDeprecatedFile}";
if (!is_null($sAdditionalMessage)) {
$sMessage .= ' : '.$sAdditionalMessage;
}
static::Warning($sMessage, static::ENUM_CHANNEL_FILE);
}
/**
* @param string|null $sAdditionalMessage
*
* @throws \ConfigException
* @link https://www.php.net/debug_backtrace
* @uses \debug_backtrace()
*/
public static function NotifyDeprecatedPhpMethod(?string $sAdditionalMessage = null): void
{
if (!static::IsLogLevelEnabled(self::LEVEL_WARNING, self::ENUM_CHANNEL_PHP_METHOD)) {
return;
}
$aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
$sCallerObject = $aStack[2]['class'];
$sCallerMethod = $aStack[2]['function'];
$sCallerLine = $aStack[2]['line'];
$sDeprecatedObject = $aStack[1]['class'];
$sDeprecatedMethod = $aStack[1]['function'];
$sMessage = "{$sCallerObject}::{$sCallerMethod} L{$sCallerLine} calling {$sDeprecatedObject}:{$sDeprecatedMethod}";
if (!is_null($sAdditionalMessage)) {
$sMessage .= ' : '.$sAdditionalMessage;
}
static::Warning($sMessage, static::ENUM_CHANNEL_PHP_METHOD);
}
public static function Log($sLevel, $sMessage, $sChannel = null, $aContext = array()): void
{
if (utils::IsDevelopmentEnvironment()) {
trigger_error($sMessage, E_USER_DEPRECATED);
}
parent::Log($sLevel, $sMessage, $sChannel, $aContext);
}
}
class LogFileRotationProcess implements iScheduledProcess class LogFileRotationProcess implements iScheduledProcess
{ {
/** /**
* Cannot get this list from anywhere as log file name is provided by the caller using LogAPI::Enable * Cannot get this list from anywhere as log file name is provided by the caller using LogAPI::Enable
*
* @var string[] * @var string[]
*/ */
const LOGFILES_TO_ROTATE = array( const LOGFILES_TO_ROTATE = array(

View File

@@ -1427,6 +1427,7 @@ class WebPage implements Page
*/ */
protected function outputCollapsibleSectionInit() protected function outputCollapsibleSectionInit()
{ {
DeprecatedCallsLog::NotifyDeprecatedPhpMethod();
if (!$this->bHasCollapsibleSection) { if (!$this->bHasCollapsibleSection) {
return; return;
} }