environment('local') || app()->environment('production') || app()->environment('LOCAL') || app()->environment('DEV') ) { $this->sendSlackException($e); } } catch (\Throwable $ex) { // app()이 아직 사용 불가하거나 env 문제가 있을 때는 무시 } parent::report($e); // 로그는 그대로 } protected function sendSlackException(Throwable $e): void { try { $url = env('LOG_SLACK_WEBHOOK_URL'); if (! $url) { return; } $ip = request()?->ip() ?? 'N/A'; // 요청이 없는 경우 대비 Http::post($url, [ 'text' => "*[Laravel] 예외 발생!*\n". "• 메시지: `{$e->getMessage()}`\n". "• 위치: `{$e->getFile()}:{$e->getLine()}`\n". '• 시간: '.now()->toDateTimeString()."\n". "• IP: `{$ip}`", ]); } catch (Throwable $ex) { logger()->error('슬랙 전송 실패', ['message' => $ex->getMessage()]); } } public function render($request, Throwable $exception) { // API 라우트는 무조건 JSON 응답 (Accept 헤더 없어도) $isApiRoute = str_starts_with($request->path(), 'api/'); if ($request->expectsJson() || $isApiRoute) { // 422 Unprocessable Entity - Validation 실패 if ($exception instanceof ValidationException) { return response()->json([ 'success' => false, 'message' => '입력값 검증 실패', 'error' => [ 'code' => 422, 'details' => $exception->errors(), ], ], 422); } // 400 Bad Request if ($exception instanceof BadRequestHttpException) { return response()->json([ 'success' => false, 'message' => '잘못된 요청', 'data' => null, ], 400); } // 401 Unauthorized if ($exception instanceof AuthenticationException) { // 토큰 만료 여부 확인 $errorCode = null; $message = '인증 실패'; // Bearer 토큰이 있는 경우 만료 여부 확인 $bearerToken = $request->bearerToken(); if ($bearerToken) { $token = \Laravel\Sanctum\PersonalAccessToken::findToken($bearerToken); if ($token && $token->expires_at && $token->expires_at->isPast()) { $errorCode = 'TOKEN_EXPIRED'; $message = __('error.token_expired'); } } return response()->json([ 'success' => false, 'message' => $message, 'error_code' => $errorCode, 'data' => null, ], 401); } // 403 Forbidden if ($exception instanceof AccessDeniedHttpException) { return response()->json([ 'success' => false, 'message' => '권한 없음', 'data' => null, ], 403); } // 404 Not Found if ($exception instanceof NotFoundHttpException) { return response()->json([ 'success' => false, 'message' => '존재하지 않는 URI 또는 데이터', 'data' => null, ], 404); } // 405 Method Not Allowed if ($exception instanceof MethodNotAllowedHttpException) { return response()->json([ 'success' => false, 'message' => '허용되지 않는 메서드', 'data' => null, ], 405); } // 500 Internal Server Error (기타 모든 에러) if ( $exception instanceof HttpException && $exception->getStatusCode() === 500 ) { return response()->json([ 'success' => false, 'message' => '서버 에러', 'data' => null, ], 500); } // 그 외 모든 예외 return response()->json([ 'success' => false, 'message' => '서버 에러', 'data' => null, ], 500); } return parent::render($request, $exception); } }