# راهنمای لاگ‌گیری و عیب‌یابی (Logging & Diagnostics)

این سند سیستم لاگ تشخیصی فاز ۷ را توضیح می‌دهد: کجا لاگ‌ها هستند، هر
فایل چیست، چطور حالت تشخیصی (diagnostic mode) را روشن کنید، چه
چیزی را برای کدنویس/پشتیبانی بفرستید، و چه چیزهایی عمداً هرگز لاگ
نمی‌شوند.

## ۰. رهگیری عملکرد اختیاری

`perf_trace` به‌صورت پیش‌فرض خاموش است. با `TFB_PERF_TRACE=1` یا `app.perf_trace=true` موقتاً روشن می‌شود و هر درخواست یک خط JSON خلاصه در `storage/logs/perf.log` می‌نویسد. این خط شامل متن پیام یا secret نیست. پس از اندازه‌گیری خاموشش کنید.

## ۱. فایل‌های لاگ کجا هستند؟

همه‌ی فایل‌های لاگ زیر پوشه `storage/logs/` قرار دارند — این پوشه
همیشه توسط `storage/.htaccess` کاملاً از دسترسی مستقیم وب مسدود است
(`Require all denied`)، پس هیچ‌وقت از طریق مرورگر قابل مشاهده نیست.

| فایل | محتوا | چه زمانی نوشته می‌شود |
|---|---|---|
| `storage/logs/app-YYYY-MM-DD.log` | همه‌ی رخدادهای INFO به بالا | همیشه (بسته به سطح لاگ تنظیم‌شده) |
| `storage/logs/error-YYYY-MM-DD.log` | فقط ERROR و CRITICAL | همیشه، وقتی چنین رخدادی پیش بیاید |
| `storage/logs/diagnostic-YYYY-MM-DD.log` | جزئیات DEBUG بسیار ریز (ورود/خروج توابع حساس، تصمیم شاخه‌ها، امتیازها) | فقط وقتی `diagnostic_mode` روشن باشد |
| `storage/logs/problems-YYYY-MM-DD.log` | خلاصه‌ی تجمیعی و انسانی‌خوان مشکلات (برای ارسال به کدنویس) | وقتی یک مشکل واقعی رخ دهد |
| `storage/logs/problems-index.json` | فایل داخلی کوچک برای تشخیص «این مشکل قبلاً دیده شده یا نه» | داخلی، معمولاً نیازی به باز کردن آن نیست |

هر فایل به‌صورت روزانه (بر اساس تاریخ UTC) جدا می‌شود؛ فایل‌های
قدیمی‌تر به‌مرور زمان جمع می‌شوند و باید طبق سیاست نگهداری هاست خود
(یا با پاک‌سازی دستی دوره‌ای) مدیریت شوند.

## ۲. سطح لاگ (log level) پیش‌فرض

بعد از نصب، سطح لاگ روی **warning** تنظیم می‌شود — یعنی فقط رخدادهای
مهم (هشدار/خطا/بحرانی) ثبت می‌شوند، نه هر تعامل عادی. این برای اجرای
production روی هاست اشتراکی مناسب است (حجم لاگ کم می‌ماند).

می‌توانید سطح لاگ را از پنل ادمین تلگرام تغییر دهید:
`/admin` → ⚙️ تنظیمات → «سطح لاگ (log level)» → یکی از مقادیر
`debug` / `info` / `warning` / `error` را بفرستید.

⚠️ **توجه:** گذاشتن سطح لاگ روی `debug` برای همیشه روی یک سایت
production توصیه نمی‌شود — حجم لاگ به‌شدت زیاد می‌شود. فقط برای مدت
کوتاهی هنگام عیب‌یابی یک مشکل خاص از آن استفاده کنید و بعد دوباره
روی `warning` برگردانید.

## ۳. حالت تشخیصی (diagnostic_mode) چطور روشن می‌شود؟

`diagnostic_mode` یک کلید جداگانه از سطح لاگ است و **فقط از طریق
فایل `config/app.php`** قابل تغییر است (عمداً از پنل تلگرام قابل
تغییر نیست، چون جزئیات بسیار سنگین‌تری تولید می‌کند):

```php
'app' => [
    // ...
    'diagnostic_logging' => true,   // پیش‌فرض: false
],
```

وقتی روشن باشد:
- فایل `diagnostic-YYYY-MM-DD.log` جزئیات ورود/خروج توابع حساس
  (شروع سوال، branch resolver، محاسبه امتیاز، انتخاب برنده) را ثبت
  می‌کند.
- محتوای خام (و truncate/redact شده) بعضی context ها کامل‌تر می‌شود.

بعد از پایان عیب‌یابی، حتماً این مقدار را به `false` برگردانید — این
حالت برای اجرای همیشگی روی production طراحی نشده (کمی سربار اضافه
می‌کند و لاگ زیاد تولید می‌کند).

## ۴. اگر ربات مشکلی داشت، چه فایلی را برای کدنویس/پشتیبانی بفرستم؟

به ترتیب اولویت:

1. **`storage/logs/problems-امروز.log`** — مهم‌ترین فایل. هر مشکل
   واقعی اینجا با فرمت ساختاریافته‌ی زیر ثبت می‌شود (نمونه در بخش ۶).
2. **`storage/logs/error-امروز.log`** — اگر problems خالی بود ولی
   ربات رفتار عجیبی داشت.
3. **`storage/logs/app-امروز.log`** — فقط در صورت نیاز به جزئیات
   بیشتر (این فایل معمولاً حجیم‌تر است).

می‌توانید مستقیم از داخل پنل ادمین این کار را انجام دهید:
`/admin` → 📋 لاگ‌ها → «📤 ارسال فایل مشکلات امروز» — ربات فایل
`problems-*.log` همان روز را مستقیم برایتان در همان چت ارسال می‌کند.

از همان صفحه می‌توانید «📄 آخرین خطوط لاگ عمومی» یا «❌ فقط خطاها»
را هم ببینید (بدون نیاز به SSH/FTP).

## ۵. چه چیزهایی عمداً هرگز لاگ نمی‌شوند (Redaction)

هرگز، تحت هیچ شرایطی (حتی در حالت diagnostic/debug) موارد زیر در
هیچ فایل لاگی ظاهر نمی‌شوند:

- توکن ربات تلگرام (`bot.token`)
- رمز وبهوک (`bot.webhook_secret`)
- رمز عبور دیتابیس (`db.password`)
- هر مقداری که ساختار یک توکن تلگرام را داشته باشد (حتی اگر در یک
  فیلد متنی دیگر تایپ شده باشد) — به‌صورت خودکار با الگو تشخیص داده
  و ماسک می‌شود
- هدرهای Authorization

این‌ها به‌صورت خودکار در `Tfb\Logging\Redaction` قبل از نوشتن هر خط
لاگ با متن `***REDACTED***` جایگزین می‌شوند — این کار در سطح
`FileLogger`/`ProblemsLog` انجام می‌شود، پس هیچ کد دیگری لازم نیست
جداگانه چیزی را پنهان کند؛ فقط کافی است هرگز مستقیم روی `error_log()`
یا `var_dump()` این مقادیر را چاپ نکنید.

## ۶. نمونه یک رکورد PROBLEM (با داده جعلی)

```
[PROBLEM]
time: 2026-07-27T04:00:00Z
code: E_DB_CONNECT
title: اتصال به دیتابیس برقرار نشد
where:
  class: Tfb\Core\Database
  method: connect
  file: -
  line: -
what_happened: تلاش برای باز کردن اتصال PDO به MySQL/MariaDB با خطا مواجه شد: SQLSTATE[HY000] [2002] Connection refused
likely_reason: معمولاً یعنی اطلاعات host/port/dbname/user/password در config/app.php نادرست است یا سرویس دیتابیس در دسترس نیست.
fix_where: config/app.php -> db.*، یا وضعیت سرویس MySQL روی هاست
fix_suggestion: اطلاعات دیتابیس را در پنل هاست بررسی کنید و مطمئن شوید کاربر دیتابیس روی همان host اجازه اتصال دارد.
context: {"host":"127.0.0.1","port":"3306","db_name":"tfb_prod"}
occurrence_count: 3
first_seen: 2026-07-27T03:58:12Z
last_seen: 2026-07-27T04:00:00Z
[/PROBLEM]
```

هر فیلد مستقیماً قابل استفاده است:
- `where` دقیقاً می‌گوید کدام کلاس/متد/فایل/خط مشکل‌دار بوده
- `what_happened`/`likely_reason` توضیح فنی به فارسی می‌دهند
- `fix_where`/`fix_suggestion` مسیر رفع مشکل را نشان می‌دهند
- `occurrence_count`/`first_seen`/`last_seen` می‌گویند این مشکل چند
  بار و در چه بازه‌ای تکرار شده (مشکلات مشابه **تجمیعی** ثبت
  می‌شوند، نه یک بلوک به‌ازای هر رخداد — تا فایل لاگ اسپم نشود)

## ۷. نکات پرفورمنس (این سیستم ربات را کند نمی‌کند)

- در مسیر موفق و پرتکرار (مثلاً هر بار که یک دانش‌آموز به یک سوال
  پاسخ می‌دهد)، **هیچ‌چیزی در سطح DEBUG نوشته نمی‌شود مگر
  diagnostic_mode روشن باشد** — چک `if (!diagnosticMode) return;`
  همیشه اولین خط `FileLogger::diagnostic()` است، پس هزینه‌ی واقعی
  فرمت‌دهی/نوشتن فایل هرگز اتفاق نمی‌افتد.
- خطاهای مشابه با فاصله‌ی کمتر از ۱۰ دقیقه دوباره به‌صورت کامل در
  فایل problems نوشته نمی‌شوند (فقط شمارنده افزایش می‌یابد) — یک
  خطای تکراری هزار باری، فایل لاگ را منفجر نمی‌کند.
- هر فایل روزانه (app/diagnostic) اگر بیش از حد بزرگ شود (پیش‌فرض
  ۲۰ مگابایت)، نوشتن ورودی‌های بیشتر در آن روز متوقف می‌شود — فایل
  error هرگز این محدودیت را ندارد (چون کوچک و همیشه مهم است).
- کوئری‌های دیتابیس کندتر از ۳۰۰ میلی‌ثانیه (قابل تنظیم با
  `app.log_query_slow_ms`) به‌صورت WARNING ثبت می‌شوند تا مشکلات
  پرفورمنسی زودتر دیده شوند.
- خود سیستم لاگ هرگز باعث کرش ربات نمی‌شود: اگر نوشتن فایل لاگ با
  خطا مواجه شود (مثلاً دیسک پر یا مجوز اشتباه)، به‌صورت خودکار به
  `error_log()` داخلی PHP سرور برمی‌گردد و اجرای ربات ادامه می‌یابد.

## ۸. برای توسعه‌دهنده: چطور از این سیستم استفاده کنم؟

هر کلاسی که نیاز به لاگ‌گیری بیش از یک پیام ساده دارد باید
`Tfb\Logging\Diagnostic` را به‌صورت **اختیاری (nullable, پیش‌فرض
null)** به constructor خود اضافه کند — این باعث می‌شود کلاس‌های
قدیمی‌تر که این پارامتر را ندارند بدون تغییر کار کنند.

```php
public function __construct(
    // ... وابستگی‌های قبلی
    private readonly ?Diagnostic $diagnostic = null
) {}
```

سپس:

```php
// یک استثنا (Throwable) که catch شده:
$this->diagnostic?->exception(
    'domain_name',      // مثلاً 'db' | 'telegram' | 'admin' | 'student' | 'engine' | 'security'
    $exception,
    what: 'چه اتفاقی افتاد (فارسی)',
    why: 'چرا احتمالاً این اتفاق افتاد (فارسی)',
    fixSuggestion: 'راهنمای اصلاح کوتاه (فارسی)'
);

// یک مشکل منطقی که استثنا نیست (مثلاً "این آزمون پروفایل ندارد"):
$this->diagnostic?->problem(
    code: 'E_SOME_CODE',
    title: 'عنوان کوتاه',
    domain: 'engine',
    class: self::class,
    method: __FUNCTION__,
    what: '...', why: '...', fixWhere: '...', fixSuggestion: '...'
);

// لاگ ساده (بدون رکورد problems):
$this->diagnostic?->warning('domain_name', 'پیام');
```

**ممنوع:**
- لاگ کردن هر چیزی داخل حلقه‌های بزرگ بدون guard
- کپی‌کردن کد لاگ‌نویسی به‌جای استفاده از `Diagnostic`
- لاگ کردن `$_SERVER` کامل یا کل آرایه config
- گذاشتن `$context` بدون فکر — فقط داده‌های کوچک و غیرحساس (شناسه‌ها،
  کدهای وضعیت) را بگذارید، نه متن آزاد کاربر بدون فکر (اگرچه
  Redaction به‌صورت خودکار اسکن می‌کند، بهتر است از ابتدا کم و مفید
  باشد).

## ۹. تست دود (Smoke Test) این سیستم

`tests/phase07_ops_e2e.php` این سیستم را به‌صورت end-to-end تست
می‌کند:
- یک درخواست عمدی با رمز وبهوک اشتباه می‌فرستد و بررسی می‌کند که در
  `problems-index.json` ثبت شده باشد
- بررسی می‌کند توکن ربات و رمز وبهوک واقعی هرگز در هیچ فایل لاگی
  ظاهر نشوند

برای اجرای دستی مشابه: یک درخواست webhook با هدر
`X-Telegram-Bot-Api-Secret-Token` اشتباه بفرستید و سپس
`storage/logs/problems-امروز.log` را باز کنید — باید یک بلوک
`[PROBLEM]` با `code: E_WEBHOOK_SECRET_MISMATCH` ببینید.
