问题 确定用于评估预处理的PHP.INI值的运行时源?


有没有办法通过增强的错误日志消息或输出变量确定运行时使用的CORE PHP DIRECTIVES值的值?这与每个错误的phpinfo()输出相同。

一个基本的例子是error_reporting变量。如果/etc/php.ini值设置为x,并且phpinfo()声明该php.ini文件实际上是ini变量的预期来源。但是如果用户在代码中对该变量实现运行时调整,我们是否能够在抛出错误时输出error_reporting变量。

例如当php.ini文件有E_ALL&〜E_STRICT但日志文件中报告了E_STRICT错误时,有一种方法可以增强输出到日志文件,显示所有系统变量的“堆栈跟踪”可能通过添加到httpd日志的错误ID来激活或至少修改了它们的每个错误的来源,该错误ID对应于在错误发生时显示那些运行时设置的可选文件。

我确实认识到可以使用try / catch,但我不相信可以在实际中实现混合第三方代码的大型部署。

这样的机制是否存在于PHP中,或者是否有人开发过(google说不太可能)?通过将变量设置的相对性与错误消息相关联,可以帮助加速代码库的合并,这对许多其他错误情况可能有所不同。

我希望我只是看着这个错误,并且有一个简单的解决方法,我很想念。

谢谢!


9423
2018-05-31 18:19


起源



答案:


我终于有了一个完整的“pecl runkit”示例,因为我在安装时遇到了麻烦 apd 在一个新鲜的分布(这是一个旧图书馆)。

你可以安装 runkit 它与PECL:

 pecl install runkit

并在php.ini中添加以下行:

 extension=runkit.so
 runkit.internal_override=1

我忘了提到那个 error_reporting 可以用不同的方式定义 error_reporting() 功能还是 ini_set。所以我们要关心每个功能。 首先我们复制旧的 ini_set 运用 runkit_function_copy 然后我们使用重新定义它 runkit_function_redefine

我用 debug_bactrace(1) 获取调用文件和行号。

最后要抓住非致命和致命的错误,我们必须使用 set_error_handler 和 register_shutdown_function

以下代码将在出错后输出 ini_set 被调用和文件名/行。

错误(类型= 2):在文件/var/www/html/test.php第47行中除以零

INI SET堆栈

  • 这里定义了error_reporting(按顺序):      
    • /var/www/html/test.php,第16行,值:0
    • /var/www/html/test.php,第17行,值:2
    • /var/www/html/test.php,第18行,值:1
    • /var/www/html/test.php,第19行,价值:32767

码:

<?php
runkit_function_copy("ini_set", "old_ini_set");

runkit_function_redefine("ini_set", '$key,$value', '
    global $iniset;
    $trace=debug_backtrace(1);
    $iniset[$key][]=$trace[0];
    old_ini_set($key, $value);');

runkit_function_redefine("error_reporting", '$value', '
    global $iniset;
    $trace=debug_backtrace(1);
    $iniset["error_reporting"][]=$trace[0];
    old_ini_set($key, $value);');

// let test now
ini_set("error_reporting", 0);
ini_set("error_reporting", E_WARNING);
error_reporting(E_ERROR);
ini_set("error_reporting", E_ALL);

set_error_handler("custom_error_handler");
register_shutdown_function("custom_error_handler");

function custom_error_handler($errno=NULL, $errstr=NULL) {
    global $iniset;
    if (!($error=func_get_args())) {
        $error=error_get_last();
        if ($error!=E_ERROR) $error=NULL;
    }
    if ($error) {
        echo "Error (type=$error[0]): $error[1]\n
            in file $error[2] line $error[3]<br />\n";
        echo "INI SET stack<br />\n";
        echo "<ul>";
        foreach ($iniset as $key=>$val) {
            echo "<li>$key was defined here (in order):<ul>";
            foreach ($val as $def) {
                echo "<li>{$def['file']}, line {$def['line']},
                      value: ".array_pop($def['args'])."</li>";
            }
            echo "</li></ul>";
        }

        echo "</table>";
    }
}

// division by 0
12/0;

上一篇文章:

您可以使用以下命令获取所有本地和全局配置选项的值 ini_get_all() 功能。

要检索本地值和全局值的值,可以设置 $details参数为true:

ini_get_all(NULL, true);

您可以使用。获取各个选项的值 ini_get() (对于运行时值)和 get_cfg_var() (用于全局php.ini值)函数。

$global_value=get_cfg_var("error_reporting");
$local_value=ini_get("error_reporting");

echo "Error reporting (Local value: $local_value, global value: $global_value)\n"

为了在发生错误时查看这些结果,您必须捕获错误。

对于非致命错误,您可以使用 的set_error_han dler()

对于致命错误,您必须定义shutdown_function(),请参阅 我如何捕获PHP致命错误


7
2017-12-06 12:45



谢谢。我将深入研究这些,但我的目标不仅是获取本地化变量状态,还要捕获在代码运行中设置的“where”。 - Lance
我使用debug_backtrace()添加了一个示例 - Adam
感谢Adam,更改由第三方软件包设置,通常是开源代码。我正在寻找一种在运行时分析代码的方法,而override_function可能就是答案。也会测试一下。 - Lance
您可能知道使用 ini_set() 在合理规模的项目中应该禁止喜欢(参见: 为什么全球国家如此邪恶?)。我非常不愿意在我的代码库中导入第三方代码,它充分利用了这一点。 - RandomSeed
虽然我同意你@RandomSeed,但在DevOps控制的环境之外,这种现实通常不存在。这正是我正在努力的方法,通过在开发评估和集成测试中将开发人员标准化。生产代码总是被剥离,但是我们发现首先理解代码以及编码器更有价值,在为有用部分购买第三方代码之前,我们太时间限制自己构建了。 - Lance


答案:


我终于有了一个完整的“pecl runkit”示例,因为我在安装时遇到了麻烦 apd 在一个新鲜的分布(这是一个旧图书馆)。

你可以安装 runkit 它与PECL:

 pecl install runkit

并在php.ini中添加以下行:

 extension=runkit.so
 runkit.internal_override=1

我忘了提到那个 error_reporting 可以用不同的方式定义 error_reporting() 功能还是 ini_set。所以我们要关心每个功能。 首先我们复制旧的 ini_set 运用 runkit_function_copy 然后我们使用重新定义它 runkit_function_redefine

我用 debug_bactrace(1) 获取调用文件和行号。

最后要抓住非致命和致命的错误,我们必须使用 set_error_handler 和 register_shutdown_function

以下代码将在出错后输出 ini_set 被调用和文件名/行。

错误(类型= 2):在文件/var/www/html/test.php第47行中除以零

INI SET堆栈

  • 这里定义了error_reporting(按顺序):      
    • /var/www/html/test.php,第16行,值:0
    • /var/www/html/test.php,第17行,值:2
    • /var/www/html/test.php,第18行,值:1
    • /var/www/html/test.php,第19行,价值:32767

码:

<?php
runkit_function_copy("ini_set", "old_ini_set");

runkit_function_redefine("ini_set", '$key,$value', '
    global $iniset;
    $trace=debug_backtrace(1);
    $iniset[$key][]=$trace[0];
    old_ini_set($key, $value);');

runkit_function_redefine("error_reporting", '$value', '
    global $iniset;
    $trace=debug_backtrace(1);
    $iniset["error_reporting"][]=$trace[0];
    old_ini_set($key, $value);');

// let test now
ini_set("error_reporting", 0);
ini_set("error_reporting", E_WARNING);
error_reporting(E_ERROR);
ini_set("error_reporting", E_ALL);

set_error_handler("custom_error_handler");
register_shutdown_function("custom_error_handler");

function custom_error_handler($errno=NULL, $errstr=NULL) {
    global $iniset;
    if (!($error=func_get_args())) {
        $error=error_get_last();
        if ($error!=E_ERROR) $error=NULL;
    }
    if ($error) {
        echo "Error (type=$error[0]): $error[1]\n
            in file $error[2] line $error[3]<br />\n";
        echo "INI SET stack<br />\n";
        echo "<ul>";
        foreach ($iniset as $key=>$val) {
            echo "<li>$key was defined here (in order):<ul>";
            foreach ($val as $def) {
                echo "<li>{$def['file']}, line {$def['line']},
                      value: ".array_pop($def['args'])."</li>";
            }
            echo "</li></ul>";
        }

        echo "</table>";
    }
}

// division by 0
12/0;

上一篇文章:

您可以使用以下命令获取所有本地和全局配置选项的值 ini_get_all() 功能。

要检索本地值和全局值的值,可以设置 $details参数为true:

ini_get_all(NULL, true);

您可以使用。获取各个选项的值 ini_get() (对于运行时值)和 get_cfg_var() (用于全局php.ini值)函数。

$global_value=get_cfg_var("error_reporting");
$local_value=ini_get("error_reporting");

echo "Error reporting (Local value: $local_value, global value: $global_value)\n"

为了在发生错误时查看这些结果,您必须捕获错误。

对于非致命错误,您可以使用 的set_error_han dler()

对于致命错误,您必须定义shutdown_function(),请参阅 我如何捕获PHP致命错误


7
2017-12-06 12:45



谢谢。我将深入研究这些,但我的目标不仅是获取本地化变量状态,还要捕获在代码运行中设置的“where”。 - Lance
我使用debug_backtrace()添加了一个示例 - Adam
感谢Adam,更改由第三方软件包设置,通常是开源代码。我正在寻找一种在运行时分析代码的方法,而override_function可能就是答案。也会测试一下。 - Lance
您可能知道使用 ini_set() 在合理规模的项目中应该禁止喜欢(参见: 为什么全球国家如此邪恶?)。我非常不愿意在我的代码库中导入第三方代码,它充分利用了这一点。 - RandomSeed
虽然我同意你@RandomSeed,但在DevOps控制的环境之外,这种现实通常不存在。这正是我正在努力的方法,通过在开发评估和集成测试中将开发人员标准化。生产代码总是被剥离,但是我们发现首先理解代码以及编码器更有价值,在为有用部分购买第三方代码之前,我们太时间限制自己构建了。 - Lance


您可以使用 error_reporting() 没有参数来返回当前的错误报告状态。要测试特定功能,请执行类似操作 error_reporting() & (E_STRICT | E_COMPILE_WARNING),这将测试是否 E_STRICT 和 E_COMPILE_WARNING 已启用。

但是,如果你真的想要那样的话 phpinfo() 详细信息,您始终可以捕获phpinfo输出并将其记录到错误日志(或其他地方),并使用html格式。例如:

ob_start();
phpinfo();
error_log(ob_get_clean());

请注意,这只会将phpinfo()写入日志,并且  在响应中输出它。

此外,您可能需要考虑使用 Xdebug的。如果您正在寻找更好的堆栈跟踪,这可能会有所帮助。


2
2017-12-05 19:36





你的问题的范围有点不清楚,所以我写了我对PHP的错误处理的了解,并将这个任务理解给你。

检测代码更改php.ini设置

您的部分问题表明您运行的代码意外地将设置从其默认值更改,并且您获得应该已禁用的错误类型。

对此的解决方案是不更改代码中的设置,并找到这些位置,有一个工具: PHP Code Sniffer。它有一个规则“ForbiddenFunction”,可以自定义查找调用 ini_seterror_reporting 和别的。

您将扫描代码以获取对这些函数的调用,然后分析它们存在的原因。简单地删除它们可能不是正确的解决方案,因为如果代码更改错误设置,则有一个原因。也许代码错误地将值设置回原始值。

在运行时获取设置的当前值

您可以注册在常规代码执行期间执行的回调 register_tick_function。它将被称为每个“滴答”数,这是低级语句的数量。实际上,您可以使注册函数几乎在每个常规脚本语句之后跳转 - 请注意,这将影响运行时。

函数本身可以尝试分析您喜欢的任何内容,并对变化做出反应。

在出现错误/异常时获取当前值

您可以注册自己的错误和异常处理程序。 的set_error_han dler 接受将被调用的回调,其中包含有关触发的任何错误的所有信息(不包括致命错误 - 如果PHP引擎失败,例如语法错误,则不能再调用任何内容)。

bool handler ( 
    int $errno , 
    string $errstr 
    [, string $errfile 
     [, int $errline 
      [, array $errcontext 
    ]]] 
)

无论使用哪种设置,都将调用该函数 error_reporting() 已被选中,但它也可以读取设置并忽略具有禁用错误的调用。所以在你的情况下,它可以读取全局php.ini设置(或让它硬编码)并忽略所有其他错误。或者在特殊日志文件中报告这些错误。返回 false从这个函数将调用常规错误处理,返回 true 将继续,好像什么也没发生。

可以通过以下方式访问全局变量 $GLOBALS 阵列。

未被捕获的抛出异常将由注册的回调处理 set_exception_handler。回调将获得异常对象,其中包含从消息到堆栈跟踪的所有内容。

现在?

修复代码怎么样?严格的错误有一个原因:更新PHP时,您的代码可能会中断。你没有提供细节,所以这必须是一个通用的建议,但我想知道你的问题的背景是什么。我有一种感觉,你提出错误的问题或试图在地毯下擦除代码问题。你可以做到这一点,但它最终会受到伤害 - 如果目前的情况已经受到伤害,它会在以后受到伤害。转到我的第一个建议:找到代码中的问题并适当地修复它们。其他一切都不是修复代码,而是减少它的错误日志占用空间。


2
2017-12-06 13:36



谢谢Sven,我的问题根本就在于我分析了大量的第三方软件包及其代码,并且通常花费超过50%的时间修复代码(或规范化代码)以便能够将它与苹果进行比较。我正在寻找一种标准化的方法,通过使用内置PHP功能将编码器规范化,从而为我们提供运行时细节,而不会产生过多的运行时间开销。范围是无限的,因为我正在寻找一种非常通用的方法来创建针对代码的变量状态映射,然后在调整其他因素时查看变量出现问题的位置 - Lance
想到它的一种方法是基数图,它显示来自代码行,可变状态和非代码因子(例如硬件,软件,流量,外围设备)的立方数据的结果。 - Lance