PHP框架(如:laravel、yii2、thinkPHP5)中统一异常处理及统一日志打印
背景: 现在写接口服务应用有一个很通用的需求,想通过日志、或者监控的形式监测的接口的运行情况,比如耗时、请求参数、响应结果。和前端联调接口时或者排查线上问题时日志必不可少,特别是现场日志。 应用运行时抛出异常时如何处理记录对应的日志。
统一处理及在一个地方一起处理异常及日志
以前开发喜欢在控制器方法内捕获异常及记录相关的异常日志😳,各种混乱。
现在的框架大多都提供了异常处理类(AOP面向切面的思想很流行)
如:laravel下的在这个位置
./app/Exceptions/Handler.php
只需要在 render() 方法中捕获对应的异常就行了,比如在异常抛出的地方记录现场日志,然后抛出对应的异常,控制器无需再捕获异常,直接交给统一异常处理,因为是做API开发,所以经常会输出(返回)html代码给接口调用方,对接口调用方极度的不友好,所以在非开发模式下就返回json格式的异常响应
/** * Render an exception into an HTTP response. * * @param \Illuminate\Http\Request $request * @param \Exception $exception * @return \Illuminate\Http\Response */ public function render($request, Exception $exception) { if ($exception instanceof ValidationException) { $msg = ''; foreach ($exception->validator->getMessageBag()->getMessages() as $message) { $msg .= $message[0] . ";"; } return ResponseService::error($msg, $exception->getCode()); } elseif ($exception instanceof BusinessException) { return ResponseService::error($exception->getMessage()); } elseif ($exception instanceof NotFoundHttpException) { return ResponseService::error('404 not found', $exception->getCode()); } else { // 开发调试模式输出页面信息 if (config('app.debug')) { return parent::render($request, $exception); } // 此处可以记录抛出异常的日志 return ResponseService::error($exception->getMessage(), $exception->getCode()); } }
上面代码就引出了response封装的一个对象了,在控制器返回时直接调用封装的responseService对象方法,返回给客户端的只有成功和失败两种情况,因为是接口,所以返回时顺便记录下来客户端的请求参数及返回参数
<?php /** * Created by PhpStorm. * User: liugx * Date: 18/1/14 * Time: 下午6:17 */ namespace App\Services; use App\Services\Response\ResponseBase; use Illuminate\Support\Facades\Log; class ResponseService extends ResponseBase { /** * 返回正确的json格式 * * @param string $data * @param string $msg * @return \Illuminate\Http\JsonResponse */ public static function success($data = '', string $msg = '') { $errorNo = 0; $res = [ 'code' => $errorNo, 'msg' => empty($msg) ? self::CODE[$errorNo] : $msg, 'data' => $data ]; $res = json_encode($res); self::writeLog($errorNo, $res); return Response($res); } /** * 错误返回json格式 * * @param string $msg * @param int $errorNo * @return \Illuminate\Http\JsonResponse */ public static function error(string $msg = '', $errorNo = 1) { if (!is_numeric($errorNo)) { $errorNo = 1; } $code = empty($errorNo) ? 1 : $errorNo; $res = [ 'code' => $code, 'msg' => !empty($msg) ? $msg : (isset(self::CODE[$errorNo]) ? self::CODE[$errorNo] : "操作失败!") ]; $res = json_encode($res); self::writeLog($code, $res); return Response($res); } /** * response记录接口请求的日志 * * @param $handleCode * @param $response */ private static function writeLog($handleCode, $response) { // 有代理ip填代理ip,没有填客户端ip $ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR']; $sessionId = !isset($_COOKIE['wechat_session']) ? '' : substr($_COOKIE['wechat_session'], 0, 16); $uri = $_SERVER['REQUEST_URI']; $startTime = $_SERVER['REQUEST_TIME_FLOAT']; $nowTime = microtime(true); $consumeTime = bcsub($nowTime, $startTime, 3) * 1000; $resData = [ 'req' => $_REQUEST ]; // 只有错误请求时,才将响应包写入日志 if ($handleCode != 0) { $resData['res'] = $response; } Log::info("「sessionId:{$sessionId}」「请求:{$uri}」「耗时:{$consumeTime}ms」「ip:{$ip}」", $resData); } }
ResponseBase里面是一些 返回码和提示语
<?php /** * Created by PhpStorm. * User: liugx * Date: 18/1/14 * Time: 下午6:21 */ namespace App\Services\Response; abstract class ResponseBase { const CODE = [ -1 => '操作未授权', 0 => '操作成功', 1 => '操作失败', ]; }
这样就能够对异常及接口请求日志做统一的处理了,至于接口里面涉及到的请求第三方接口日志需要在单独封装并记录日志