function _drupal_log_error

Same name and namespace in other branches
  1. 7.x includes/errors.inc \_drupal_log_error()
  2. 8.9.x core/includes/errors.inc \_drupal_log_error()
  3. 10 core/includes/errors.inc \_drupal_log_error()
  4. 11.x core/includes/errors.inc \_drupal_log_error()

Logs a PHP error or exception and displays an error page in fatal cases.

Parameters

$error: An array with the following keys: %type, @message, %function, %file, %line, @backtrace_string, severity_level, backtrace, and exception. All the parameters are plain-text, with the exception of @message, which needs to be an HTML string, backtrace, which is a standard PHP backtrace, and exception, which is the exception object (or NULL if the error is not an exception).

bool $fatal: TRUE for:

  • An exception is thrown and not caught by something else.
  • A recoverable fatal error, which is a fatal error.

Non-recoverable fatal errors cannot be logged by Drupal.

2 calls to _drupal_log_error()
_drupal_error_handler_real in core/includes/errors.inc
Provides custom PHP error handling.
_drupal_exception_handler in core/includes/bootstrap.inc
Provides custom PHP exception handling.

File

core/includes/errors.inc, line 149

Code

function _drupal_log_error($error, $fatal = FALSE) {
    $is_installer = InstallerKernel::installationAttempted();
    // Backtrace, exception and 'severity_level' are not valid replacement values
    // for t().
    $backtrace = $error['backtrace'];
    $exception = $error['exception'];
    $severity = $error['severity_level'];
    unset($error['backtrace'], $error['exception'], $error['severity_level']);
    // When running inside the testing framework, we relay the errors
    // to the tested site by the way of HTTP headers.
    if (DRUPAL_TEST_IN_CHILD_SITE && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
        _drupal_error_header($error['@message'], $error['%type'], $error['%function'], $error['%file'], $error['%line']);
    }
    $response = new Response();
    // Only call the logger if there is a logger factory available. This can occur
    // if there is an error while rebuilding the container or during the
    // installer.
    if (\Drupal::hasService('logger.factory')) {
        try {
            // Provide the PHP backtrace and exception to logger implementations. Add
            // 'severity_level' to the context to maintain BC and allow logging
            // implementations to use it.
            \Drupal::logger('php')->log($severity, '%type: @message in %function (line %line of %file) @backtrace_string.', $error + [
                'backtrace' => $backtrace,
                'exception' => $exception,
                'severity_level' => $severity,
            ]);
        } catch (\Exception $e) {
            // We can't log, for example because the database connection is not
            // available. At least try to log to PHP error log.
            error_log(strtr('Failed to log error: ' . Error::DEFAULT_ERROR_MESSAGE . ' @backtrace_string', $error));
        }
    }
    // Log fatal errors, so developers can find and debug them.
    if ($fatal) {
        error_log(sprintf('%s: %s in %s on line %d %s', $error['%type'], $error['@message'], $error['%file'], $error['%line'], $error['@backtrace_string']));
    }
    if (PHP_SAPI === 'cli') {
        if ($fatal) {
            // When called from CLI, simply output a plain text message.
            // Should not translate the string to avoid errors producing more errors.
            $response->setContent(html_entity_decode(strip_tags(new FormattableMarkup(Error::DEFAULT_ERROR_MESSAGE, $error))) . "\n");
            $response->send();
            exit(1);
        }
    }
    if (\Drupal::hasRequest() && \Drupal::request()->isXmlHttpRequest()) {
        if ($fatal) {
            if (error_displayable($error)) {
                // When called from JavaScript, simply output the error message.
                // Should not translate the string to avoid errors producing more errors.
                $response->setContent(new FormattableMarkup(Error::DEFAULT_ERROR_MESSAGE, $error));
                $response->send();
            }
            exit;
        }
    }
    else {
        // Display the message if the current error reporting level allows this type
        // of message to be displayed, and unconditionally in update.php.
        $message = '';
        $class = NULL;
        if (error_displayable($error)) {
            $class = 'error';
            // If error type is 'User notice' then treat it as debug information
            // instead of an error message.
            if ($error['%type'] == 'User notice') {
                $error['%type'] = 'Debug';
                $class = 'status';
            }
            // Attempt to reduce verbosity by removing DRUPAL_ROOT from the file path
            // in the message. This does not happen for (false) security.
            if (\Drupal::hasService('kernel')) {
                $root_length = strlen(\Drupal::root());
                if (substr($error['%file'], 0, $root_length) == \Drupal::root()) {
                    $error['%file'] = substr($error['%file'], $root_length + 1);
                }
            }
            // Check if verbose error reporting is on.
            $error_level = _drupal_get_error_level();
            if ($error_level != ERROR_REPORTING_DISPLAY_VERBOSE) {
                // Without verbose logging, use a simple message.
                // We use \Drupal\Component\Render\FormattableMarkup directly here,
                // rather than use t() since we are in the middle of error handling, and
                // we don't want t() to cause further errors.
                $message = new FormattableMarkup(Error::DEFAULT_ERROR_MESSAGE, $error);
            }
            else {
                // With verbose logging, we will also include a backtrace.
                // First trace is the error itself, already contained in the message.
                // While the second trace is the error source and also contained in the
                // message, the message doesn't contain argument values, so we output it
                // once more in the backtrace.
                array_shift($backtrace);
                // Generate a backtrace containing only scalar argument values.
                $error['@backtrace'] = Error::formatBacktrace($backtrace);
                $message = new FormattableMarkup(Error::DEFAULT_ERROR_MESSAGE . ' <pre class="backtrace">@backtrace</pre>', $error);
            }
        }
        if ($fatal) {
            // We fallback to a maintenance page at this point, because the page generation
            // itself can generate errors.
            // Should not translate the string to avoid errors producing more errors.
            $message = 'The website encountered an unexpected error. Please try again later.' . '<br />' . $message;
            if ($is_installer) {
                // install_display_output() prints the output and ends script execution.
                $output = [
                    '#title' => 'Error',
                    '#markup' => $message,
                ];
                install_display_output($output, $GLOBALS['install_state']);
                exit;
            }
            $response->setContent($message);
            $response->setStatusCode(500, '500 Service unavailable (with message)');
            $response->send();
            // An exception must halt script execution.
            exit;
        }
        if ($message) {
            if (\Drupal::hasService('session')) {
                // Message display is dependent on sessions being available.
                \Drupal::messenger()->addMessage($message, $class, TRUE);
            }
            else {
                print $message;
            }
        }
    }
}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.