diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php index e6a47e438..8b8bdef71 100644 --- a/core/cmdbsource.class.inc.php +++ b/core/cmdbsource.class.inc.php @@ -119,7 +119,18 @@ class CMDBSource const ENUM_DB_VENDOR_MYSQL = 'MySQL'; const ENUM_DB_VENDOR_MARIADB = 'MariaDB'; const ENUM_DB_VENDOR_PERCONA = 'Percona'; - + + /** + * Error: 1205 SQLSTATE: HY000 (ER_LOCK_WAIT_TIMEOUT) + * Message: Lock wait timeout exceeded; try restarting transaction + */ + const MYSQL_ERRNO_WAIT_TIMEOUT = 1205; + /** + * Error: 1213 SQLSTATE: 40001 (ER_LOCK_DEADLOCK) + * Message: Deadlock found when trying to get lock; try restarting transaction + */ + const MYSQL_ERRNO_DEADLOCK = 1213; + protected static $m_sDBHost; protected static $m_sDBUser; protected static $m_sDBPwd; @@ -683,55 +694,45 @@ class CMDBSource return $oResult; } + /** + * @param \Exception $e + * + * @since 2.7.1 + */ private static function LogDeadLock(Exception $e) { - // Deadlock detection + // checks MySQL error code $iMySqlErrorNo = self::$m_oMysqli->errno; - if (($iMySqlErrorNo == 1213) || ($iMySqlErrorNo == 1205)) + if (!in_array($iMySqlErrorNo, array(self::MYSQL_ERRNO_WAIT_TIMEOUT, self::MYSQL_ERRNO_DEADLOCK))) { - // Try to log the deadlock - $oError = self::$m_oMysqli->query("SHOW ENGINE INNODB STATUS"); - $aData = array(); - if ($oError !== false) - { - $aData = $oError->fetch_all(MYSQLI_ASSOC); - } - if (MetaModel::IsLogEnabledIssue()) - { - if (MetaModel::IsValidClass('EventIssue')) - { - try - { - $bInTransaction = self::IsInsideTransaction(); - if ($bInTransaction) - { - // In order to log the error, we have to rollback the current transaction - // Else the caller will rollback our log - self::DBQuery('ROLLBACK'); - } - $oLog = new EventIssue(); - $oLog->Set('message', $e->getMessage()); - $oLog->Set('userinfo', UserRights::GetUser()); - $oLog->Set('issue', 'Database DeadLock'); - $oLog->Set('impact', 'Request execution failed'); - $oLog->Set('callstack', $e->getTrace()); - $oLog->Set('data', $aData[0]); - $oLog->DBInsertNoReload(); - if ($bInTransaction) - { - self::DBQuery('START TRANSACTION'); - } - } - catch(Exception $e1) - { - IssueLog::Error("Failed to log database deadlock issue into the DB\n".$e1->getMessage()); - } - } - } - IssueLog::Error($e->getMessage()); - IssueLog::Error(print_r($aData[0], true)); + return; } + // Get error info + $sUser = UserRights::GetUser(); + $oError = self::$m_oMysqli->query('SHOW ENGINE INNODB STATUS'); + if ($oError !== false) + { + $aData = $oError->fetch_all(MYSQLI_ASSOC); + $sInnodbStatus = $aData[0]; + } + else + { + $sInnodbStatus = 'Get status query cannot execute'; + } + + // log ! + $sMessage = "deadlock detected: user= $sUser; errno=$iMySqlErrorNo"; + $aLogContext = array( + 'userinfo' => $sUser, + 'errno' => $iMySqlErrorNo, + 'ex_msg' => $e->getMessage(), + 'callstack' => $e->getTraceAsString(), + 'data' => $sInnodbStatus, + ); + DeadLockLog::Info($sMessage, $iMySqlErrorNo, $aLogContext); + + IssueLog::Error($sMessage, 'DeadLock', $e->getMessage()); } /** diff --git a/core/log.class.inc.php b/core/log.class.inc.php index 135599da1..1590d26e3 100644 --- a/core/log.class.inc.php +++ b/core/log.class.inc.php @@ -704,6 +704,59 @@ class ToolsLog extends LogAPI protected static $m_oFileLog = null; } +/** + * @see \CMDBSource::LogDeadLock() + * @since 2.7.1 + */ +class DeadLockLog extends LogAPI +{ + const CHANNEL_WAIT_TIMEOUT = 'Deadlock-WaitTimeout'; + const CHANNEL_DEADLOCK_FOUND = 'Deadlock-Found'; + const CHANNEL_DEFAULT = self::CHANNEL_WAIT_TIMEOUT; + + /** @var \FileLog we want our own instance ! */ + protected static $m_oFileLog = null; + + public static function Enable($sTargetFile = null) + { + if (empty($sTargetFile)) + { + $sTargetFile = APPROOT.'log/deadlocks.log'; + } + parent::Enable($sTargetFile); + } + + private static function GetChannelFromMysqlErrorNo($iMysqlErrorNo) + { + switch ($iMysqlErrorNo) + { + case 1205: + return self::CHANNEL_WAIT_TIMEOUT; + break; + case 1213: + return self::CHANNEL_DEADLOCK_FOUND; + break; + default: + return self::CHANNEL_DEFAULT; + break; + } + } + + /** + * @param int $iMySQLErrNo will be converted to channel using {@link GetChannelFromMysqlErrorNo} + * @param string $sMessage + * @param null $iMysqlErroNo + * @param array $aContext + * + * @throws \Exception + */ + public static function Log($iMySQLErrNo, $sMessage, $iMysqlErroNo = null, $aContext = array()) + { + $sChannel = self::GetChannelFromMysqlErrorNo($iMysqlErroNo); + parent::Log($iMySQLErrNo, $sMessage, $sChannel, $aContext); + } +} + class LogFileRotationProcess implements iScheduledProcess { diff --git a/core/metamodel.class.php b/core/metamodel.class.php index d63cb6d14..50a46c0fa 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -6371,6 +6371,7 @@ abstract class MetaModel self::$m_bLogWebService = self::$m_oConfig->GetLogWebService(); ToolsLog::Enable(APPROOT.'log/tools.log'); + DeadLockLog::Enable(); } else {