diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 03bbaae..8fe3948 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -8,6 +8,8 @@ use App\Services\Auth\AuthService; use App\Services\Auth\Contracts\AuthServiceInterface; use App\Services\User\Contracts\UserServiceInterface; use App\Services\User\UserService; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Log; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider @@ -21,6 +23,17 @@ class AppServiceProvider extends ServiceProvider public function boot(): void { - // + if (config('app.debug')) { + DB::listen(function ($query) { + Log::channel('query')->info( + sprintf( + "[%s ms] %s | Bindings: %s", + $query->time, + $query->sql, + json_encode($query->bindings) + ) + ); + }); + } } } diff --git a/config/logging.php b/config/logging.php index b09cb25..4f4a77d 100644 --- a/config/logging.php +++ b/config/logging.php @@ -63,6 +63,7 @@ return [ 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), 'replace_placeholders' => true, + 'permission' => 0666, ], 'daily' => [ @@ -71,6 +72,7 @@ return [ 'level' => env('LOG_LEVEL', 'debug'), 'days' => env('LOG_DAILY_DAYS', 14), 'replace_placeholders' => true, + 'permission' => 0666, ], 'slack' => [ @@ -123,6 +125,13 @@ return [ 'handler' => NullHandler::class, ], + 'query' => [ + 'driver' => 'single', + 'path' => storage_path('logs/query.log'), + 'level' => 'debug', + 'permission' => 0666, + ], + 'emergency' => [ 'path' => storage_path('logs/laravel.log'), ], diff --git a/docker/php/entrypoint.sh b/docker/php/entrypoint.sh index c9d0ab9..41971f4 100644 --- a/docker/php/entrypoint.sh +++ b/docker/php/entrypoint.sh @@ -13,6 +13,8 @@ mkdir -p storage/logs \ storage/framework/views \ bootstrap/cache +touch storage/logs/laravel.log storage/logs/query.log +chmod 666 storage/logs/laravel.log storage/logs/query.log chmod -R 777 storage bootstrap/cache exec "$@" diff --git a/docs/system-error-pages.md b/docs/system-error-pages.md new file mode 100644 index 0000000..8c7b408 --- /dev/null +++ b/docs/system-error-pages.md @@ -0,0 +1,124 @@ +# Tài liệu cấu hình và sử dụng System Error Pages + +Tài liệu này hướng dẫn chi tiết cách hoạt động, các trường hợp sử dụng, cách kích hoạt (trigger) và kiểm thử các trang lỗi hệ thống trong dự án **thankcard-system**. + +--- + +## 1. Danh sách các trang lỗi & Tình huống sử dụng + +| Status Code | Tên lỗi | Tình huống sử dụng | Key Localization (`errors.php`) | +| :--- | :--- | :--- | :--- | +| **401** | Unauthorized | Người dùng chưa đăng nhập nhưng cố truy cập tài nguyên yêu cầu xác thực. | `errors.401_title` / `errors.401_message` | +| **403** | Forbidden | Người dùng đã đăng nhập nhưng không có quyền hạn thực hiện hành động này (ví dụ: User thường cố truy cập chức năng Admin). | `errors.403_title` / `errors.403_message` | +| **404** | Not Found | Đường dẫn (URL) không tồn tại, hoặc khi tìm dữ liệu bằng Model binding/Query không thấy mà muốn trả về lỗi. | `errors.404_title` / `errors.404_message` | +| **419** | Session Expired | Phiên làm việc hết hạn hoặc token CSRF không hợp lệ/hết hạn khi gửi Form (POST/PUT/DELETE). | `errors.419_title` / `errors.419_message` | +| **429** | Too Many Requests | Người dùng/IP gửi quá nhiều yêu cầu trong thời gian ngắn (vượt giới hạn Rate Limit của Middleware). | `errors.429_title` / `errors.429_message` | +| **500** | Internal Error | Lỗi xảy ra từ phía máy chủ hoặc do code PHP bị lỗi crash, kết nối cơ sở dữ liệu thất bại... | `errors.500_title` / `errors.500_message` | +| **503** | Service Unavailable | Hệ thống đang được bảo trì khi chạy lệnh `php artisan down`. | `errors.503_title` / `errors.503_message` | + +--- + +## 2. Cách kích hoạt (Trigger) trong Laravel + +Trong Laravel, bạn có thể trigger các trang lỗi này bằng nhiều cách khác nhau tùy thuộc vào ngữ cảnh. + +### 2.1. Sử dụng hàm `abort()` (Khuyên dùng trong Controller/Service) +Hàm `abort()` sẽ ném ra một `HttpException` và Laravel sẽ tự động bắt lấy để render trang lỗi tương ứng nằm trong `resources/views/errors/{status}.blade.php`. + +```php +// Ví dụ kiểm tra quyền trong Controller +if ($user->role !== 'admin') { + abort(403, 'Bạn không có quyền quản trị viên.'); +} + +// Ví dụ không tìm thấy tài nguyên +$card = Card::find($id); +if (!$card) { + abort(404, 'Không tìm thấy thẻ cảm ơn.'); +} +``` + +### 2.2. Sử dụng lệnh bảo trì hệ thống (Maintenance Mode) +Kích hoạt lỗi **503** bằng cách đưa ứng dụng vào trạng thái bảo trì: +```bash +php artisan down +``` +Để tắt chế độ bảo trì và đưa hệ thống hoạt động bình thường: +```bash +php artisan up +``` + +### 2.3. Sử dụng Middleware (Rate Limiting cho lỗi 429) +Laravel tích hợp sẵn middleware `throttle`. Bạn có thể áp dụng nó trong file Routes (`routes/web.php` hoặc `routes/api.php`): +```php +Route::middleware('throttle:60,1')->group(function () { + Route::get('/send-card', [UserController::class, 'send']); +}); +``` +*(Nếu một IP gọi quá 60 lần/phút, Laravel sẽ tự động ném ra lỗi 429 và render trang 429)* + +--- + +## 3. Ví dụ triển khai thực tế + +### Trong Controller/Service: +```php +namespace App\Http\Controllers; + +use App\Services\User\Contracts\UserServiceInterface; +use Illuminate\Http\Request; + +class UserController extends Controller +{ + protected $userService; + + public function __construct(UserServiceInterface $userService) + { + $this->userService = $userService; + } + + public function show($id) + { + $user = $this->userService->findUserById($id); + + if (!$user) { + // Tự động render view errors/404.blade.php + abort(404); + } + + return view('user.profile', compact('user')); + } +} +``` + +--- + +## 4. Checklist kiểm thử các trang lỗi + +Để chắc chắn các trang lỗi hoạt động chính xác và hiển thị đẹp mắt, hãy tiến hành kiểm thử theo checklist sau: + +- [ ] **Test 401 Unauthorized:** + - Tạm thời gỡ bỏ middleware auth ở một route và ném `abort(401)`. + - Kiểm tra xem có hiển thị nút **"Đi đến trang Đăng Nhập"** hay không. +- [ ] **Test 403 Forbidden:** + - Đăng nhập tài khoản User thường, sau đó truy cập một route dành riêng cho Admin hoặc ném `abort(403)`. + - Kiểm tra xem nút **"Quay lại Trang Chủ"** có hoạt động chính xác không. +- [ ] **Test 404 Not Found:** + - Truy cập một đường dẫn ngẫu nhiên không khai báo trong route (ví dụ: `http://localhost:8888/path-does-not-exist`). + - Kiểm tra giao diện và nút **"Quay lại Trang Chủ"**. +- [ ] **Test 419 Session Expired:** + - Mở một trang có form POST, xóa trường `_token` ẩn (CSRF) trong thẻ `