php-fpmの標準出力をDockerのstderrにまとめるため名前付きパイプを使った

  • Docker
  • PHP
  • Laravel
  • php-fpm
  • Logging

この記事は、構成と文章の整理にAIを使っています。
あくまで読みやすくするための補助で、内容そのものをAIにすべて任せて書いているわけではありませんが、ところどころ相談しながら整えています。

何が起きたか

php-fpmは子プロセス(workerプロセス)の標準出力・標準エラーを、そのままDockerのstdout/stderrには流してくれない。docker logsで全部拾いたかったので、名前付きパイプ経由で一本化する構成を試した。

構成

名前付きパイプで一本化する

Dockerfileでmkfifoにより/tmp/stderrという名前付きパイプを作り、fd3として読み書き両用で開く。書き込み側だけを先に開くとリーダーが来るまでブロックするらしいので、exec 3<>/tmp/stderrのように読み書き両用で開くのがポイントっぽい。

バックグラウンドでcat <&3 >&2を回し、パイプに書かれた内容をコンテナの実stderr(fd2)に流す。php-fpm自体はexec php-fpm >/tmp/stderr 2>&1でこのパイプに向けて起動する。

FROM php:7.2-fpm-alpine
COPY docker.conf /usr/local/etc/php-fpm.d/docker.conf
COPY php.ini /usr/local/etc/php
RUN mkfifo -m 666 /tmp/stderr
CMD ["sh", "-c", "exec 3<>/tmp/stderr; cat <&3 >&2 & exec php-fpm >/tmp/stderr 2>&1"]

php-fpm・php.ini側の設定

worker側の標準出力キャプチャ(catch_workers_output)はオフにして、php.iniのerror_logで直接/tmp/stderrに書かせる形にした。

[global]
error_log = /tmp/stderr
[www]
access.log = /dev/null
clear_env = no
catch_workers_output = no
[php]
log_errors = On
error_log = /tmp/stderr

Laravel側(Monolog)

Laravelのログチャンネルはstderrにして、MonologのStreamHandlerで同じ/tmp/stderrに書く。フォーマッタはJsonFormatterにしてJSON Linesで出すようにした。

'stderr' => [
'driver' => 'monolog',
'handler' => \Monolog\Handler\StreamHandler::class,
'handler_with' => [
'stream' => '/tmp/stderr',
'level' => \Monolog\Logger::DEBUG,
],
'formatter' => \Monolog\Formatter\JsonFormatter::class,
],

.env側はLOG_CHANNEL=stderrとLOG_USE_JSONL=trueを設定。ログ収集基盤側でJSON Linesとしてパースする前提で組んだ。

気になっている点

php.iniのerror_logはMonologを経由しないので、PHP本体が吐くFatal ErrorやWarningはJSONにならず、プレーンテキストのまま同じ/tmp/stderrに混ざる。JSON Linesとしてパースする側があるなら、この行で崩れるはず。LOG_USE_JSONLをどこでどう分岐に使っているかはこのメモだけでは追えていないので、後で確認する。