diff --git a/NEWS b/NEWS index ee8ab9f3529c..43e07bbdb626 100644 --- a/NEWS +++ b/NEWS @@ -16,6 +16,10 @@ PHP NEWS . Fixed bug GH-25268 (NULL php_libxml_ref_obj dereference in dom_objects_store_clone_obj when cloning Dom\Notation). (David Carlier) +- FPM: + . Fixed bug GH-25077 (Worker output lost when catch_workers_output is enabled + and error_log is syslog). (localzet) + - MBString: . Fixed stack overflow in mb_check_encoding() and mb_convert_encoding() with deeply nested arrays. (Eyüp Can Akman) diff --git a/sapi/fpm/fpm/fpm_stdio.c b/sapi/fpm/fpm/fpm_stdio.c index c9386f941f18..aa693e9bc4b2 100644 --- a/sapi/fpm/fpm/fpm_stdio.c +++ b/sapi/fpm/fpm/fpm_stdio.c @@ -192,7 +192,14 @@ static void fpm_stdio_child_said(struct fpm_event_s *ev, short which, void *arg) create_log_stream = !child->log_stream; if (create_log_stream) { log_stream = child->log_stream = malloc(sizeof(struct zlog_stream)); - zlog_stream_init_ex(log_stream, ZLOG_WARNING, STDERR_FILENO); + int log_fd = STDERR_FILENO; +#ifdef HAVE_SYSLOG_H + /* The FPM master redirects stderr to /dev/null when logging to syslog. */ + if (fpm_use_error_log() && fpm_globals.error_log_fd == ZLOG_SYSLOG) { + log_fd = ZLOG_SYSLOG; + } +#endif + zlog_stream_init_ex(log_stream, ZLOG_WARNING, log_fd); zlog_stream_set_decorating(log_stream, child->wp->config->decorate_workers_output); zlog_stream_set_wrapping(log_stream, ZLOG_TRUE); zlog_stream_set_msg_prefix(log_stream, STREAM_SET_MSG_PREFIX_FMT, diff --git a/sapi/fpm/tests/gh25077-syslog-worker-output.phpt b/sapi/fpm/tests/gh25077-syslog-worker-output.phpt new file mode 100644 index 000000000000..8f6939a55129 --- /dev/null +++ b/sapi/fpm/tests/gh25077-syslog-worker-output.phpt @@ -0,0 +1,97 @@ +--TEST-- +GH-25077: FPM captured worker output reaches syslog +--SKIPIF-- +/dev/null', $output, $status); +if ($status !== 0 || !$output) { + die('skip C compiler not available'); +} +?> +--FILE-- +&1', $output, $status); + if ($status !== 0) { + throw new RuntimeException('Failed to compile test syslog interceptor: ' . implode("\n", $output)); + } + + $config = <<<'CONFIG' +[global] +error_log = syslog +daemonize = no +[unconfined] +listen = {{ADDR}} +pm = static +pm.max_children = 1 +catch_workers_output = yes +CONFIG; + + $code = <<<'PHP' +start(forceStderr: false, envVars: $env); + $tester->checkConnection(); + $tester->request()->expectEmptyBody(); + + $found = false; + for ($i = 0; $i < 100; $i++) { + if (is_file($log)) { + $captured = file_get_contents($log); + if (str_contains($captured, 'GH25077_WORKER_STDOUT') + && str_contains($captured, 'GH25077_WORKER_STDERR')) { + $found = true; + break; + } + } + usleep(50000); + } + $tester->terminate(); + $tester->close(); + var_dump($found); +} finally { + @unlink($log); + @unlink($library); + @unlink($temp); +} +?> +Done +--EXPECT-- +bool(true) +Done +--CLEAN-- + diff --git a/sapi/fpm/tests/gh25077_syslog_intercept.c b/sapi/fpm/tests/gh25077_syslog_intercept.c new file mode 100644 index 000000000000..daeb460ea076 --- /dev/null +++ b/sapi/fpm/tests/gh25077_syslog_intercept.c @@ -0,0 +1,65 @@ +/* Test-only syslog interceptor; no host syslog service is required. */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +static void capture_syslog(const char *format, va_list args) +{ + const char *path = getenv("PHP_FPM_TEST_SYSLOG_CAPTURE"); + if (path == NULL || *path == '\0') { + return; + } + + char message[8192]; + int len = vsnprintf(message, sizeof(message), format, args); + if (len < 0) { + return; + } + + int fd = open(path, O_WRONLY | O_APPEND | O_CREAT, 0600); + if (fd < 0) { + return; + } + size_t count = (size_t) len < sizeof(message) ? (size_t) len : sizeof(message) - 1; + (void) write(fd, message, count); + (void) write(fd, "\n", 1); + close(fd); +} + +void syslog(int priority, const char *format, ...) +{ + (void) priority; + va_list args; + va_start(args, format); + capture_syslog(format, args); + va_end(args); +} + +void vsyslog(int priority, const char *format, va_list args) +{ + (void) priority; + capture_syslog(format, args); +} + +/* glibc uses these symbols when _FORTIFY_SOURCE is enabled. */ +void __syslog_chk(int priority, int flag, const char *format, ...) +{ + (void) priority; + (void) flag; + va_list args; + va_start(args, format); + capture_syslog(format, args); + va_end(args); +} + +void __vsyslog_chk(int priority, int flag, const char *format, va_list args) +{ + (void) priority; + (void) flag; + capture_syslog(format, args); +}