为什么 fopen 在寄存器关闭函数中失败?

Why does fopen fail within a register shutdown function?

我正在共享主机中的站点上工作。我无权访问 PHP ini 文件或 PHP ini_set 函数,并且我无法使用 php_flagphp_value 指令。

我正在使用 set_error_handler 函数来记录大多数错误,并尝试使用 register_shutdown_function 来记录致命错误。我在这里尝试了几种捕获致命错误的解决方案:How do I catch a PHP Fatal Error .我可以成功地使用那里的解决方案在屏幕上显示错误或调出自定义错误页面,并记录 non-fatal 错误,但我似乎无法记录致命错误。

这是我尝试的最简单版本:

    function errorHandler($num, $str, $file, $line) {

        $now        = strftime("%B %d, %Y at %I:%M %p", time());
        $text       = "{$now} → error {$num} in {$file}, line {$line}: {$str}\n";
        $errorlog   = "my_path/my_log";

        $new        = file_exists($errorlog) ? false : true;
        if($handle  = fopen($errorlog, 'a')) {
            fwrite($handle, $text);
            fclose($handle);
            if($new) { chmod($errorlog, 0755); }
        } else {
            print("Could not open log file for writing"); //I'm using this to test
        }

    }

    function fatalHandler() {
        $error  = error_get_last();
        if($error) {
            errorHandler($error["type"], $error["message"], $error["file"], $error["line"]);
        }
    }

    set_error_handler("errorHandler");
    register_shutdown_function("fatalHandler");    

当我用 non-fatal 错误(如 echo $undefined_variable)测试它时,它工作正常并且错误被正确记录。但是,如果我使用致命错误进行测试,例如 undefined_function(),它不会记录任何内容并打印 "could not open log file"。如果我在两个错误都存在的情况下进行测试,它会记录 non-fatal 一个错误,但不会记录致命错误。

您的问题已被确定为可能与另一个问题重复。如果那里的答案没有解决您的问题,请编辑以详细解释您问题的独特部分。

@Vic Seedoubleyew -嗯,最明显的是你提到的问题中提到的警告 failed to open stream: ...etc 与我的情况无关。该警告从未发生过。测试消息:"could not open log file for writing" 是 我自己的 消息,我写 给自己 ,所以我会有一些证据表明 if 正在处理语句。那条消息是我编造的。在我的问题中非常清楚地指出,该消息是我自己制作的。遇到我遇到的问题的任何人都永远不会收到消息 failed to open stream...etc,因此解决他们从未收到的消息的问题不会对他们有所帮助。我认为所有这一切的目的是真正帮助像我这样的傻瓜。

标题:"Why does fopen fail within a register shutdown function" 不是我的标题。那是别人的编辑。我的问题是为什么我不能在 register_shutdown_function 内写入日志。我不知道 fopen 是失败了还是有其他问题。我试图从函数中记录一些东西,但它没有用。从问题中删除既定目标(注销功能),并添加其他人的评估 "why does fopen fail",实际上它对任何来这里寻求解决我所遇到情况的人来说用处不大。如果已经有一个问题解决了在关闭函数中写入文件的问题,我会找到它的。 "Why does fopen fail" 太具体了,对于不知道为什么不能在函数中写入文件的人的搜索没有用。

我本来不想说任何关于编辑的事情,但既然你要我解释,我就解释一下。我知道每个人都可能会因为编辑而获得分数,但请花一点时间考虑一下您的编辑对问题的有用性的影响,因为有人遇到了我遇到的情况。

您使用的是自定义错误日志的相对路径吗?如果是这样,register_shutdown_function page 上的这条注释可能是相关的:

Working directory of the script can change inside the shutdown function under some web servers, e.g. Apache.

事实上,第二条评论说:

If you want to do something with files in function, that registered in register_shutdown_function(), use ABSOLUTE paths to files instead of relative. Because when script processing is complete current working directory chages to ServerRoot (see httpd.conf)

我要提到的其他事情:

您的变量 $error 和逻辑 if ($error) { ... 具有误导性。看,当您使用 register_shutdown_function 注册函数时,您是在告诉 PHP 每次 调用该函数 您的脚本完成执行 - 无论是否存在是一个错误。这意味着即使您的脚本以非致命错误结束,甚至根本没有错误,也会调用 fatalHandler

由于您有处理非致命错误的替代方法(使用 set_error_handler),您应该专门检查 fatalHandler 中的致命错误 :

function fatalHandler() {
    if( $error !== NULL && $error['type'] == E_ERROR) {
        errorHandler($error["type"], $error["message"], $error["file"], $error["line"]);
        header("HTTP/1.1 500 Internal Server Error");
        exit();
    }
}

查看 PHP 错误级别列表 here

您可能没有意识到这一点,但是 PHP 有一个 built-in error-logging function。默认情况下,它会写入 php.ini.

中为 error_log 指定的文件

如果您对更高级的日志记录感兴趣,并且通常将您的 PHP-fu 提升到一个新的水平,我建议查看 Monolog 包。它或多或少被认为是登录专业 PHP 社区的通用标准。

对于这种情况,我只包含文件
有这样的内容:

function ErrorHandler($errno, $errstr, $errfile, $errline) {
    if(in_array($errno, [E_NOTICE, E_USER_NOTICE, E_WARNING])) { //, E_WARNING, E_CORE_WARNING, E_DEPRECATED, E_USER_DEPRECATED])) {
        return;
    }

    $err_type='ERROR  ';

    switch ($errno)
    {
        case E_ERROR:
        case E_CORE_ERROR:
        case E_COMPILE_ERROR:
        case E_PARSE:
            $err_type='FATAL  ';
            break;

        case E_USER_ERROR:
        case E_RECOVERABLE_ERROR:
            $err_type='ERROR  ';
            break;

        case E_WARNING:
        case E_CORE_WARNING:
        case E_COMPILE_WARNING:
        case E_USER_WARNING:
            $err_type='WARNING  ';
            break;

        case E_NOTICE:
        case E_USER_NOTICE:
            $err_type='NOTICE  ';
            break;

        case E_STRICT:
            $err_type='STRICT  ';
            break;
    }

    $log_text = $err_type.$errfile.'('.$errline.')'.' :  ('.$errno.')  '.$errstr;
    Logger::log('ErrorHandler', $log_text); // Logger is custom class that appends to log file or sends email, You can write same one
}

function ShutdownHandler() {
    $last_error=error_get_last();
    if(!empty($last_error)) {
        if(in_array($last_error['type'], [E_NOTICE, E_USER_NOTICE])) { //, E_WARNING, E_CORE_WARNING, E_DEPRECATED, E_USER_DEPRECATED])) {
            return;
        }

        $error = [];
        foreach($last_error AS $key => $val) {
            $error[] = $key.' : '.$val."\n";
        }
        Logger::log('ShutdownHandler', implode(', ', $error));
        exit(-1);
    }
}

function ExceptionHandler($exception) {
    Logger::log('ExceptionHandler', $exception->getFile().'('.$exception->getLine().')'.' :  '.$exception->getMessage());
}

// here I register handlers for different types of situations
set_error_handler("ErrorHandler", E_ALL);
set_exception_handler("ExceptionHandler");
register_shutdown_function("ShutdownHandler");

还有我的记录器class:

class Logger {
  public static $logDir;
  public static setLogDir($dir) {
    self::$logDir = realpath($dir);
  }

  public static logFile($label = '') {
    return 
      self::$logDir.'/'
        .date('Ymd')
        .(($label!='')?'.'.$label:'')
        .'.log'; // example: /somepath/logs/20160906.ExceptionHandler.log
  }

  public static function log($label, $data) {
    @file_put_contents(self::logFile($label), $data, FILE_APPEND);
  }
}

Logger::setLogDir(__DIR__.'/logs'); // modify logs path as You wish

p.s。通常不需要特殊日志,如果您对系统具有完全访问权限您可以在 php 设置中启用 error_log