我让Sentry在我的PHP应用程序中跟踪未捕获的异常,我注意到PDO中一个特殊的未捕获异常。代码如下所示:
/**
* @return boolean TRUE if the connection to the database worked; FALSE otherwise.
*/
public function verifyDatabase() {
try{
$this->pdo->query('SELECT 1');
return true;
}
catch (\PDOException $e) {
echo 'Lost connection to database: ' . $e->getMessage() . PHP_EOL;
return false;
}
}
这应该捕获“MySQL服务器已经消失”等错误,它确实可以在我的开发机器上运行。但是,Sentry最近记录了这个错误:
ErrorException PDO :: query():MySQL服务器已经消失
据哨兵说,这是由上面的$this->pdo->query('SELECT 1');
声明引起的。像这样的错误应该被try/catch
捕获。为什么PDO投掷ErrorException
而不是PDOException
?
我无法重现ErrorException。
$pdo = new PDO('mysql:host=127.0.0.1;dbname=test', ..., ...);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
sleep(20); // during this sleep, I stop my MySQL Server instance.
$result = $pdo->query("SELECT 1");
输出:
Warning: PDO::query(): MySQL server has gone away
Warning: PDO::query(): Error reading result set's header
Fatal error: Uncaught PDOException: SQLSTATE[HY000]: General error: 2006 MySQL server has gone away
Stack trace:
#0 /Users/bkarwin/Documents/SO/pdo.php(8): PDO->query('SELECT 1')
这表明它在服务器消失时抛出PDOException,而不是ErrorException。
使用MySQL 5.6.37和PHP 7.1.23进行测试。
我想知道你在问题中显示的代码是否实际上是部署的代码并将异常抛给Sentry。也许你有一些代码:
catch (\PDOException $e) {
throw ErrorException($e->getMessage());
}
在您的verifyDatabase()函数中,或者在调用verifyDatabase()的代码中。换句话说,当verifyDatabase()返回false时,您的应用程序会执行什么操作?
好吧,我想我已经弄明白了。看来this is related to a bug in which the PDO MySQL driver emits warnings even when they are supposed to be disabled(参见:this answer)。我相信Sentry然后将这些视为错误。
我终于能够通过修改Bill Karwin的测试脚本来复制并解决这个问题:
// Initialize the Sentry reporting client
$ravenClient = new \Raven_Client(SENTRY_KEY);
$ravenClient->install();
echo 'Connecting...' . PHP_EOL;
$pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo 'Connected. Waiting...' . PHP_EOL;
sleep(20); // during this sleep, I stop my MySQL Server instance.
echo 'Querying...' . PHP_EOL;
try {
$result = $pdo->query("SELECT 1");
}
catch(\PDOException $e) {
echo 'Caught PDOException ' . $e->getMessage() . PHP_EOL;
}
这将打印以下内容:
Connecting...
Connected. Waiting...
Querying...
PHP Warning: PDO::query(): MySQL server has gone away in /home/xxx/src/test.php on line 37
PHP Stack trace:
PHP 1. {main}() /home/xxx/src/test.php:0
PHP 2. PDO->query() /home/xxx/src/test.php:37
Caught PDOException SQLSTATE[HY000]: General error: 2006 MySQL server has gone away
Done.
而Sentry将在ErrorException
线上记录一个query()
。
我能够通过实施jferrer在the PHP bug report上发布的“解决方案”来解决这个问题。
// Convert NOTICE, WARNING, ... in Exceptions
$convertErrorToException = function ($level, $message, $file, $line){
throw new ErrorException($message, 0, $level, $file, $line);
};
// The $previousErrorHandler will contain Sentry's handler
$previousErrorHandler = set_error_handler($convertErrorToException);
try {
$result = $pdo->query("SELECT 1");
}
catch(\PDOException $e) {
echo 'Caught PDOException ' . $e->getMessage() . PHP_EOL;
}
catch(\ErrorException $e) {
echo 'Caught ErrorException ' . $e->getMessage() . PHP_EOL;
}
// Restore Sentry as the default handler
set_error_handler($previousErrorHandler);
这导致抛出并捕获ErrorException:
Connecting...
Connected. Waiting...
Querying...
Caught ErrorException PDO::query(): MySQL server has gone away
Done.