یک عادت قدیمی داریم: وقتی چیزی روی سرور خراب می‌شود، APP_DEBUG را روشن می‌کنیم تا ببینیم چه خبر است. روی این سرور، همین عادت باعث شد دو ساعت دنبال خطای اشتباهی بگردیم.

این نوشته توضیح می‌دهد چرا، و چه کاری به‌جایش باید کرد.

علامت: خطایی که هیچ ربطی ندارد#

سایت خطای ۵۰۰ می‌داد. APP_DEBUG را روشن کردیم تا صفحه‌ی خطای مفصل لاراول را ببینیم. چیزی که آمد یک خطای عجیب درباره‌ی خودِ سازوکار نمایش خطا بود — نه یک کلمه درباره‌ی کدی که ما نوشته بودیم.

هر تغییری در کد می‌دادیم، همان خطا برمی‌گشت. انگار کد اصلاً اجرا نمی‌شد.

علت: رندرکننده‌ی خطا خودش می‌ترکد#

وقتی APP_DEBUG روشن است، لاراول یک صفحه‌ی خطای زیبا نشان می‌دهد: پیام، ردیابی پشته، و قطعه‌کدِ محلی که خطا رخ داده با رنگ‌آمیزی نحوی.

آن قطعه‌کد رنگی از کجا می‌آید؟ از توابعی که کد PHP را با رنگ‌آمیزی چاپ می‌کنند — همان خانواده‌ای که highlight_file عضوش است.

و روی این سرور، highlight_file در فهرست disable_functions بود.

زنجیره‌ی اتفاق:

  1. کد شما خطا می‌دهد. این خطای واقعی است.
  2. لاراول تصمیم می‌گیرد صفحه‌ی خطای مفصل را رندر کند.
  3. رندرکننده به تابع بسته می‌رسد و خودش خطا می‌دهد.
  4. مرورگر خطای مرحله‌ی سه را نشان می‌دهد.
  5. خطای مرحله‌ی یک — همانی که دنبالش بودید — هیچ‌جا نوشته نمی‌شود.

نتیجه: حالت دیباگ نه‌تنها کمکی نکرد، بلکه یک لایه‌ی نویز روی خطای اصلی گذاشت.

قاعده‌ای که برای همیشه نگه داشتیم: اگر روی این سرور خطایی بی‌دلیل یا مبهم دیدید، اول APP_DEBUG را چک کنید. حالت دیباگ این‌جا کمک نمی‌کند، پنهان می‌کند.

چطور بفهمید همین مشکل را دارید#

ببینید تابع در دسترس هست یا نه:

Bash
php84 -r 'var_dump(function_exists("highlight_file"));'

اگر false بود، صفحه‌ی خطای مفصل لاراول روی این سرور قابل اعتماد نیست. فهرست کامل توابعی که ممکن است بسته باشند و اثرشان را در نوشته‌ی disable_functions جمع کرده‌ایم.

کار درست: خطاها را از لاگ بخوانید#

روی سایت زنده، APP_DEBUG باید همیشه خاموش باشد. نه فقط به‌خاطر این باگ — به‌خاطر امنیت.

Code
APP_ENV=production
APP_DEBUG=false

چرا خاموش، حتی وقتی مشکلی ندارید#

صفحه‌ی خطای دیباگ چیزهایی نشان می‌دهد که هیچ بازدیدکننده‌ای نباید ببیند:

  • مقدار متغیرهای محیطی، از جمله اطلاعات اتصال دیتابیس
  • مسیر کامل فایل‌ها روی سرور
  • بخشی از داده‌ی درخواست
  • نسخه‌ی دقیق پکیج‌ها، که برای مهاجم یعنی فهرست آسیب‌پذیری‌های احتمالی

روشن گذاشتن APP_DEBUG روی سایت زنده یکی از رایج‌ترین اشتباهات امنیتی پروژه‌های لاراول است. ما هم مدتی همین اشتباه را داشتیم و موقع بازبینی پیدایش کردیم.

لاگ را قابل استفاده کنید#

پیش‌فرض لاراول همه چیز را در یک فایل می‌ریزد که با گذشت زمان غیرقابل استفاده می‌شود. کانال روزانه بهتر است:

Code
LOG_CHANNEL=daily
LOG_LEVEL=warning

حالا هر روز یک فایل جدا در storage/logs دارید و می‌توانید نگه‌داری را محدود کنید:

PHP
'daily' => [
    'driver' => 'daily',
    'path' => storage_path('logs/laravel.log'),
    'level' => env('LOG_LEVEL', 'debug'),
    'days' => 14,
],

خواندن لاگ در ترمینال#

آخرین خطاها:

Bash
tail -100 ~/app/storage/logs/laravel-$(date +%F).log

دنبال کردن زنده در حالی که خطا را بازتولید می‌کنید:

Bash
tail -f ~/app/storage/logs/laravel-$(date +%F).log

فقط خطاها، بدون ردیابی پشته:

Bash
grep -n 'ERROR' ~/app/storage/logs/laravel-$(date +%F).log | tail -30

اگر لاگ هم خالی بود#

گاهی خطا آن‌قدر زود رخ می‌دهد که لاراول هنوز سیستم لاگ را راه نینداخته — مثلاً خطای نحوی یا مشکل بارگذاری خودکار. آن‌وقت باید یک پله پایین‌تر بروید:

۱. لاگ خطای خود PHP. روی سی‌پنل معمولاً فایلی به نام error_log کنار فایل اجراشده ساخته می‌شود:

Bash
find ~/ -name 'error_log' -mmin -60 2>/dev/null

سوییچ آخر یعنی فقط فایل‌هایی که در یک ساعت گذشته تغییر کرده‌اند — همان چیزی که دنبالش هستید.

۲. لاگ خطای وب‌سرور. سی‌پنل معمولاً بخشی به نام Errors دارد که چند صد خط آخر را نشان می‌دهد.

۳. تست نحوی مستقیم. اگر مشکوکید فایلی خطای نحوی دارد:

Bash
php84 -l ~/app/app/Http/Controllers/SomeController.php

توجه: این تست روی فایل‌های Blade بی‌فایده است. دستورهای Blade از دید PHP متن ساده‌اند و همیشه «بدون خطا» می‌گیرید. برای Blade باید کامپایل واقعی بگیرید:

Bash
php84 artisan view:cache && php84 artisan view:clear

صفحه‌ی خطای ۵۰۰ که کاربر می‌بیند#

وقتی APP_DEBUG خاموش است، لاراول یک صفحه‌ی ساده‌ی خطا نشان می‌دهد. مشکل این است که آن صفحه قالب فروشگاه شما را ندارد — انگلیسی است، چپ‌چین است، و برای بازدیدکننده‌ی فارسی‌زبان بی‌ربط به نظر می‌رسد.

همین برای صفحه‌ی ۴۰۴ هم صادق است و از خطای ۵۰۰ خیلی بیشتر دیده می‌شود: هر لینک قدیمی، هر آدرس تایپ‌شده‌ی اشتباه، هر خزنده‌ای که مسیر حذف‌شده‌ای را امتحان می‌کند.

درستش کردن ساده است. یک ویو با همان نام کد خطا بسازید:

Code
resources/views/errors/404.blade.php
resources/views/errors/500.blade.php
resources/views/errors/503.blade.php

و در آن قالب سایت خودتان را گسترش دهید. برای ۴۰۴ یک جعبه‌ی جستجو و چند لینک مفید بگذارید؛ کاربری که به بن‌بست خورده باید راهی برای ادامه داشته باشد.

یک نکته: ویوی 500 نباید به چیزی وابسته باشد که ممکن است خودش علت خطا باشد. اگر در قالب اصلی سایت کوئری دیتابیس می‌زنید و مشکل از دیتابیس است، صفحه‌ی خطا هم می‌شکند و همان حلقه‌ای که بالاتر گفتیم تکرار می‌شود. صفحه‌ی خطا باید ساده‌ترین صفحه‌ی سایت باشد.

حالت تعمیر و تفاوتش با خطا#

خطای ۵۰۳ حالت جداگانه‌ای است: یعنی سایت عمداً موقتاً در دسترس نیست. لاراول برایش دستور دارد:

Bash
php84 artisan down --retry=60
php84 artisan up

دو نکته که روی سایت واقعی مهم‌اند:

  • همیشه --retry بدهید. این عدد در سرآیند پاسخ می‌نشیند و به خزنده می‌گوید بعداً برگردد. بدون آن، خزنده ممکن است صفحه را به‌عنوان مشکل دائمی ثبت کند.
  • کد وضعیت باید ۵۰۳ باشد، نه ۲۰۰. اگر صفحه‌ی «در حال تعمیر» را با کد موفقیت برگردانید، موتور جستجو همان را به‌عنوان محتوای صفحه ثبت می‌کند.

چند لاگ که ارزش نوشتن دارند#

بعد از اینکه لاگ را قابل استفاده کردید، از آن استفاده کنید. چیزهایی که برای ما ارزشمند بودند:

PHP
// ورودی‌های ناموفق، برای تشخیص حمله‌ی حدس رمز
Log::warning('ورود ناموفق', ['mobile' => $mobile, 'ip' => $request->ip()]);

// شکست در تحویل فایل خریداری‌شده
Log::error('تحویل فایل شکست خورد', ['order' => $order->id, 'reason' => $e->getMessage()]);

// کارهای زمان‌بندی‌شده، برای اینکه بفهمید اصلاً اجرا شده‌اند
Log::info('گزارش شبانه ساخته شد', ['rows' => $count, 'seconds' => $elapsed]);

مورد آخر را دست‌کم نگیرید. یک کرون که اجرا نمی‌شود هیچ خطایی تولید نمی‌کند؛ تنها راه فهمیدنش این است که خودش موقع موفقیت یک خط بنویسد.

نکته‌ی امنیتی: در لاگ هیچ‌وقت رمز، توکن یا کد یک‌بارمصرف ننویسید. اگر برای تست لازم شد، بعد از تمام‌شدن کار هم آن کد را بردارید و هم فایل‌های لاگ آن دوره را پاک کنید.

وقتی لازم است واقعاً دیباگ کنید#

اگر مجبورید روی سرور زنده جزئیات ببینید و صفحه‌ی خطا قابل اعتماد نیست، ساده‌ترین راه امن این است که خودتان لاگ بنویسید:

PHP
Log::debug('مقدار مورد بررسی', ['id' => $id, 'state' => $state]);

و بعد فایل امروز را دنبال کنید. کندتر از صفحه‌ی خطای رنگی است، ولی روی این سرور تنها راهی است که واقعاً جواب می‌دهد — و هیچ اطلاعاتی را به بازدیدکننده نشان نمی‌دهد.

اگر برای مدت کوتاهی سطح لاگ را پایین می‌آورید تا رویداد بیشتری ثبت شود، یادتان باشد بعدش برش گردانید. لاگ سطح پایین روی سایت پربازدید هم دیسک می‌خورد و هم می‌تواند داده‌ی حساس ثبت کند.

بقیه‌ی متغیرهایی که روی سایت زنده باید درست باشند#

APP_DEBUG مهم‌ترین است ولی تنها نیست. این چند مورد را با هم چک کنید:

Code
APP_ENV=production
APP_DEBUG=false
APP_URL=https://example.com
LOG_CHANNEL=daily
LOG_LEVEL=warning
SESSION_SECURE_COOKIE=true
  • ‏APP_ENV فقط یک برچسب نیست؛ لاراول بر اساسش رفتارهایی را عوض می‌کند، از جمله اینکه دستورهای خطرناک تأیید تعاملی بخواهند.
  • ‏APP_URL اگر اشتباه باشد، آدرس‌هایی که در ایمیل و نقشه‌ی سایت و فید تولید می‌شوند غلط از آب درمی‌آیند — بدون اینکه سایت خطا بدهد.
  • ‏SESSION_SECURE_COOKIE یعنی کوکی نشست فقط روی اتصال امن فرستاده شود. اگر سایت شما کامل روی HTTPS است، این باید روشن باشد.

بعد از هر تغییر در فایل محیط، کش پیکربندی را پاک کنید وگرنه مقدار قدیمی باقی می‌ماند:

Bash
php84 artisan config:clear

و اگر مقدارها را با ویرایشگر تحت وب عوض می‌کنید، حواستان به تله‌ی پایان‌خط ویندوزی باشد.

یک بررسی سریع که ماهی یک بار ارزش دارد#

سه دستور که وضعیت را در چند ثانیه نشان می‌دهند:

Bash
grep -E '^(APP_ENV|APP_DEBUG|LOG_LEVEL)=' ~/app/.env
ls -la ~/app/storage/logs/ | tail -5
du -sh ~/app/storage/logs/

اولی می‌گوید سایت در حالت درستی هست یا نه. دومی نشان می‌دهد آخرین بار کی چیزی در لاگ نوشته شده — اگر فایل امروز خالی است و شما هیچ خطایی هم گزارش نشده، خبر خوبی است. سومی جلوی سورپرایز پر شدن فضا را می‌گیرد.

اگر یک روز دیدید فایل لاگ ناگهان چند برابر بزرگ‌تر شده، همان‌جا نگاهش کنید؛ معمولاً یعنی یک خطا در حال تکرار شدن است.

خلاصه#

  • روی هاست اشتراکی، APP_DEBUG=true می‌تواند خطای اصلی را با یک خطای ثانویه بپوشاند.
  • روی سایت زنده همیشه APP_DEBUG=false و APP_ENV=production.
  • خطاها را از storage/logs بخوانید، با کانال روزانه.
  • اگر لاگ لاراول خالی بود، سراغ لاگ خطای PHP و وب‌سرور بروید.
  • برای Blade هیچ‌وقت به تست نحوی ساده اعتماد نکنید.

و درس کلی‌تری که این باگ داد: وقتی علامت و علت نمی‌خوانند، اول فرض کنید علامت گمراه‌کننده است. ما دو ساعت دنبال کدی گشتیم که هیچ ایرادی نداشت، چون پیام روی صفحه از جای دیگری می‌آمد.