Patch to commit is in #9.

Problem/Motivation

Software has bugs, so errors are there and will be logged.
Sadly by default Drupal just puts the line of the broken code, which is already helpful, but on the other hand often now really helpful, because you need a full backtrace to figure something out.

Proposed resolution

Log a backtrace on top of the actual error message.
This will just make the backtrace available in the variables section, but won't be displayed as part of the watchdog site.

Remaining tasks

User interface changes

API changes

Data model changes

Comments

dawehner created an issue. See original summary.

dawehner’s picture

Status: Active » Needs review
StatusFileSize
new1.72 KB

Here is a patch for that.

Status: Needs review » Needs work

The last submitted patch, 2: 2638140-2.patch, failed testing.

cilefen’s picture

+++ b/core/includes/errors.inc
@@ -129,7 +129,7 @@ function _drupal_log_error($error, $fatal = FALSE) {
+  $backtrace = $error['@backtrace'] = $error['backtrace'];

The backtrace is an array of arrays. I think it is the output of debug_backtrace() so something must be done to make it plain text.

cilefen’s picture

Status: Needs work » Needs review
StatusFileSize
new806 bytes
new1.8 KB
dawehner’s picture

@cilefen++
Ideally we would remove maybe one or two functions from the stack, given that there is _drupal_log_error and _drupal_exception_handler
and _drupal_error_handler_real()

cilefen’s picture

@dawehner

I am not sure you can do that with an exception object. An alternative is a backtrace array to string formatting function.

Status: Needs review » Needs work

The last submitted patch, 5: drupal_log_error-2638140-5.patch, failed testing.

cilefen’s picture

Status: Needs work » Needs review
StatusFileSize
new939 bytes
new2.85 KB

Let's see if I can get this green while we think about ideas.

dawehner’s picture

Yeah you made a good point. I don't think its that important to get the perfect backtrace.

xano’s picture

This is a useful improvement! We may need to remove the first few items off the trace, as those are internal error handling calls. Their presence in the trace is unnecessary and can be confusing.

dawehner’s picture

This is a useful improvement! We may need to remove the first few items off the trace, as those are internal error handling calls. Their presence in the trace is unnecessary and can be confusing.

Well, sure we could, on the other hand, there are different ways how this method is called.
In general making the available data a bit nicer is like the most unimportant thing compared to fixing the actual problem.

xano’s picture

I understood your argument from #10. The way I saw it, was that this is not necessarily about presentation, but about making the data itself less confusing for people who may not be entirely familiar with backtraces and Drupal's error handling.

_drupal_log_error() is an internal function. Though it would be somewhat difficult to determine the exact number of calls to remove from the stack, it would not be impossible.

fabianx’s picture

Status: Needs review » Reviewed & tested by the community

RTBC for me - looks like a great improvement. I am using such a patch sometimes locally.

"Perfect is the enemy of good enough."

Lets fix any problems with the backtrace (too many, too less, etc.) in a follow-up.

dawehner’s picture

cilefen’s picture

Is there a reason not to backport this to 7?

dawehner’s picture

If anyone cares about it ... sure.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

Why don't we just use $error['@backtrace'] = Error::formatBacktrace($error['backtrace']) Then we don't have to generate the backtrace twice.

cilefen’s picture

Status: Needs work » Needs review
StatusFileSize
new1.65 KB
new2.74 KB

Why don't we just use $error['@backtrace'] = Error::formatBacktrace($error['backtrace'])...?

Total ignorance that Error::formatBacktrace() exists.

We may not need that follow-up if this is to everyone's liking.

dawehner’s picture

Status: Needs review » Needs work

@alexpott
Good question. Well I don't really care which one, just that we have one.

dawehner’s picture

Status: Needs work » Reviewed & tested by the community

I'm sorry, I simply haven't see #19, or rather my browser haven't refreshed in a long while.

alexpott’s picture

So thinking about this some more I'm a bit concerned about this resulting in massive log files. It might be nice to make this optional. Not sure.

dawehner’s picture

Well, what is the reason of creating log entires when you cannot make any sense about them in the first place. If you have massive log files, due to huge problems on your site,
IMHO its important to get that information ASAP, a huge log file is than probably the least problem.

catch’s picture

Assigned: Unassigned » alexpott

fwiw I agree with dawehner - if the log entries are big, that's less of a problem than having them in the first place and the backtrace provides more chance of fixing them.

Also at this level there really shouldn't be a massive number being logged - most would go into a proper logging handler (which we already log backtraces for) - unless the site down more or less.

Moving back to Alex for a second look.

dawehner’s picture

(which we already log backtraces for)

You would think so, right?

  public function log($level, $message, array $context = array()) {
    // Remove any backtraces since they may contain an unserializable variable.
    unset($context['backtrace']);

See #2646786: Add a backtrace for logged exceptions for that

alexpott’s picture

Hmmm... actually the comment

    // Remove any backtraces since they may contain an unserializable variable.
    unset($context['backtrace']);

Hints as to why we're not doing this...

+++ b/core/includes/errors.inc
@@ -130,6 +130,7 @@ function _drupal_log_error($error, $fatal = FALSE) {
+  $error['@backtrace'] = Error::formatBacktrace($error['backtrace']);

However I think the above code protects us because of...

      if (isset($trace['args'])) {
        foreach ($trace['args'] as $arg) {
          if (is_scalar($arg)) {
            $call['args'][] = is_string($arg) ? '\'' . Xss::filter($arg) . '\'' : $arg;
          }
          else {
            $call['args'][] = ucfirst(gettype($arg));
          }
        }
      }

So we're not serializing non-scalar arguments.

We have to be certain that adding the backtrace will not cause errors that hide the real error as that would make things worse.

Also I'm not sure why the patch attached to #19 is not showing in the issue summary.

alexpott’s picture

Assigned: alexpott » Unassigned
Status: Reviewed & tested by the community » Needs review

Setting back to "needs review" so that other people can confirm that my thoughts in #26 are correct.

dawehner’s picture

@alexpott
Right, this have been pretty much my train of thought when I created the patch.

If generating that BT causes issues, it would be an issue on other exception pages as well. I thought we consider them as fully working though.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.0-beta1 was released on March 2, 2016, which means new developments and disruptive changes should now be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

dawehner’s picture

+ $error['@backtrace'] = Error::formatBacktrace($error['backtrace']);

Well, this really adds just a string, so what should go wrong? At least in {watchdog} the variable column is blob.

fabianx’s picture

I like adding the backtrace, however Error::formatBacktrace() might indeed do too much.

I personally prefer the backtrace I get from PHP exceptions itself:

$e = new \Exception();
$backtrace = $e->getTraceAsString();

That is:

- a) incredibly fast - as its internally created and formatted
- b) has no superfluous data and internally deals with objects, etc.
- c) is kinda directly what python, ruby, etc. output in its stage.

I now use this exclusively for creating backtraces in my own debugging and we can directly also Xss::filter() the whole string - as there is no HTML output to preserve.

Also those backtraces never get too large.

In fact I think we should even deprecate our backtrace formatting and replace with that method.

dawehner’s picture

See #2638140-18: Error logging should log a backtrace consistently ... some earlier patches did exactly that.

fabianx’s picture

#32 I think we should get back to that. internal backtrace generation is so much faster than all the string manipulation in user code - unless Alex strongly disagrees, e.g. even if we generate it twice as its generated by PHP it is still faster.

And not every error is an Exception in PHP 5.x - IIRC.

In fact, my RTBC for #9 still stands.

dawehner’s picture

IMHO error handling should in general not care about performance but about safety. Doing something not in userland adds safety, IMHO.

fabianx’s picture

Issue tags: +Needs backport to D7

I agree with #34, too.

Can we re-RTBC #9 or should we re-upload os its clearer?

We probably would need a comment explaining why we create a new Exception to get the backtrace().

Also marking for backport to D7 as we just discussed adding that to make specially the missing module errors easier to find, but for D7 it would of course be opt-in.

catch’s picture

I use $e = new Exception(); $e->getTraceAsString() for my own debugging too, so happy with a version that does that.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

Alright, we all agree on #9

fabianx’s picture

Issue summary: View changes
xjm’s picture

Title: drupal_log_error() should log a backtrace. » drupal_log_error() should log a backtrace
Status: Reviewed & tested by the community » Needs work

The issue doesn't have a current patch, so NW for that. (@dawehner says #9 but all the files are hidden.)

Do these backtraces ever show up in watchdog? If so we should have screenshots since it will impact site admins. If not let's document that here.

Thanks! This would be valuable for developers.

dawehner’s picture

Issue summary: View changes
StatusFileSize
new2.85 KB

Well sure, let's reupload the patch.

Do these backtraces ever show up in watchdog? If so we should have screenshots since it will impact site admins. If not let's document that here.

Well yeah this is really just in the variables at the moment, let me document that in the issue.

fabianx’s picture

#40 How would you access the backtrace then?

dawehner’s picture

#40 How would you access the backtrace then?

Well, it would be part of the variables, just not shown.

You know, I tried to keep the patch as simple as possible.

fabianx’s picture

Status: Needs work » Reviewed & tested by the community

Back to RTBC then, you can at least do a DB query or write a contrib module.

dawehner’s picture

Just another usecase for this patch: Debugging errors which happened, maybe just once, in cron.

alexpott’s picture

  1. +++ b/core/includes/errors.inc
    @@ -132,6 +132,9 @@ function _drupal_log_error($error, $fatal = FALSE) {
       $backtrace = $error['backtrace'];
    ...
    +  $e = new \Exception();
    +  $error['@backtrace'] = $e->getTraceAsString();
    

    One thing that is weird is that these backtraces will be different.

  2. +++ b/core/includes/errors.inc
    @@ -132,6 +132,9 @@ function _drupal_log_error($error, $fatal = FALSE) {
       unset($error['backtrace']);
    

    No need to unset now

dawehner’s picture

No need to unset now

Isn't the problem that this backtrace, which contains objects, might be not serializable and end up somewhere? It also seems a bit out of scope of this issue to change that.

One thing that is weird is that these backtraces will be different.

Well, one is a string and the other one is an actual array representation. There are potential different usecases for them :)

alexpott’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new3.36 KB
new3.92 KB

Ah I missed the @ re the unset.

What I meant by different is this... the backtrace is created in _drupal_error_handler_real() and the string version in _drupal_log_error() - I think they should be created in the same place. However interestingly the only other call to _drupal_log_error() is _drupal_log_error(Error::decodeException($exception), TRUE); in _drupal_exception_handler(). This already creates an string backtrace and we should definitely use that one.

alexpott’s picture

StatusFileSize
new3.3 KB
new4.58 KB

BC layer... if we think we need one.

The last submitted patch, 47: 2638140-47.patch, failed testing.

fabianx’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: +Needs change record

RTBC - New approach looks great to me!

@backstrace_string makes a lot of sense.

Might be a good idea to write a quick CR for this?

dawehner’s picture

+++ b/core/includes/bootstrap.inc
@@ -551,7 +551,10 @@ function _drupal_exception_handler($exception) {
+    $error = Error::decodeException($exception);
+    $error['@backtrace_string'] = $error['backtrace_string'];

Can't we use $exception->getTraceAsString() for some form of consistency?

alexpott’s picture

@dawehner that's what happens inside Error::decodeException($exception); - in drupal 9 we can just set the return to have a key @backtrace_string instead of backtrace_string.

dawehner’s picture

@dawehner that's what happens inside Error::decodeException($exception); - in drupal 9 we can just set the return to have a key @backtrace_string instead of backtrace_string.

Nope. Its using its own scheme of encoding the exception.

alexpott’s picture

    return array(
      '%type' => get_class($exception),
      // The standard PHP exception handler considers that the exception message
      // is plain-text. We mimic this behavior here.
      '@message' => $message,
      '%function' => $caller['function'],
      '%file' => $caller['file'],
      '%line' => $caller['line'],
      'severity_level' => static::ERROR,
      'backtrace' => $backtrace,
      'backtrace_string' => $exception->getTraceAsString(),
    );

This is the return from \Drupal\Core\Utility\Error::decodeException() - I don;t understand what is meant by

Nope. Its using its own scheme of encoding the exception.
dawehner’s picture

I was simply wrong, sorry alex

xjm’s picture

+++ b/core/includes/bootstrap.inc
@@ -551,7 +551,10 @@ function _drupal_exception_handler($exception) {
+    $error['@backtrace_string'] = $error['backtrace_string'];
+    unset($error['backtrace_string']);

This causes a bit of squinting and WTF; maybe an inline comment?

xjm’s picture

Status: Reviewed & tested by the community » Needs work
xjm’s picture

Also could still use that CR. Thanks!

xjm’s picture

Issue tags: +rc deadline

I think this would be an okay addition during beta; it's task-ish and BC.

dawehner’s picture

I would kinda say its bugish :)

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new4.7 KB
new635 bytes

I hope this helps ...

dawehner’s picture

Issue tags: -Needs change record

We have a change record now as well.

fabianx’s picture

Status: Needs review » Reviewed & tested by the community
+++ b/core/includes/bootstrap.inc
@@ -562,6 +562,8 @@ function _drupal_exception_handler($exception) {
+    // We use @backtrace_string, as it will end up in the logging as variable
+    // as part of _drupal_log_error().

nit - in the logging == in the logs?

--

Back to RTBC

xjm’s picture

Status: Reviewed & tested by the community » Needs work

Thanks @dawehner and @Fabianx; that helps.

It's still not entirely clear to me. I'd suggest something like:
The 'backtrace_string' key contains the raw backtrace from \Drupal\Utility\Whatever\Error::decodeException(). Store it to '@backtrace_string' for use with a string placeholder in _drupal_error_log(), and unset the old placeholder.

Or something. What's confusing to me is that we are not making this API addition to Error as well and handling the BC there.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new4.5 KB
new1.86 KB

Or something. What's confusing to me is that we are not making this API addition to Error as well and handling the BC there.

Because that would be really out of scope :)

Opened a new follow up: #2775803: Move parts of _drupal_error_handler_real partially to Error / related issue.

The 'backtrace_string' key contains the raw backtrace from \Drupal\Utility\Whatever\Error::decodeException(). Store it to '@backtrace_string' for use with a string placeholder in _drupal_error_log(), and unset the old placeholder.

Fair point. There are already all kind of placeholder variables, so let's just use @backtrace_string there and call it a day.

alexpott’s picture

StatusFileSize
new2.2 KB
new5.45 KB
+++ b/core/lib/Drupal/Core/Utility/Error.php
@@ -71,7 +71,7 @@ public static function decodeException($exception) {
-      'backtrace_string' => $exception->getTraceAsString(),
+      '@backtrace_string' => $exception->getTraceAsString(),

I don't think we should be changing the return array of Error::decodeException() even in a minor release.

+++ b/core/includes/errors.inc
@@ -129,10 +129,6 @@ function error_displayable($error = NULL) {
-  // This was added in Drupal 8.2.0. This code can be removed in Drupal 9.0.0.
-  if (!isset($error['@backtrace_string'])) {
-    $error['@backtrace_string'] = (new \Exception())->getTraceAsString();
-  }

I don't think we should be removing this tiny bc layer either.

@xjm / #64 The reason this change is not made in Error::decodeException is our BC policy.

In my opinion we should go back to #64

Or here is a potentially cleaner way of handling this. Well at least putting all of the BC logic in one place.

alexpott’s picture

StatusFileSize
new576 bytes
new4.88 KB

Removing a (now) unnecessary change.

dawehner’s picture

Well, then we should simply return both entries?

alexpott’s picture

@dawehner I guess we could. Seems messy but I guess we could do & to not have two copies lying around.

alexpott’s picture

StatusFileSize
new2.26 KB
new5.01 KB

Here it is

Status: Needs review » Needs work

The last submitted patch, 70: 2638140-2-70.patch, failed testing.

alexpott’s picture

Status: Needs work » Needs review

The test fail has nothing to do with this patch - https://www.drupal.org/pift-ci-job/396395 - it looks like l.d.o was unavailable for a second for some reason.

fabianx’s picture

Status: Needs review » Reviewed & tested by the community

That is pretty neat way of solving that and keeping BC at the same time, +1 from me.

Back to RTBC

xjm’s picture

@xjm / #64 The reason this change is not made in Error::decodeException is our BC policy.

I said add a key, not change the key. Adding a key to the associative array return value is a totally acceptable API addition.

alexpott’s picture

+++ b/core/lib/Drupal/Core/Utility/Error.php
@@ -61,7 +61,7 @@ public static function decodeException($exception) {
-    return array(
+    $return = array(
       '%type' => get_class($exception),
       // The standard PHP exception handler considers that the exception message
       // is plain-text. We mimic this behavior here.
@@ -71,8 +71,12 @@ public static function decodeException($exception) {

@@ -71,8 +71,12 @@ public static function decodeException($exception) {
       '%line' => $caller['line'],
       'severity_level' => static::ERROR,
       'backtrace' => $backtrace,
+      // @todo https://www.drupal.org/node/2775867 Change to @backtrace_string
+      //   in Drupal 9.
       'backtrace_string' => $exception->getTraceAsString(),
     );
+    $return['@backtrace_string'] = &$return['backtrace_string'];
+    return $return;

Well I'm not exactly wild about these changes. it makes it really weird for anything that was manipulating the 'backtrace_string' before - which one should you manipulate now? In fact it does not matter... which is quite funky...

Psy Shell v0.7.0 (PHP 7.0.7 — cli) by Justin Hileman
>>> $a = []
=> []
>>> $a['a'] = '3';
=> "3"
>>> $a['b'] = &$a['a'];
=> "3"
>>> $a;
=> [
     "a" => &1 "3",
     "b" => &1 "3",
   ]
>>> $a['a'] = '2';
=> "2"
>>> $a;
=> [
     "a" => &1 "2",
     "b" => &1 "2",
   ]
>>> $a['b'] = '3';
=> "3"
>>> $a;
=> [
     "a" => &1 "3",
     "b" => &1 "3",
   ]

A bit special :)

dawehner’s picture

before - which one should you manipulate now

Do we now try to seriously overthink stuff?

  1. Why should you change it, a backtrace doesn't magically change
  2. Why is our error system treated as a public API? Its designed to not be super swappable but rather just implement exactly what it has to do and than stop
  3. The only extension points you could change something here are loggers, which didn't received the backtrace before, which is kind of the point of this issue
xjm’s picture

I actually kinda agree with @alexpott that I would avoid saving it by reference; that tends to lead to Bad Things Happen™.

Still not sure why this would be out of scope:

diff --git a/core/lib/Drupal/Core/Utility/Error.php b/core/lib/Drupal/Core/Utility/Error.php
index aca31fa..ea048be 100644
--- a/core/lib/Drupal/Core/Utility/Error.php
+++ b/core/lib/Drupal/Core/Utility/Error.php
@@ -32,7 +32,17 @@ class Error {
    *   The exception object that was thrown.
    *
    * @return array
-   *   An error in the format expected by _drupal_log_error().
+   *   All the parameters are plain text, with the exception of !message, which
+   *   needs to be a safe HTML string.
+   *   - %type
+   *   - !message
+   *   - %function
+   *   - %file
+   *   - %line
+   *   - severity_level
+   *   - @backtrace_string
+   *   - backtrace_string: (deprecated) Same as @backtrace_string, retained for
+   *     BC. Will be removed before 9.0.0. Use @backtrace_string instead.
    */
   public static function decodeException($exception) {
     $message = $exception->getMessage();
@@ -72,6 +82,7 @@ public static function decodeException($exception) {
       'severity_level' => static::ERROR,
       'backtrace' => $backtrace,
       'backtrace_string' => $exception->getTraceAsString(),
+      '@backtrace_string' => $exception->getTraceAsString(),
     );
   }
 

Then we could just use @backtrace_string everywhere in core but leave backtrace_string for BC. The only thing that seems kind of out of scope is fixing the return value documentation for Error::decodeException() (it's backwards for something in Utility to refer to a procedural function for docs; should be the other way around).

xjm’s picture

Status: Reviewed & tested by the community » Needs review

Going to set at NR until we sort this one way or the other -- sorry @dawehner. :(

alexpott’s picture

StatusFileSize
new4.88 KB

@xjm the reason I made it by reference is that backtraces are not small. Especially in some deeply nested rendering situation like threaded comments. We're already in a error situation and running out of memory for BC seemed pointless.

This is yet another reason I didn't want to actually make any changes to the Error class and just handle all the BC in _drupal_log_error() which why my preference has always been for #67. In my opinion we should go back to that and be done. Having multiple places where we handle BC also feels more fragile.

Reuploading the patch from #67.

fabianx’s picture

Status: Needs review » Reviewed & tested by the community
dawehner’s picture

All I want is to be able to get reasonable backtraces from errors, especially from cron errors.

RTBC+1

alexpott’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new1.8 KB

Discussed the BC at length with @xjm. Salient points:

  • \Drupal\Core\Utility\Error::decodeException() is documented to return An error in the format expected by _drupal_log_error(). - public API depending on _drupal_log_error?
  • The backtrace_string was only introduced in 8.0.5 by #2646786: Add a backtrace for logged exceptions

Decided that the BC layer and difficulties implementing something simple means that we should just change the key and be done. I've updated the CR to reflect this.

alexpott’s picture

StatusFileSize
new3.94 KB
dawehner’s picture

Status: Needs review » Reviewed & tested by the community

Nice! This is the simplest approach one could make

xjm’s picture

Updating issue credit.

xjm’s picture

Title: drupal_log_error() should log a backtrace » _drupal_log_error() should log a backtrace
xjm’s picture

Title: _drupal_log_error() should log a backtrace » Error logging should log a backtrace consistently

  • xjm committed dd1c1ce on 8.2.x
    Issue #2638140 by alexpott, dawehner, cilefen, xjm, Fabianx, Xano, catch...
xjm’s picture

Status: Reviewed & tested by the community » Patch (to be ported)

Committed dd1c1ce and pushed to 8.2.x. Thanks!

This issue is tagged for D7 backport, so according to our current backport policy, a separate issue to discuss that backport should be created and then we can mark this issue fixed.

dawehner’s picture

What is the policy with committing it to 8.1.x now at that point in time?

fabianx’s picture

#90: If its a bug, then it could be pushed to 8.1.x (the new description makes it look like a bug), as FR => no go.

Also:

YEAH!

xjm’s picture

@dawehner I think probably we should not backport it to 8.1.x in this case, particularly given the internal API addition/internal BC break. At this point 8.1.x is in commit freeze for 8.1.8 anyway, so there is only one more bugfix release for 8.1.x that will include any changes we make from this point forward, and we are very conservative for the month before the final patch release.

fabianx’s picture

Status: Patch (to be ported) » Fixed

D7 issue is here: https://www.drupal.org/node/2777955, Thanks all!

  • xjm committed dd1c1ce on 8.3.x
    Issue #2638140 by alexpott, dawehner, cilefen, xjm, Fabianx, Xano, catch...

  • xjm committed dd1c1ce on 8.3.x
    Issue #2638140 by alexpott, dawehner, cilefen, xjm, Fabianx, Xano, catch...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.