9. Діагностичні логи

Після кроку 8 у вас є безпечний app-канал: exceptions і бізнес-info у app.log з masking. Але два типові дебаг-сценарії в app.log часто не видно:

  • Форма «мовчки» не проходить. Користувач бачить підсвічені поля (крок 7), а ви в app-лозі — нічого: ValidationException оброблена middleware, це не «аварія». Щоб зрозуміти, які правила впали і з яким input, потрібен окремий слід саме валідації.
  • SQL поводиться дивно. N+1, зайвий SELECT, неочікувані bindings — у info controller-а цього немає. Треба бачити реальний запит до MySQL, не лише «insert пройшов».

Обидва болі вирішуються окремими sinks на вже наявних bricks, не новим addLogging(): Validation і Database мають власні rotating-файли. Їх шум інший (сотні рядків SQL на один page load), тому змішувати з app-каналом — погана ідея. На проді ці sinks зазвичай вимикають; локально / на staging — вмикають, коли дебажите.

Masking з кроку 8 уже є: підключаємо його й сюди через withMasking(), бо validation log і query bindings легко тягнуть email/паролі.

Що додається і навіщо

  • withLogging(…/validation.log) на Validation — файл спроб валідації (які поля, які помилки)
  • withQueryLogging(…/query.log) на Database — SQL + bindings у окремий rotating файл
  • withMasking() на обох — ті самі правила Data Masker, що й для app.log

Це не capability Logging: свої handlers усередині Validation / Database providers. API — Validation і Database.

Лог валідації

Без цього виклику валідація працює як раніше — лише без файлу. З ним ValidationLogger пише в rotating sink: зручно відтворити «чому formal failed», коли flash уже зник або API повернув 422 без деталей у вашому app-лозі.

$stack->addValidation()
    ->withLogging($rootPath . 'storage/logs/validation.log', 7)
    ->withMasking();

withMasking() вимагає addMasking() (у нас уже з кроку 8). Інакше Stack не збереться на providers() — fail-fast замість «лог з паролями».

Query log

Eloquent/Capsule виконує SQL; без query log ви бачите лише результат у відповіді. withQueryLogging вмикає окремий RotatingFileHandler у Database provider — кожен запит потрапляє у query.log. На проді з постійним трафіком файл швидко роздується: тримайте opt-in.

$stack->addDatabase()
    ->setConnection([ /* … */ ])
    ->setMigrations([ /* … */ ])
    ->setSeeders([ /* … */ ])
    ->withQueryLogging($rootPath . 'storage/logs/query.log', 7)
    ->withMasking();

Bindings у SQL часто містять ті самі email/паролі, що й форма — тому masking тут не «опція на потім», а частина кроку.

Фрагмент Bootstrap

Diff відносно кроку 8 — лише ланцюжки Validation і Database. addLogging() / addMasking() / ErrorHandling лишаються як були.

$stack->addValidation()
    ->withLogging($rootPath . 'storage/logs/validation.log', 7)
    ->withMasking();

$stack->addDatabase()
    ->setConnection([ /* mysql … */ ])
    ->setMigrations([$rootPath . 'database/migrations'])
    ->setMigrationsTable('migrations')
    ->setSeeders([ContactSubmissionsSeeder::class])
    ->withQueryLogging($rootPath . 'storage/logs/query.log', 7)
    ->withMasking();

// … Session, Http, View, Console, Masking, Logging, ErrorHandling — без змін

Запуск і перевірка

Тег step-09-diagnostic-logs. Мета — побачити validation*.log і query*.log і переконатися, що чутливі поля замасковані.

Запуск

git checkout step-09-diagnostic-logs
rm -rf storage/cache storage/sessions storage/logs
composer install
cp docker-compose.yml.example docker-compose.yml
docker compose up -d --build

Перевірка в браузері

  1. Відкрийте http://localhost:8089/contact і надішліть невалідну форму — redirect з помилками; з’явиться storage/logs/validation*.log.
  2. Надішліть валідну форму або відкрийте http://localhost:8089/api/stats — SQL потрапить у storage/logs/query*.log.

Перевірка з CLI

COOKIE=/tmp/c.txt
curl -s -c $COOKIE http://localhost:8089/contact >/dev/null
TOKEN=$(curl -s -b $COOKIE http://localhost:8089/contact \
  | sed -n 's/.*name="_csrf_token" value="\([^"]*\)".*/\1/p')

# 1) невалідний POST → рядок у validation.log
curl -s -b $COOKIE -c $COOKIE -o /dev/null -w '%{http_code}\n' -X POST http://localhost:8089/contact \
  -d "name=a&email=bad&message=hi&_csrf_token=$TOKEN"
# 302
ls -la storage/logs/validation*.log
tail -n 20 storage/logs/validation*.log

# 2) валідний POST /contact або GET /api/stats → SQL у query.log
curl -s http://localhost:8089/api/stats
ls -la storage/logs/query*.log
tail -n 30 storage/logs/query*.log
# шукайте SELECT/INSERT; email у bindings має бути замаскований, якщо потрапив

Ланцюжок

POST /contact (invalid)
  → FormRequest / validator fails
  → ValidationLogger → storage/logs/validation-*.log  (+ masker)
  → HandleValidationException → flash + redirect
  → app.log: тиша (це не exception для ErrorHandling)

POST /contact (valid) або GET /api/stats
  → Capsule SQL
  → query log → storage/logs/query-*.log  (+ masker)
  → app.log: лише ваш info / errors, не кожен SELECT

Далі — 10. Casting і DTO: FormRequest → typed DTO через toDto(). Усі кроки — Огляд. Bricks — Validation, Database.