Пакет · logger

Вывод и broken pipe

Логгер, способный уронить ваш веб-воркер, хуже, чем никакого логгера. Эта страница объясняет режим отказа broken pipe, однострочный обработчик, который его обезвреживает, и почему правильная цель вывода различается у FPM и Swoole.

Режим отказа

Под PHP-FPM php://stdout — это не файл лога, а поток ответа FastCGI обратно к nginx или Apache. Когда HTTP-клиент отключается до завершения ответа, читающего конца этого потока больше нет. Следующий fwrite() в него поднимает SIGPIPE, и стандартная реакция PHP на SIGPIPE завершает процесс. Случайная строка лога, записанная после того как клиент повесил трубку, может уронить FPM-воркер.

text
клиент отключается   →  читающий конец php://stdout закрыт
пишется строка лога  →  fwrite() → SIGPIPE → FPM-воркер умирает

SafeStreamHandler — никогда не бросать на записи

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

php
final class SafeStreamHandler extends StreamHandler
{
  protected function streamWrite(mixed $stream, LogRecord $record): void
  {
      if (!is_resource($stream)) {
          return;
      }
      @fwrite($stream, $record->formatted);
  }
}

@ подавляет E_WARNING от неудачной записи, а пути с исключением нет — так что разорванный поток стоит вам одной строки лога, а не воркера. Этот обработчик стоит за выводами stdout и stderr (через HandlerFactory).

Потерянная строка — это и есть смысл

Компромисс намеренный: когда цель записи исчезла, строка всё равно невосстановима. Потерять её молча строго лучше, чем уронить процесс, который пытался её залогировать.

Почему stderr для FPM

SafeStreamHandler сохраняет воркер живым, но более чистое решение — вообще не писать в поток ответа. Под FPM php://stderr подключён к error_log FPM, независимому от клиентского соединения. Он не может сломаться при отключении клиента, поэтому сценарий с SIGPIPE даже не возникает.

text
FPM stdout  →  ответ FastCGI  →  умирает при отключении клиента   ✗
FPM stderr  →  error_log FPM  →  независим от клиента              ✓

Вот почему каждый FPM-канал в рекомендуемом конфиге использует output: 'stderr'.

Почему stdout нормален для Swoole

Swoole не гоняет HTTP-ответы через stdout PHP — он управляет ответами через свой асинхронный слой I/O ($response->end(...)). Так что php://stdout в воркере Swoole — обычный поток в stdout контейнера, ровно то, что нужно для сбора логов в Docker/Kubernetes. Риска broken pipe нет, поэтому output: 'stdout' — правильный выбор там.

Сводка целей вывода

Рантайм Рекомендуемый output Почему
PHP-FPM stderr Поток ответа хрупок; error_log безопасен.
Swoole stdout Ответы обрабатывает Swoole, а не stdout.
CLI stdout Обычный терминал/поток; HTTP-клиента нет.
Docker/K8s stdout / stderr / syslog Собирается драйвером логов платформы.

Каким бы ни был выбор, SafeStreamHandler — страховочная сетка: даже неверно настроенный FPM-канал на stdout не уронит воркер.

Смотрите также