Логирование
Winter логирует через пакет winter-logger — тонкий PSR-3-слой над Monolog, рассчитанный на несколько рантаймов (FPM, Swoole, CLI). Каналы, контекст запроса и маскирование секретов работают из коробки; вам достаточно получить логгер и писать.
Что такое логирование и зачем
Логирование — запись событий приложения для наблюдаемости и отладки.
Проблема. Разрозненные error_log() и echo дают неструктурированный вывод без
уровней, без контекста запроса и с риском утечь пароль в лог. Под FPM и Swoole
поток вывода к тому же разный.
Решение. Единый PSR-3-логгер с каналами, полями контекста и маскированием
секретов, который одинаково работает во всех рантаймах. Об этом и раздел; настройка
через .env — на странице Конфигурация.
Каналы
Ядро регистрирует три канала, а точка входа выбирает текущий по рантайму:
| Канал | Для чего | Устанавливается |
|---|---|---|
sys |
Системные события ядра | по умолчанию |
http |
Жизненный цикл HTTP-запроса | index.php / Swoole |
cli |
Команды и джобы | call / executor |
Логирование — opt-in
Если LOG_LEVEL пуст (или Monolog не установлен) — все каналы получают
NullLogger и вызовы логов тихо отбрасываются. Приложение работает без настройки
логов. Переменные LOG_* — на странице Конфигурация.
Как писать логи
Инъекция логгера — #[Autowired] (рекомендуется)
Классу, который строит контейнер (контроллер, сервис, джоба), достаточно объявить
свойство типа LoggerInterface — логгер придёт автоматически именованным по классу:
use Psr\Log\LoggerInterface;
use Flytachi\Winter\DI\Attribute\Autowired;
class OrderController extends Controller
{
#[Autowired] private LoggerInterface $logger;
public function index(): ResponseEntity
{
$this->logger->info('order placed', ['total' => 99.0]);
// → [INFO ] -http- [4821] (OrderController): order placed {"total":99,...}
return ResponseEntity::ok();
}
}Фабрика и фасад
Для кода вне контейнера (статические хелперы, ручной new):
use Flytachi\Winter\Logger\LoggerFactory;
use Flytachi\Winter\Logger\Log;
LoggerFactory::getLogger(UserService::class)->info('user created', ['id' => 42]);
LoggerFactory::getLogger(MyJob::class, 'job')->debug('processing'); // явный канал
Log::warning('retrying', ['attempt' => 3]); // фасад, текущий канал по умолчаниюКонтекст запроса
Поля можно задать один раз — и они попадут в каждую запись текущего запроса/корутины, без ручной передачи:
// в middleware — один раз на запрос
$ctx = LoggerFactory::contextStorage();
$ctx->set('request_id', uniqid('', true));
$ctx->set('user_id', $auth->id());
// дальше любой лог в этом запросе несёт request_id + user_idПод FPM/CLI это ProcessContext (на процесс), под Swoole — CoroutineContext
(изолирован по корутине; поля одного запроса не утекают в другой).
Маскирование секретов
SensitiveMaskingProcessor заменяет чувствительные значения на *** до записи —
по ключам, без учёта регистра, рекурсивно по вложенным массивам:
$logger->info('login attempt', [
'username' => 'alice',
'password' => 'hunter2', // → ***
'meta' => ['token' => 'jwt'], // вложенное тоже → ***
]);По умолчанию маскируются password, secret, token, authorization, cookie,
credit_card, cvv и другие. Полный список и добавление своих ключей — в
доках logger.
Свои каналы
Дополнительные каналы регистрируются в bootstrap.php после Kernel::init():
protected static function channels(): void
{
Kernel::channel('job');
Kernel::channel('daemon');
}Каждый канал читает свои LOG_{NAME}_* переменные (напр. LOG_JOB_LEVEL,
LOG_JOB_OUTPUT) с тем же набором настроек, что и встроенные.
Дальше
- Winter Logger — полный reference — формат, процессоры, каналы
- Конфигурация — переменные
LOG_* - Runtime — вывод логов под FPM и Swoole