以平稳的方式记录错误1. 错误2. 例外情况3. 代码

2022-08-30 12:30:59

我一直在阅读特别是“错误日志记录”,并且我想出了“error_log”功能,这似乎是用于处理错误日志记录的好工具。但是,如何最流畅,最好的使用方式呢?

如果我有

try {
     //try a database connection...

} catch (PDOException $e) {
    error_log($e->getMessage(), 3, "/var/tmp/my-errors.log");

}

这会将错误记录在 my-errors.log 文件中。但是,如果我有时需要更改文件的位置,新文件夹或其他内容,该怎么办?如果我有大量的文件,我需要全部更改它们。

现在我开始考虑使用变量来设置错误日志的路径。当然,这可以工作,但是如果我想在函数或类方法中使用error_log呢?然后我需要将变量设置为全局变量,但这被认为是不好的做法!但是,如果我不应该在类中深入使用这个函数,那不是也被认为是不好的做法吗?什么是好的解决方案?

<?php

function legit() {
    try {
        if (1 == 1) {
            throw new Exception('There was an error here');
        }
    } catch (Exception $e) {
        throw new Exception('throw the error to the try-catch outside the function...');
    }

}

try {
    legit();
} catch (Exception $e) {
    echo 'error here' . $e->getMessage();

    //log it
}

这是我上面所说的一个例子(在类/函数中没有深入的日志记录...这是一个好方法吗?

此外:

我不太确定我应该如何使用例外。假设我想在方法中使用SQL对数据库执行INSERT,我会使用try/catch,然后在失败时重新抛出异常吗?这被认为是好的做法吗?请举例说明。


答案 1

首先,我想赞扬您查看PHP中的标准误差方法。不幸的是,正如您发现的那样,有一些限制。error_log

这是一个很长的答案,请继续阅读以了解:

  1. 错误
    • 直接记录错误与trigger_errorset_error_handler
    • 好错误变坏的地方 - 致命错误。
  2. 异常
    • 声压级
    • 如何处理它们?
  3. 法典
    • 设置
    • 用法

TL;DR用于引发错误和记录错误。trigger_errorset_error_handler

1. 错误

当程序中的情况没有按预期进行时,您经常会希望引发错误,以便通知某人或某事。错误是指程序可能继续,但发生了值得注意的,可能是有害的或错误的情况。此时,许多人希望立即使用他们选择的日志记录包来记录错误。我认为这完全是错误的做法。我建议 使用 来引发错误,以便可以使用 由 设置的回调进行处理。让我们比较这些选项:trigger_errorset_error_handler

直接记录错误

因此,您已选择日志记录包。现在,只要代码中发生错误,就可以将调用分散到记录器。让我们看一下您可能拨打的单个电话(我将使用与Jack答案中的记录器类似的记录器):

Logger::getLogger('standard')->error('Ouch, this hurts');

运行此代码需要什么?

    Class:  Logger
    Method: getLogger
    Return: Object with method 'error'

这些是使用此代码所需的依赖项。每个想要重用此代码的人都必须提供这些依赖项。这意味着标准的PHP配置将不再足以重用您的代码。在最佳情况下,使用依赖关系注入,您仍然需要将记录器对象传递到所有可能发出错误的代码中。

此外,除了代码负责的任何内容之外,它还负责记录错误。这违背了单一责任原则

我们可以看到直接记录错误是不好的

trigger_error救援

PHP有一个名为trigger_error函数可以用来引发错误,就像标准函数一样。与它一起使用的错误级别在错误级别常量中定义。作为用户,您必须使用其中一个用户错误:或默认值(为标准函数等保留其他错误级别)。使用标准的PHP函数来引发错误允许代码在任何标准的PHP安装中重复使用!我们的代码不再负责记录错误(仅确保它已引发)。E_USER_ERRORE_USER_WARNINGE_USER_NOTICE

使用时,我们只执行一半的错误记录过程(引发错误),并将响应错误的责任留给错误处理程序,这将在下面介绍。trigger_error

错误处理程序

我们使用set_error_handler函数设置自定义错误处理程序(请参阅代码设置)。此自定义错误处理程序取代了标准的 PHP 错误处理程序,该处理程序通常根据 PHP 配置设置在 Web 服务器错误日志中记录消息。我们仍然可以通过在自定义错误处理程序中返回来使用此标准错误处理程序。false

自定义错误处理程序只有一个职责:响应错误(包括要执行的任何日志记录)。在自定义错误处理程序中,您具有对系统的完全访问权限,并且可以运行所需的任何类型的日志记录。实际上,任何使用观察者设计模式的记录器都可以(我不打算深入讨论,因为我相信它是次要的)。这应该允许您挂接新的日志观察器,以将输出发送到您需要它的位置。

您可以完全控制对代码的单个可维护部分中的错误执行您喜欢的操作。现在可以在项目之间快速轻松地更改错误日志记录,也可以在单个项目中从一个页面更改为另一个页面。有趣的是,即使被抑制的错误也以0的精度进入自定义错误处理程序,如果error_reporting掩码得到尊重,则不应报告。@errno

当好错误变坏时 - 致命错误

无法从某些错误继续。无法从自定义错误处理程序处理以下错误级别:、 、 、 、 、 。当这些类型的错误由标准函数调用触发时,将跳过自定义错误处理程序并关闭系统。这可以通过以下方式生成:E_ERRORE_PARSEE_CORE_ERRORE_CORE_WARNINGE_COMPILE_ERRORE_COMPILE_WARNING

call_this_function_that_obviously_does_not_exist_or_was_misspelt();

这是一个严重的错误!无法从中恢复,并且系统即将关闭。我们唯一的选择是register_shutdown_function处理关闭。但是,每当脚本完成(成功和不成功)时,都会执行此函数。使用这个和error_get_last当最后一个错误是致命错误时,可以记录一些基本信息(此时系统几乎关闭)。发送正确的状态代码并显示您选择的“内部服务器错误类型”页面也很有用。

2. 例外情况

异常的处理方式与基本错误非常相似。而不是由代码引发异常(使用或从标准函数调用手动)。使用set_exception_handler定义要用于处理异常的回调。trigger_errorthrow new Exception

声压级

标准 PHP 库 (SPL) 提供了例外情况。它们是我提出异常的首选方式,因为它们是PHP的标准部分,不会给代码引入额外的依赖关系。trigger_error

如何处理它们?

当引发异常时,可以做出三种选择:

  1. 捕获它并修复它(然后代码继续,好像没有发生任何不好的事情)。
  2. 抓住它,附加有用的信息,然后重新抛出它。
  3. 让它冒泡到一个更高的层次。

在堆栈的每个级别上,都会做出这些选择。最终,一旦它冒泡到最高级别,您设置的回调将被执行。这是日志记录代码所属的位置(出于与错误处理相同的原因),而不是分散在代码中的语句中。set_exception_handlercatch

3. 代码

设置

错误处理程序

function errorHandler($errno , $errstr, $errfile, $errline, $errcontext)
{
    // Perform your error handling here, respecting error_reporting() and
    // $errno.  This is where you can log the errors.  The choice of logger
    // that you use is based on your preference.  So long as it implements
    // the observer pattern you will be able to easily add logging for any
    // type of output you desire.
}

$previousErrorHandler = set_error_handler('errorHandler');

异常处理程序

function exceptionHandler($e)
{
    // Perform your exception handling here.
}

$previousExceptionHandler = set_exception_handler('exceptionHandler');

关断功能

function shutdownFunction()
{
    $err = error_get_last();

    if (!isset($err))
    {
        return;
    }

    $handledErrorTypes = array(
        E_USER_ERROR      => 'USER ERROR',
        E_ERROR           => 'ERROR',
        E_PARSE           => 'PARSE',
        E_CORE_ERROR      => 'CORE_ERROR',
        E_CORE_WARNING    => 'CORE_WARNING',
        E_COMPILE_ERROR   => 'COMPILE_ERROR',
        E_COMPILE_WARNING => 'COMPILE_WARNING');

    // If our last error wasn't fatal then this must be a normal shutdown.  
    if (!isset($handledErrorTypes[$err['type']]))
    {
        return;
    }

    if (!headers_sent())
    {
        header('HTTP/1.1 500 Internal Server Error');
    }

    // Perform simple logging here.
}

register_shutdown_function('shutdownFunction');

用法

错误

// Notices.
trigger_error('Disk space is below 20%.', E_USER_NOTICE);
trigger_error('Disk space is below 20%.'); // Defaults to E_USER_NOTICE

// Warnings.
fopen('BAD_ARGS'); // E_WARNING fopen() expects at least 2 parameters, 1 given
trigger_error('Warning, this mode could be dangerous', E_USER_WARNING);

// Fatal Errors.    
// This function has not been defined and so a fatal error is generated that
// does not reach the custom error handler.
this_function_has_not_been_defined();
// Execution does not reach this point.

// The following will be received by the custom error handler but is fatal.
trigger_error('Error in the code, cannot continue.', E_USER_ERROR);
// Execution does not reach this point.

异常

之前的三个选项中的每一个都以通用的方式在这里列出,修复它,附加到它并让它冒泡。

1 可修复:

try
{
    $value = code_that_can_generate_exception();
}
catch (Exception $e)
{
    // We decide to emit a notice here (a warning could also be used).
    trigger_error('We had to use the default value instead of ' .
                  'code_that_can_generate_exception\'s', E_USER_NOTICE);
    // Fix the exception.
    $value = DEFAULT_VALUE;
}

// Code continues executing happily here.

2 追加:

在下面观察如何不知道.此级别的 catch 块具有更多信息,如果通过重新抛出异常有用,则可以将其附加到异常中。code_that_can_generate_exception()$context

try
{
    $context = 'foo';
    $value = code_that_can_generate_exception();
}
catch (Exception $e)
{
    // Raise another exception, with extra information and the existing
    // exception set as the previous exception. 
    throw new Exception('Context: ' . $context, 0, $e);
}

3 让它冒泡:

// Don't catch it.

答案 2

有人要求这个答案更适合更多的受众,所以这里开始了。

序言

错误处理通常不是您在编写应用程序时要考虑的第一件事;作为间接结果,它在需要时被固定。但是,利用PHP中的现有机制也不必花费太多。

这是一篇相当长的文章,所以我把它分解成逻辑文本集。

触发错误

在 PHP 中,有两种不同的错误触发方式:

  1. 来自PHP本身(例如使用未定义的变量)或内部函数(例如 无法打开文件),imagecreatefromjpeg
  2. 用户代码使用trigger_error触发的错误,

这些通常打印在您的页面上(除非display_errors被关闭或error_reporting为零),这应该是生产机器的标准配置,除非您像我一样编写完美的代码......继续前进);这些错误也可以被捕获,通过使用稍后解释set_error_handler,让您瞥见代码中的任何障碍。

引发异常

异常与错误在三个主要方面有所不同:

  1. 处理它们的代码可能与抛出它们的位置相去甚远。源的变量状态必须显式传递给 Exception 构造函数,否则您只有堆栈跟踪。
  2. 异常和 catch 之间的代码将完全跳过,而在发生错误(并且不是致命的)之后,代码仍会继续。
  3. 它们可以从主类扩展;这允许您捕获和处理特定的异常,但让其他人在堆栈中冒泡,直到它们被其他代码捕获。另请参见:http://www.php.net/manual/en/language.exceptions.phpException

稍后将给出引发异常的示例。

处理错误

通过注册错误处理程序捕获和处理错误非常简单,例如:

function my_error_handler($errno, $errstr, $errfile = 'unknown', $errline = 0, array $errcontext = array())
{
    // $errcontext is very powerful, it gives you the variable state at the point of error; this can be a pretty big variable in certain cases, but it may be extremely valuable for debugging
    // if error_reporting() returns 0, it means the error control operator was used (@)
    printf("%s [%d] occurred in %s:%d\n%s\n", $errstr, $errno, $errfile, $errline, print_r($errcontext, true));

    // if necessary, you can retrieve the stack trace that led up to the error by calling debug_backtrace()

    // if you return false here, the standard PHP error reporting is performed
}

set_error_handler('my_error_handler');

对于 kicks,您也可以通过注册以下错误处理程序 (PHP >= 5.1) 将所有错误转换为 ErrorException

function exception_error_handler($errno, $errstr, $errfile, $errline)
{
    throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
}

set_error_handler("exception_error_handler");

处理异常

在大多数情况下,您处理的异常尽可能接近导致其允许备份计划的代码。例如,您尝试插入数据库记录,并引发主键约束异常;您可以通过更新记录来恢复(因为大多数数据库可以自己处理这个问题)。有些异常无法在本地处理,因此您希望这些异常向下级联。例:

function insertRecord($user, $name)
{
    try {
        if (true) {
            throw new Exception('This exception should not be handled here');
        }
        // this code is not executed
        $this->db->insert('users', array('uid' => $user, 'name' => $name));
    } catch (PDOException $e) {
        // attempt to fix; an exception thrown here will cascade down
        throw $e; // rethrow exception

        // since PHP 5.3.0 you can also nest exceptions
        throw new Exception("Could not insert '$name'", -1, $e);
    } catch (WhatEverException $e) {
        // guess what, we can handle whatever too
    }
}

滑溜溜的例外

那么,当您在任何地方都没有发现异常时会发生什么呢?您也可以通过使用set_exception_handler来捕获它。

function my_exception_handler(Exception $exception)
{
    // do your stuff here, just don't throw another exception here
}

set_exception_handler('my_exception_handler');

除非您没有有意义的方法来处理代码中任何位置的异常,否则不鼓励这样做。

记录错误/异常

现在您正在处理错误,您必须将其记录在某个地方。以我为例,我使用了Apache从Java移植到PHP的一个项目,称为LOG4PHP。还有其他的,但它说明了灵活的伐木设施的重要性。

它使用以下概念:

  1. 记录器 - 代表您执行日志记录的命名实体;它们可以特定于项目中的类,也可以作为公共记录器共享,
  2. 追加器 - 每个日志请求都可以根据预定义的条件(例如日志级别)发送到一个或多个目标(电子邮件,数据库,文本文件),
  3. 级别 - 日志从调试消息分类为致命错误。

说明不同消息级别的基本用法:

Logger::getLogger('main')->info('We have lift off');
Logger::getLogger('main')->warn('Rocket is a bit hot');
Logger::getLogger('main')->error('Houston, we have a problem');

使用这些概念,您可以对一个非常强大的日志记录工具进行建模。例如,在不更改上述代码的情况下,您可以实现以下设置:

  1. 收集数据库中的所有调试消息,供开发人员查看;您可以在生产服务器上禁用此功能,
  2. 将警告收集到每日文件中,您可能会在一天结束时通过电子邮件发送,
  3. 立即发送有关致命错误的电子邮件。

推荐