「to後端」在業務運營過程中,我們希望能夠主動的瞭解應用系統存在的bug和錯誤,以便進行迭代優化,提升系統的健壯性。在系統運維過程中,日誌的管理非常重要,需要進行規範化的日誌管理,以便進行故障分析、錯誤排查和問題回溯。本文針對laravel框架,介紹日誌管理規範。
一、設計思路
以接口調用為例進行說明,每次用戶請求,分配請求流水,存入session,在本次請求過程中輸出的日誌都加入請求流水標識,在工具類中封裝日誌打印方法,從請求進入到報文返回,均帶流水號,以便進行日誌查詢。
二、實例展示
以下為測試環境中的實例展示:
紅色框:LOG類型(Request:請求接口;Process:過程接口;Error:代碼錯誤;Back:返回接口);
黃色框:流水號;
藍色框:過程接口中的過程描述;
綠色框:動作控制器;
紅線:數據;
白框:錯誤返回信息
三、實現方法
以laravel為例,實現方案如下:
- Utils文件添加以下方法:
/** * 請求接口LOG * @param string $logPath 請求接口 * @param string $logIp IP地址 * @param array $logData 請求參數 */ public static function requestLog($logPath="",$logIp="",$logData=[]){ $LOGO_NO = 'LOG'.date('Ymdhis',time()).rand(1000000,10000000); Session::put('LOGO_NO', $LOGO_NO); Log::info('[Request] '.$LOGO_NO.' '. $logPath . "(" . $logIp . ") " .json_encode($logData)); } /** * 過程中接口LOG * @param string $logModular 模塊 * @param string/array $logData 數據 * @param string $logContent 備註 */ public static function processLog($logModular="", $logContent="", $logData=""){ $LOGO_NO = Session::get("LOGO_NO"); if(is_array($logData)){ $logData = json_encode($logData,true); } if($logContent){ Log::info('[Process] '.$LOGO_NO.' '.$logContent.' '.$logModular .' ' . $logData ); } else{ Log::info('[Process] '.$LOGO_NO.' '.$logModular .' ' . $logData ); } } /** * 過程報錯接口LOG * @param string/array $logData 數據 */ public static function errorLog($logData=""){ $LOGO_NO = Session::get("LOGO_NO"); if(!$LOGO_NO){ $LOGO_NO = 'LOG'.date('Ymdhis',time()).rand(1000000,10000000); Session::put('LOGO_NO', $LOGO_NO); } if(is_array($logData)){ $logData = json_encode($logData,true); } Log::info('[Error] '.$LOGO_NO.' '. $logData ); Session::remove("LOGO_NO"); } /** * 返回接口LOG * @param string $logModular 模塊 * @param string/array $logData 數據 */ public static function backLog($logModular="", $logData=[]){ $LOGO_NO = Session::get("LOGO_NO"); $log = array( 'code' => $logData['code'], 'result' => $logData['result'], 'message' => $logData['message'], ); if(array_key_exists('ret',$logData)){ $log['ret'] = $logData['ret']; } Log::info('[Back] '.$LOGO_NO.' '. $logModular .' ' .json_encode($log,true)); Session::remove("LOGO_NO"); } /** * 自定義LOG * @param string $label log標籤 * @param string $logContent 備註 * @param string/array $logData 數據 */ public static function customLog($label="DEBUG", $logContent="", $logData=""){ $LOGO_NO = Session::get("LOGO_NO"); if(!$LOGO_NO){ $LOGO_NO = 'LOG'.date('Ymdhis',time()).rand(1000000,10000000); Session::put('LOGO_NO', $LOGO_NO); } if(is_array($logData)){ // 將數組轉為字符串 $logDataArray = $logData; $logData = ''; foreach ($logDataArray as $key=>$logDataRow){ if(is_array($logDataRow)){ $logDataRow = json_encode($logDataRow,true); } $str=$key.":".$logDataRow; $logData.=$str.' '; } } if($logContent){ Log::info('['.$label.'] '.$LOGO_NO.' '.$logContent.' '. $logData ); } else{ Log::info('['.$label.'] '.$LOGO_NO.' '. $logData ); } Session::remove("LOGO_NO"); }
- 調用:
- 入參時調用(中間件BeforeRequest)
public function handle($request, Closure $next) { Utils::requestLog($request->getPathInfo(), $request->getClientIp(), $request->all()); return $next($request); }
- 過程中調用,例如:
public function recommendUsers(Request $request) { Utils::processLog(__METHOD__, '測試'); // 1 $data = Utils::requestParameter($request); $user_id = $data['user_id']; $language = $data['language']; Utils::processLog(__METHOD__, '',$language); // 2 Utils::processLog(__METHOD__, '',[$language,$user_id]); // 3 Utils::processLog(__METHOD__,'我是測試', [$language,$user_id]); // 4 //推薦騎士 $users = self::getUsersWithOutNotice($user_id, $language); return ApiResponse::makeResponse(true, $users, ApiResponse::SUCCESS_CODE, $data['language']); }
- 代碼錯誤時(/app/Exceptions/Handler.php)
public function report(Exception $exception) { Utils::errorLog($exception); //添加此行代碼 parent::report($exception); }
- 返回時調用(ApiResponse):
//以上為省略代碼 Utils::backLog(__METHOD__, $rsp); return response()->json($rsp);
- 5.自定義LOG,以檢測到錯誤路由為例:
- (/vendor/laravel/framework/src/Illuminate/Routing/RouteCollection.php)
public function match(Request $request) { $routes = $this->get($request->getMethod()); // First, we will see if we can find a matching route for this current request // method. If we can, great, we can just return it so that it can be called // by the consumer. Otherwise we will check for routes with another verb. $route = $this->matchAgainstRoutes($routes, $request); if (! is_null($route)) { return $route->bind($request); } /////////////LOG-START//////////////////// $logData=array( 'route'=>$request->getPathInfo(), 'method'=>$request->getMethod(), 'Info'=>$request ); Utils::customLog("ErrorNotFoundHttpException","路由錯誤",$logData); /////////////LOG-END//////////////////// // If no route was found we will now check if a matching route is specified by // another HTTP verb. If it is we will need to throw a MethodNotAllowed and // inform the user agent of which HTTP verb it should use for this route. $others = $this->checkForAlternateVerbs($request); if (count($others) > 0) { return $this->getRouteForMethods($request, $others); } throw new NotFoundHttpException; }
四、後記
應用日誌是排查故障、發展隱患、回溯問題的重要工具,本次主要講解應用系統的日誌輸出方案,每天應該進行日誌巡檢(tail -f -n 100000 laravel.log | grep Error),檢索錯誤信息,及時優化業務系統,提升穩定性和健壯性。