为什么fopen在寄存器关闭功能中失败?

时间:2016-09-05 22:37:41

标签: php error-handling fatal-error

我正在共享主机中的某个网站上工作。我无权访问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中提到的警告与我的情况无关。这个警告从未发生过。测试消息:“无法打开用于写入的日志文件”是我自己的消息,我写了给自己,所以我会有一些证据表明if声明正在处理中。我发了那条消息。在我的问题中非常清楚地指出,这个信息是我自己制作的。任何遇到问题的人都永远不会得到failed to open stream...etc这样的信息,所以他们不会被一个他们从未得到过的信息的问题所帮助。我认为所有这一切的重点是真正帮助像我这样的假人。

标题:“为什么fopen在注册关闭功能中失败”不是我的头衔。那是别人的编辑。我的问题是为什么我无法写入register_shutdown_function内的日志。我不知道fopen是否失败或是否存在其他问题。我试图从功能中记录一些内容,但它无法正常工作。从问题中删除所述目标(退出该功能),并添加其他人的评估“为什么fopen失败”,实际上对于来到这里解决我遇到的情况的人来说实际上没那么有用。如果已经有一个问题出现在写入关闭函数内的文件,我会找到它。 “为什么fopen失败”过于具体,无法在不知道为什么不能写入函数中的文件的人的搜索中有用。

我不打算对编辑说些什么,但既然你已经让我解释,我正在解释。我意识到每个人都可能获得编辑积分,但是花一点时间考虑你的编辑对问题的有用性的影响,对于那些来到我身边的人来说。

2 个答案:

答案 0 :(得分:4)

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

  

脚本的工作目录可以在某些Web服务器下的关闭功能内部进行更改,例如: Apache的。

事实上,第二条评论说:

  

如果你想对函数中的文件执行某些操作,在register_shutdown_function()中注册,请使用ABSOLUTE路径来代替相对文件。因为当脚本处理完成时,当前工作目录会转向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。默认情况下,它会写入error_log中为php.ini指定的文件。

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

答案 1 :(得分:0)

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

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

P.S。通常不需要特殊日志,如果您可以完全访问系统您可以在php设置中启用error_log