11

Is there a way to determine, via enhanced error log message or output variable, the value of the CORE PHP DIRECTIVES' values used at run-time? This would be identical to a phpinfo() output for each error.

A basic example is the error_reporting variable. If the /etc/php.ini value is set at x, and the phpinfo() states that that php.ini file is in-fact the intended source of the ini variables. But if a user implements a run-time adjustment to that variable in their code, are we able to output the error_reporting variable at the time when an error is thrown.

Such as when the php.ini file has E_ALL & ~E_STRICT but E_STRICT errors are being reported in the log files, it would be great to have a way to enhance the output to the logfile that shows the "stacktrace" of all the system variables active, or at least modified, and their source for each error reported, perhaps via an error-id added to the httpd log that corresponds to an optional file that shows those run-time settings at the time of the error.

I do realize that a try/catch could be used for this, but I don't believe that could be implemented in practicality for a large deployment of mixed 3rd party code.

Does such a mechanism exist in PHP, or has anyone developed one (google says not likely)? This could be vary valuable for many other cases of errors, by helping expedite the merging of code-bases through relativity of variable settings to error message.

I hope that I'm just looking at this wrong and there is an easy fix I'm missing.

Thanks!

Lance
  • 638
  • 1
  • 6
  • 22

4 Answers4

7

I finally have a fully working example with "pecl runkit" as I had trouble installing apd on a fresh distrib (it's an old library).

You can install runkit it with PECL:

 pecl install runkit

and add the following lines in your php.ini:

 extension=runkit.so
 runkit.internal_override=1

I forgot to mention that error_reporting can be defined in different way using error_reporting() function or ini_set. So we have to care about each function. First we copy the old ini_set using runkit_function_copy and then we redefine it using runkit_function_redefine

I use debug_bactrace(1) to get the calling file and line number.

And finally to catch both non-fatal and fatal error, we have to use set_error_handler and register_shutdown_function.

The following code will output, after an error, where ini_set was called and the filename/line.

Error (type=2): Division by zero in file /var/www/html/test.php line 47

INI SET stack

  • error_reporting was defined here (in order):
    • /var/www/html/test.php, line 16, value: 0
    • /var/www/html/test.php, line 17, value: 2
    • /var/www/html/test.php, line 18, value: 1
    • /var/www/html/test.php, line 19, value: 32767

Code:

<?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;

Previous post:

You can get the value of all local and global configuration options with the ini_get_all() function.

To retrieve the values of both local and global value, you can set the $details parameter to true:

ini_get_all(NULL, true);

You can get the value for individual options with the ini_get() (for runtime value) and get_cfg_var() (for global php.ini value) functions.

$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"

In order to see those results when an error occurs, you have to catch the errors.

For non fatal errors, you can use set_error_handler()

For fatal errors, you have to define a shutdown_function(), see How do I catch a PHP Fatal Error

Community
  • 1
  • 1
Adam
  • 17,838
  • 32
  • 54
  • Thanks. I'll dig deeper into those, but my goal is not only to get the localized variable state, but also to capture "where" it was set in that code run. – Lance Dec 06 '15 at 17:42
  • i added an example using debug_backtrace() – Adam Dec 06 '15 at 19:44
  • Thanks Adam, the changes are set by 3rd party packages, typically opensource code. I'm looking for a way to profile code at run-time, and override_function might be the answer. Will test that, too. – Lance Dec 07 '15 at 13:12
  • You may be aware that use of `ini_set()` and the likes should be prohibited in a project of reasonable size (see also: [Why is Global State so Evil?](http://programmers.stackexchange.com/q/148108/105521)). I would be very reluctant to import into my code base an 3rd party piece of code that makes extensive use of this. – RandomSeed Dec 10 '15 at 13:15
  • While I agree with you @RandomSeed, that reality often doesn't exist outside of DevOps controlled environments. And that is exactly what I'm working around, by normalizing the developer out of the equation in evaluations and integration tests. Production code is always stripped, but we find it much more valuable to first understand the code, and thus the coder, before just chop-shopping 3rd party code for the useful parts we were too time constrained to build ourselves. – Lance Dec 10 '15 at 18:25
  • @Adam So your suggestion would work, if I was able to override ini_set AND be able to log the file and line that each call was made. Then I would create a storage object that would monitor each ini_set call along with the session id, as well as the overridden non-fatal and fatal error handling to do the same. Then I could parse each error and see the active trace within the session of the system-variable changes in sequence prior to the errors. Ugh... so close. I will think this functionality needs to be drafted into a feature request for PHP8 – Lance Dec 12 '15 at 04:50
  • I'll try to edit my answer today to provide a more complete example. But I think we have everything to make it work. The only difficult part is the installation of PECL & apd to override ini_set. – Adam Dec 12 '15 at 09:45
2

You can use error_reporting() without a parameter to return the current error reporting status. To test for particular features, do something like error_reporting() & (E_STRICT | E_COMPILE_WARNING), which would test if E_STRICT and E_COMPILE_WARNING are enabled.

If however, you really want that phpinfo() detail, you can always capture the phpinfo output and log it to the error log (or somewhere else), complete with html formatting. For example:

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

Note that this will only write the phpinfo() to the log, and not output it in the response.

Also, you may want to consider using xdebug. If you're looking for a better stack trace, that may help.

James K.
  • 364
  • 1
  • 15
2

The scope of your question is a bit unclear, so I write what I know about PHP's error handling and give the task making sense out of it back to you.

Detecting code changing php.ini settings

Part of your question suggest that you are running code that unexpectedly changes the settings from their default values, and you get error types that should have been disabled.

The solution for this would be to not change the settings in the code, and to find those places, there is a tool: PHP Code Sniffer. It has a rule "ForbiddenFunction" that can be customized to find calls to ini_set, error_reporting and others.

You'd scan the code for calls to these functions and then analyze why they are there. Simply removing them likely isn't the correct solution, because if code changes error settings, there is a reason. Maybe the code incorrectly sets the value back to the original.

Getting at the current values of settings at runtime

You can register a callback that is executed during regular code execution with register_tick_function. It will be called every number of "ticks", which is the number of low level statements. Effectively, you can make the registered function jump in almost after every regular script statement - note that this will affect run time.

The function itself can try to analyze whatever you like, and react to changes.

Getting at the current values in case of errors/exceptions

You can register your own error and exception handler. set_error_handler accepts a callback that will be called with all information about any error that was triggered (not including fatal errors - if the PHP engine fails e.g. with syntax errors, nothing can be called anymore).

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

The function will be called regardless of which setting of error_reporting() was chosen, but it can also read the setting and ignore calls with disabled errors. So in your case, it could read the global php.ini setting (or get it hardcoded) and ignore all other errors. Or report these errors in a special log file. Returning false from this function will call the regular error handling, returning true will go on as if nothing happened.

Access to global variables are possible via the $GLOBALS array.

Thrown exceptions that are not being catched will be handled by a callback registered with set_exception_handler. The callback will get the exception object, which has everything from message to stack trace.

And now?

What about repairing the code? The strict errors are there for a reason: Your code is likely to break when updating PHP. You are not giving details, so this has to be a generic suggestion, but I wonder what is the background of your question. I have a feeling of you asking the wrong question or trying to wipe code problems under the carpet. You can do that, but it will hurt eventually - and if the current situation already hurts, it will hurt even more later. Go for my first suggestion: Find the problems in the code and fix them appropriately. Everything else is not fixing code, but reducing it's error log footprint.

Sven
  • 69,403
  • 10
  • 107
  • 109
  • Thanks Sven, my question is rooted in fact that I analyze a tremendous amount of 3rd party packages, and their code, and usually spend over 50% of our time fixing the code (or normalizing it) to be able to compare it apples to apples. I am seeking for a standardized way to normalize the coder out of the equation by using build-in PHP functionality that will provide us run-time details without excessive run-time overhead. The scope is unbounded, as I'm looking for a very generic way to create a variable-state map against code, and then see where vars become issues when adjusting other factors – Lance Dec 06 '15 at 17:35
  • One way to think of it is a cardinality-map that displays the results of the cubed data from code-line, variable-states, and non-code factors (e.g. hardware, software, traffic, peripherals). – Lance Dec 06 '15 at 17:39
1

I too encountered this issue recently, so phpinfo() was showing it had loaded the correct php.ini but no matter what I do, it just won't use the values I set for the php directives it (PHP) just won't use it.

PHP phpinfo() directory

So I just searched the additional directory /etc/php/7.4/apache2/ for the string key of the directive (memory_limit in my case) as follows

grep -r 'memory_limit' /etc/php/7.4/apache2/

I found that one of the "Additional .ini files parsed" was overriding my php.ini for that directive. Once I got rid of that override, my changes in php.ini started reflecting correctly.

Mohd Abdul Mujib
  • 13,071
  • 8
  • 64
  • 88