Laravel调用fwrite输出特定字符串时应用崩溃问题
问题:Laravel自定义日志函数在特定场景下崩溃
问题重现
在Laravel应用中使用自定义助手函数向服务器端输出日志,正常场景下运行正常,但在中间件中终止请求时,输出字符串" URI:"时应用直接崩溃,且该字符串未被打印。
自定义日志函数代码
if(!defined('STDIN')) define('STDIN', fopen('php://stdin', 'rb')); if(!defined('STDOUT')) define('STDOUT', fopen('php://stdout', 'wb')); if(!defined('STDERR')) define('STDERR', fopen('php://stderr', 'wb')); class Funcoes{ public static function consolelog(...$args): void{ foreach ($args as $arg) { if (is_object($arg) || is_array($arg) || is_resource($arg)) { $output = print_r($arg, true); } else { $output = (string) $arg; } fwrite(STDOUT, $output . "\n"); } } }
中间件中触发崩溃的代码
Funcoes::consolelog("Requisição inválida, headers:\n"); Funcoes::consolelog(getallheaders()); Funcoes::consolelog(" Remote addr:"); Funcoes::consolelog($_SERVER['REMOTE_ADDR']); Funcoes::consolelog(" URI:"); // 此处崩溃,未打印字符串 Funcoes::consolelog($_SERVER['REQUEST_URI']); abort(400);
错误信息
在ServeCommand.php第381行:
Undefined array key 1
PHP版本信息
$ php -v PHP 8.3.11 (cli) (built: Aug 28 2024 22:31:38) (NTS) Copyright (c) The PHP Group Zend Engine v4.3.11, Copyright (c) Zend Technologies with Xdebug v3.3.2, Copyright (c) 2002-2024, by Derick Rethans
完整栈追踪
[2024-09-21 20:52:46] local.ERROR: Undefined array key 1 {"exception":"[object] (ErrorException(code: 0): Undefined array key 1 at /home/user/src/LaravelProj/vendor/laravel/framework/src/Illuminate/Foundation/Console/ServeCommand.php:381) [stacktrace] #0 /home/user/src/LaravelProj/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php(256): Illuminate\\Foundation\\Bootstrap\\HandleExceptions->handleError() #1 /home/user/src/LaravelProj/vendor/laravel/framework/src/Illuminate/Foundation/Console/ServeCommand.php(381): Illuminate\\Foundation\\Bootstrap\\HandleExceptions->Illuminate\\Foundation\\Bootstrap\\{closure}() #2 /home/user/src/LaravelProj/vendor/laravel/framework/src/Illuminate/Foundation/Console/ServeCommand.php(306): Illuminate\\Foundation\\Console\\ServeCommand->getRequestPortFromLine() #3 /home/user/src/LaravelProj/vendor/laravel/framework/src/Illuminate/Collections/Traits/EnumeratesValues.php(257): Illuminate\\Foundation\\Console\\ServeCommand->Illuminate\\Foundation\\Console\\{closure}() #4 /home/user/src/LaravelProj/vendor/laravel/framework/src/Illuminate/Foundation/Console/ServeCommand.php(279): Illuminate\\Support\\Collection->each() #5 /home/user/src/LaravelProj/vendor/laravel/framework/src/Illuminate/Foundation/Console/ServeCommand.php(261): Illuminate\\Foundation\\Console\\ServeCommand->flushOutputBuffer() #6 /home/user/src/LaravelProj/vendor/symfony/process/Process.php(1306): Illuminate\\Foundation\\Console\\ServeCommand->Illuminate\\Foundation\\Console\\{closure}() #7 /home/user/src/LaravelProj/vendor/symfony/process/Process.php(1417): Symfony\\Component\\Process\\Process->Symfony\\Component\\Process\\{closure}() #8 /home/user/src/LaravelProj/vendor/symfony/process/Process.php(1337): Symfony\\Component\\Process\\Process->readPipes() #9 /home/user/src/LaravelProj/vendor/symfony/process/Process.php(860): Symfony\\Component\\Process\\Process->updateStatus() #10 /home/user/src/LaravelProj/vendor/laravel/framework/src/Illuminate/Foundation/Console/ServeCommand.php(105): Symfony\\Component\\Process\\Process->isRunning() #11 /home/user/src/LaravelProj/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36): Illuminate\\Foundation\\Console\\ServeCommand->handle() #12 /home/user/src/LaravelProj/vendor/laravel/framework/src/Illuminate/Container/Util.php(43): Illuminate\\Container\\BoundMethod::Illuminate\\Container\\{closure}() #13 /home/user/src/LaravelProj/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(95): Illuminate\\Container\\Util::unwrapIfClosure() #14 /home/user/src/LaravelProj/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(35): Illuminate\\Container\\BoundMethod::callBoundMethod() #15 /home/user/src/LaravelProj/vendor/laravel/framework/src/Illuminate/Container/Container.php(690): Illuminate\\Container\\BoundMethod::call() #16 /home/user/src/LaravelProj/vendor/laravel/framework/src/Illuminate/Console/Command.php(213): Illuminate\\Container\\Container->call() #17 /home/user/src/LaravelProj/vendor/symfony/console/Command/Command.php(279): Illuminate\\Console\\Command->execute() #18 /home/user/src/LaravelProj/vendor/laravel/framework/src/Illuminate/Console/Command.php(182): Symfony\\Component\\Console\\Command\\Command->run() #19 /home/user/src/LaravelProj/vendor/symfony/console/Application.php(1045): Illuminate\\Console\\Command->run() #20 /home/user/src/LaravelProj/vendor/symfony/console/Application.php(316): Symfony\\Component\\Console\\Application->doRunCommand() #21 /home/user/src/LaravelProj/vendor/symfony/console/Application.php(167): Symfony\\Component\\Console\\Application->doRun() #22 /home/user/src/LaravelProj/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php(197): Symfony\\Component\\Console\\Application->run() #23 /home/user/src/LaravelProj/artisan(33): Illuminate\\Foundation\\Console\\Kernel->handle() #24 {main}
原因分析
Laravel的serve命令(即ServeCommand类)会监听PHP内置服务器的输出,尝试解析日志行中的请求端口(比如[Thu Sep 21 20:52:46 2024] 127.0.0.1:54321 Accepted这种格式)。而自定义函数直接往STDOUT写入内容,当输出的行不符合内置服务器的日志格式时,getRequestPortFromLine方法会尝试分割字符串并取数组索引1,导致数组越界错误。
解决方案
方案1:改用Laravel内置日志系统
放弃直接写STDOUT,使用Laravel官方提供的Log门面,日志会写入项目的日志文件,不会干扰serve命令的输出解析:
use Illuminate\Support\Facades\Log; Log::info("Requisição inválida, headers:", getallheaders()); Log::info("Remote addr: {$_SERVER['REMOTE_ADDR']}"); Log::info("URI: {$_SERVER['REQUEST_URI']}");
方案2:修改自定义函数写入STDERR
将输出目标从STDOUT改为STDERR,因为serve命令不会解析STDERR的输出,这样既可以在控制台看到日志,又不会触发错误:
class Funcoes{ public static function consolelog(...$args): void{ foreach ($args as $arg) { if (is_object($arg) || is_array($arg) || is_resource($arg)) { $output = print_r($arg, true); } else { $output = (string) $arg; } fwrite(STDERR, $output . "\n"); // 替换为STDERR } } }
方案3:生产环境避免使用serve命令
生产环境通常使用Nginx或Apache作为Web服务器,此时直接往STDOUT写内容不会有这个问题,因为没有serve命令的日志解析逻辑。
内容的提问来源于stack exchange,提问作者Fnr
相关产品推荐
相关产品推荐

