【问题标题】:Why does fopen fail within a register shutdown function?为什么 fopen 在寄存器关闭功能中失败?
【发布时间】:2017-01-13 06:53:46
【问题描述】:

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

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

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

    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");    

当我使用非致命错误(例如echo $undefined_variable)对此进行测试时,它可以正常工作并且错误会被正确记录。但是,如果我使用致命错误进行测试,例如undefined_function(),它不会记录任何内容并打印“无法打开日志文件”。如果我在两个错误都存在的情况下进行测试,它会记录非致命错误,但不会记录致命错误。

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

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

标题:“为什么fopen 在注册关闭函数中失败”不是我的标题。那是别人的编辑。我的问题是为什么我不能写入register_shutdown_function 中的日志。我不知道fopen 是失败了还是有其他问题。我试图从该功能中注销某些内容,但它不起作用。从问题中删除既定目标(退出功能),并添加其他人的评估“为什么fopen 失败”,实际上对于任何来到这里寻求解决我遇到的情况的人来说,它的用处要小得多。如果已经存在一个关于在关闭函数中写入文件的问题,我会找到它。 “为什么fopen 失败”太具体,无法在不知道为什么无法写入函数中的文件的人的搜索中有用。

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

【问题讨论】:

  • 您无法记录在错误处理程序注册之前发生的致命错误。所以请确保它首先运行,在单独的文件中执行此操作也是一个好主意。
  • @ArtisticPhoenix:错误处理程序位于会话文件中,该文件包含在每个页面上第一件事。如果我要求错误处理程序打印或显示错误但不记录它,它怎么会打印或显示错误? 编辑:如果您选择对问题投反对票,请告诉我原因,以便我从中学习。谢谢。
  • 如果你不调用函数而是在fatalHandler()本身中进行所有日志记录,它是否有效?
  • @RiggsFolly 感谢您的建议。我尝试它在fatalHandler() 中进行所有日志记录并且不调用函数,但它仍然打印我的测试语句“无法打开..”,但它实际上在屏幕上打印它两次,如如果致命处理程序自动调用errorHandler()

标签: php error-handling fatal-error


【解决方案1】:

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

脚本的工作目录可以在某些 Web 服务器下的关闭功能内更改,例如阿帕奇。

其实第二条评论说:

如果您想对在 register_shutdown_function() 中注册的函数中的文件执行某些操作,请使用文件的绝对路径而不是相对路径。因为当脚本处理完成时,当前工作目录会更改为 ServerRoot(参见 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 社区的通用标准。

【讨论】:

  • 是的,我使用的是相对路径。你的回答解决了我的问题。我注意到有人更改了我的问题的措辞,但我不知道到底是什么失败了(fopen),我只知道错误没有被记录。我知道我可以在php.ini 中指定错误日志,但我正在共享托管环境中的站点上工作。我无权访问它或 ini_set 函数。由于他们的限制,我也无法在 .htaccess 中设置它。感谢您对 Monolog 的建议。
  • 更一般地说,不应在包含或要求中使用相对路径。你可以在这里找到解释和解决方案:stackoverflow.com/questions/36577020/…
【解决方案2】:

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

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");

还有我的 Logger 类:

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

附言通常不需要特殊的日志,如果你有系统的完全访问权限你可以在 php 设置中启用error_log

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-15
    • 2011-11-23
    • 2020-04-25
    相关资源
    最近更新 更多