Продвинутое

Логирование

Winter логирует через пакет winter-logger — тонкий PSR-3-слой над Monolog, рассчитанный на несколько рантаймов (FPM, Swoole, CLI). Каналы, контекст запроса и маскирование секретов работают из коробки; вам достаточно получить логгер и писать.

Пакет winter-loggerКаналы sys · http · cliСтандарт PSR-3

Что такое логирование и зачем

Логирование — запись событий приложения для наблюдаемости и отладки.

Проблема. Разрозненные 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 — логгер придёт автоматически именованным по классу:

php
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):

php
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]);   // фасад, текущий канал по умолчанию

Контекст запроса

Поля можно задать один раз — и они попадут в каждую запись текущего запроса/корутины, без ручной передачи:

php
// в 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 заменяет чувствительные значения на *** до записи — по ключам, без учёта регистра, рекурсивно по вложенным массивам:

php
$logger->info('login attempt', [
  'username' => 'alice',
  'password' => 'hunter2',            // → ***
  'meta'     => ['token' => 'jwt'],   // вложенное тоже → ***
]);

По умолчанию маскируются password, secret, token, authorization, cookie, credit_card, cvv и другие. Полный список и добавление своих ключей — в доках logger.

Свои каналы

Дополнительные каналы регистрируются в bootstrap.php после Kernel::init():

bootstrap.php
protected static function channels(): void
{
  Kernel::channel('job');
  Kernel::channel('daemon');
}

Каждый канал читает свои LOG_{NAME}_* переменные (напр. LOG_JOB_LEVEL, LOG_JOB_OUTPUT) с тем же набором настроек, что и встроенные.

Дальше