From 6c16dd1ba4e58b970bf99f2023d105711a9d7325 Mon Sep 17 00:00:00 2001 From: Matt Van Horn Date: Thu, 22 Oct 2026 19:38:29 +0000 Subject: [PATCH] cli: Add --loglevel for the built-in development server. The server already has process, error, and request log levels. This exposes them as --loglevel (1=process, 2=error, 3=message) so request noise can be filtered without relying on repeated -q flags. Closes GH-16855. --- NEWS | 4 +++ UPGRADING | 5 ++++ sapi/cli/php_cli.c | 8 ++++- sapi/cli/php_cli_server.c | 13 +++++++++ sapi/cli/tests/gh16855.phpt | 45 +++++++++++++++++++++++++++++ sapi/cli/tests/gh16855_default.phpt | 18 ++++++++++++ 6 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 sapi/cli/tests/gh16855.phpt create mode 100644 sapi/cli/tests/gh16855_default.phpt diff --git a/NEWS b/NEWS index 6bb8496271f8..0ae5252925b7 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,10 @@ PHP NEWS target, such as [$a, $b] = [$b, $a], no longer builds the array when the result is unused (GH-23048). (Artem Ukrainskiy) +- CLI: + . Added --loglevel for the built-in development server to select process, + error, or request logs (GH-16855). (mvanhorn) + - Curl: . curl_multi_exec() now throws a TypeError when still_running is not an int. (arshidkv12) diff --git a/UPGRADING b/UPGRADING index 39f8d9bbc0ba..4f1e44ef867d 100644 --- a/UPGRADING +++ b/UPGRADING @@ -84,6 +84,11 @@ PHP 8.7 UPGRADE NOTES 3. Changes in SAPI modules ======================================== +- CLI: + . The built-in development server accepts --loglevel , where 1 logs + process messages only, 2 also logs errors, and 3 (the default) also logs + each request. + ======================================== 4. Deprecated Functionality ======================================== diff --git a/sapi/cli/php_cli.c b/sapi/cli/php_cli.c index 3702da89f9f0..b6a01b66f9b4 100644 --- a/sapi/cli/php_cli.c +++ b/sapi/cli/php_cli.c @@ -151,6 +151,7 @@ const opt_struct OPTIONS[] = { /* Internal testing option -- may be changed or removed without notice, * including in patch releases. */ {16, 1, "repeat"}, + {17, 1, "loglevel"}, {'-', 0, NULL} /* end of args */ }; @@ -459,7 +460,7 @@ static void php_cli_usage(char *argv0) " %s [options] -r [--] [args...]\n" " %s [options] [-B ] -R [-E ] [--] [args...]\n" " %s [options] [-B ] -F [-E ] [--] [args...]\n" - " %s [options] -S : [-t docroot] [router]\n" + " %s [options] -S : [-t docroot] [--loglevel ] [router]\n" " %s [options] -- [args...]\n" " %s [options] -a\n" "\n" @@ -490,6 +491,8 @@ static void php_cli_usage(char *argv0) "\n" " --ini Show configuration file names\n" " --ini=diff Show INI entries that differ from the built-in default\n" + " --loglevel Built-in server log level: 1=process, 2=error,\n" + " 3=message (default).\n" "\n" " --rf Show information about function .\n" " --rc Show information about class .\n" @@ -825,6 +828,9 @@ static int do_cli(int argc, char **argv) /* {{{ */ case 16: num_repeats = atoi(php_optarg); break; + case 17: + param_error = "--loglevel is only valid with the built-in server (-S).\n"; + break; default: break; } diff --git a/sapi/cli/php_cli_server.c b/sapi/cli/php_cli_server.c index ece42a1e93f9..4f688abce718 100644 --- a/sapi/cli/php_cli_server.c +++ b/sapi/cli/php_cli_server.c @@ -778,6 +778,7 @@ static void sapi_cli_server_log_write(int type, const char *msg) /* {{{ */ #else fprintf(stderr, "[%s] %s\n", buf, msg); #endif + fflush(stderr); } /* }}} */ static void sapi_cli_server_log_message(const char *msg, int syslog_type_int) /* {{{ */ @@ -2908,6 +2909,18 @@ int do_cli_server(int argc, char **argv) /* {{{ */ php_cli_server_log_level--; } break; + case 17: { + char *end = NULL; + long level = strtol(php_optarg, &end, 10); + if (end == php_optarg || *end != '\0' || level < 1 || level > 3) { + fprintf(stderr, + "Invalid --loglevel \"%s\". Use 1 (process), 2 (error), or 3 (message).\n", + php_optarg); + return 1; + } + php_cli_server_log_level = (int) level; + break; + } } } diff --git a/sapi/cli/tests/gh16855.phpt b/sapi/cli/tests/gh16855.phpt new file mode 100644 index 000000000000..6156c6fae43f --- /dev/null +++ b/sapi/cli/tests/gh16855.phpt @@ -0,0 +1,45 @@ +--TEST-- +GH-16855: --loglevel CLI option for the built-in server +--SKIPIF-- + +--FILE-- +&1", $status); +echo "exit=$status\n"; + +echo "=== without -S ===\n"; +passthru("$php -n --loglevel 1 -r 'echo 1;' 2>&1", $status); +echo "exit=$status\n"; + +echo "=== loglevel 1 omits request logs ===\n"; +$quiet = php_cli_server_start('echo "ok";', 'index.php', ['--loglevel', '1']); +file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS); +$out = file_get_contents($quiet->outputFile); +echo str_contains($out, 'Development Server') ? "startup logged\n" : "startup missing\n"; +echo (str_contains($out, 'Accepted') || str_contains($out, ' GET ') || str_contains($out, 'Closing')) + ? "request logs present\n" + : "request logs omitted\n"; +?> +--EXPECT-- +=== help === +help lists --loglevel +=== invalid value === +Invalid --loglevel "9". Use 1 (process), 2 (error), or 3 (message). +exit=1 +=== without -S === +--loglevel is only valid with the built-in server (-S). +exit=1 +=== loglevel 1 omits request logs === +startup logged +request logs omitted diff --git a/sapi/cli/tests/gh16855_default.phpt b/sapi/cli/tests/gh16855_default.phpt new file mode 100644 index 000000000000..56c7205f951c --- /dev/null +++ b/sapi/cli/tests/gh16855_default.phpt @@ -0,0 +1,18 @@ +--TEST-- +GH-16855: built-in server default log level records requests +--SKIPIF-- + +--FILE-- +outputFile); +echo (str_contains($out, 'Accepted') || str_contains($out, ' GET ')) + ? "request logs present\n" + : "request logs missing\n"; +?> +--EXPECT-- +request logs present