이 글은 PHP5에서의 오류 처리 및 문제 위치에 대한 소개를 제공합니다(코드 예제). 도움이 필요한 친구들이 참고할 수 있기를 바랍니다.
PHP에서 E_ERROR 수준의 치명적인 런타임 오류가 발생했을 때 문제를 찾는 방법에 대해 이야기해 보겠습니다. 예를 들어 치명적인 오류: 허용된 크기의 메모리
메모리 오버플로입니다. 이런 종류의 오류가 발생하면 프로그램이 바로 종료됩니다. 오류가 보고된 특정 파일과 코드 줄 수를 나타내는 오류 로그가 PHP 오류 로그에 기록되며 다른 정보는 손실되지 않습니다. PHP7이라면 예외와 같은 오류를 잡을 수 있지만 PHP5에서는 그렇지 않습니다. Fatal error: Allowed memory size of
内存溢出这种。当出现这种错误时会导致程序直接退出,PHP的error log中会记录一条错误日志说明报错的具体文件和代码行数,其它的任何信息都没有了。如果是PHP7的话还可以像捕获异常一样捕获错误,PHP5的话就不行了。
一般想到的方法就是看看报错的具体代码,如果报错文件是CommonReturn.class.php像下面这个样子。
<?php /** * 公共返回封装 * Class CommonReturn */ class CommonReturn { /** * 打包函数 * @param $params * @param int $status * * @return mixed */ static public function packData($params, $status = 0) { $res['status'] = $status; $res['data'] = json_encode($params); return $res; } }
其中json_encode那一行报错了,然后你查了下packData这个方法,有很多项目的类中都有调用,这时要怎么定位问题呢?
场景复现
好,首先我们复现下场景。假如实际调用的程序bug.php如下
<?php require_once './CommonReturn.class.php'; $res = ini_set('memory_limit', '1m'); $res = []; $char = str_repeat('x', 999); for ($i = 0; $i < 900 ; $i++) { $res[] = $char; } $get_pack = CommonReturn::packData($res); // something else
运行bug.php PHP错误日志中会记录
[08-Jan-2019 11:22:52 Asia/Shanghai] PHP Fatal error: Allowed memory size of 1048576 bytes exhausted (tried to allocate 525177 bytes) in /CommonReturn.class.php on line 20
复现成功,错误日志中只是说明了报错的文件和哪行代码,无法知道程序的上下文堆栈信息,不知道具体是哪块业务逻辑调用的,这样一来就无法定位修复错误。如果是偶尔出现,并且没有来自前端业务的反馈要怎么排查呢。
解决思路
1、有人想到了修改memory_limit增加内存分配,但这种方法治标不治本。做开发肯定要找到问题的根源。
2、开启core dump,如果生成code文件可以进行调试,但是发现code只有进程异常退出才会生成。像E_ERROR级别的错误不一定会生成code文件,内存溢出这种可能PHP内部自己就处理了。
3、使用register_shutdown_function注册一个PHP终止时的回调函数,再调用error_get_last如果获取到了最后发生的错误,就通过debug_print_backtrace获取程序的堆栈信息,我们试试看。
修改CommonReturn.class.php文件如下
<?php /** * 公共返回封装 * Class CommonReturn */ class CommonReturn { /** * 打包函数 * @param $params * @param int $status * * @return mixed */ static public function packData($params, $status = 0) { register_shutdown_function(['CommonReturn', 'handleFatal']); $res['status'] = $status; $res['data'] = json_encode($params); return $res; } /** * 错误处理 */ static protected function handleFatal() { $err = error_get_last(); if ($err['type']) { ob_start(); debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5); $trace = ob_get_clean(); $log_cont = 'time=%s' . PHP_EOL . 'error_get_last:%s' . PHP_EOL . 'trace:%s' . PHP_EOL; @file_put_contents('/tmp/debug_' . __FUNCTION__ . '.log', sprintf($log_cont, date('Y-m-d H:i:s'), var_export($err, 1), $trace), FILE_APPEND); } } }
再次运行bug.php,日志如下。
error_get_last:array ( 'type' => 1, 'message' => 'Allowed memory size of 1048576 bytes exhausted (tried to allocate 525177 bytes)', 'file' => '/CommonReturn.class.php', 'line' => 23, ) trace:#0 CommonReturn::handleFatal()
回溯信息没有来源,尴尬了。猜测因为backtrace信息保存在内存中,当出现致命错误时会清空。没办法,把backtrace从外面传进来试试。再次修改CommonReturn.class.php。
<?php /** * 公共返回封装 * Class CommonReturn */ class CommonReturn { /** * 打包函数 * @param $params * @param int $status * * @return mixed */ static public function packData($params, $status = 0) { ob_start(); debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5); $trace = ob_get_clean(); register_shutdown_function(['CommonReturn', 'handleFatal'], $trace); $res['status'] = $status; $res['data'] = json_encode($params); return $res; } /** * 错误处理 * @param $trace */ static protected function handleFatal($trace) { $err = error_get_last(); if ($err['type']) { $log_cont = 'time=%s' . PHP_EOL . 'error_get_last:%s' . PHP_EOL . 'trace:%s' . PHP_EOL; @file_put_contents('/tmp/debug_' . __FUNCTION__ . '.log', sprintf($log_cont, date('Y-m-d H:i:s'), var_export($err, 1), $trace), FILE_APPEND); } } }
再次运行bug.php
error_get_last:array ( 'type' => 1, 'message' => 'Allowed memory size of 1048576 bytes exhausted (tried to allocate 525177 bytes)', 'file' => '/CommonReturn.class.php', 'line' => 26, ) trace:#0 CommonReturn::packData() called at [/bug.php:13]
장면 재현
rrreee
bug.php를 실행하면 PHP 에러 로그에1. 어떤 사람들은 메모리 할당을 늘리기 위해 memory_limit를 수정하려고 생각했지만 이 방법은 증상을 치료할 뿐 근본 원인을 치료하지는 않습니다. 개발을 할 때에는 문제의 근본 원인을 찾아야 합니다.
3. PHP 종료 시 콜백 함수를 등록하기 위해 Register_shutdown_function을 사용하고, 마지막으로 발생한 오류가 발생하면 debug_print_backtrace를 사용하여 프로그램의 스택 정보를 얻으십시오.
rrreee
트레이스백 정보에는 출처가 없어 당황스럽습니다. 역추적 정보는 메모리에 저장했다가 치명적인 오류가 발생하면 지워지기 때문인 것 같아요. 다른 방법은 없습니다. 외부에서 역추적을 전달해 보세요. CommonReturn.class.php를 다시 수정하세요. #🎜🎜#rrreee#🎜🎜#bug.php
를 다시 실행하면 로그는 다음과 같습니다. #🎜🎜#rrreee#🎜🎜#bug.php의 13번째 줄에서 호출 소스를 성공적으로 찾았습니다. 최종 CommonReturn.class.php를 프로덕션 환경에 게시하고, 다시 오류가 발생하면 로그를 살펴보세요. 그러나 이 경우 packData를 호출하는 모든 프로그램은 추적 기능을 실행하므로 성능에 확실히 영향을 미칩니다. #🎜🎜##🎜🎜##🎜🎜#Summary#🎜🎜##🎜🎜##🎜🎜##🎜🎜##🎜🎜#사용된 Register_shutdown_function 함수에 주의해야 합니다. 여러 개의 다른 콜백을 등록할 수 있습니다. 하지만 특정 콜백 함수가 종료되면 나중에 등록된 콜백 함수는 모두 실행되지 않습니다. #🎜🎜##🎜🎜##🎜🎜##🎜🎜#debug_print_backtrace 역추적 정보를 얻는 첫 번째 함수에는 요청 매개변수가 포함됩니다. 두 번째 함수는 역추적 레코드 레이어 수를 저장하기 위한 요청 매개변수를 반환하지 않습니다. 약간의 돈이 필요하며 요청 매개변수가 큰 경우 이 함수를 호출하면 메모리가 직접 오버플로될 수 있습니다. #🎜🎜##🎜🎜##🎜🎜##🎜🎜##🎜🎜##🎜🎜##🎜🎜##🎜🎜##🎜🎜#가장 좋은 방법은 PHP7을 업그레이드하는 것인데, 예외와 같은 오류를 잡을 수 있습니다. . #🎜🎜##🎜🎜##🎜🎜##🎜🎜##🎜🎜##🎜🎜#위 내용은 PHP5에서의 오류 처리 및 문제 위치 소개(코드 예)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!