While making a change to a method's type hint, it triggered a catchable fatal error.
Drupal sends the following message to the browser: "The website encountered an unexpected error. Please try again later."
I can understand not displaying the details of the fatal to the browser, so next I go to the web server's error log, where that kind of message would normally appear. Nothing there.
The problem appears to be in _drupal_log_error (relevant section condensed for simplicity):
if ($fatal) {
$message = 'The website encountered an unexpected error. Please try again later.' . '<br />' . $message;
$response->setContent($message);
$response->setStatusCode(500, '500 Service unavailable (with message)');
$response->send();
exit;
}
Towards the top of _drupal_log_error, it does try to log the message using `\Drupal::logger('php')`. However, this logger only has dblog by default, and I can't see dblog because Drupal is throwing 500s. I get that I can look in the database to see the error, but that is a pretty awful experience that is far from intuitive for any new dev (it took me a while to realize it was even getting logged to the db).
One simple fix could be to add a line like this in the `if ($fatal)` clause:
error_log(sprintf('%s: %s in %s on line %d', $error['%type'], $error['!message'], $error['%file'], $error['%line']));
However, this function is a bit of a mess and it is very difficult to trace the different paths through it, so I'm not sure what the best solution is. I've run into similar issues like this in the past and I usually just comment out the call to set_error_handler, which we should never have to do.
Why this should be an RC target
No disruption as there is no such thing as an API involved.
We gain happier developers, even more happier devops.
The additional logging generated by this particular issue also doesn't cause any problematic performance problem on working sites. In general syslog/php error log is also reasonable fast, but if we come to the place where we log those fatal errors, we have other problems already.
Comment | File | Size | Author |
---|---|---|---|
#48 | interdiff.txt | 718 bytes | dawehner |
#48 | 2535774-48.patch | 6.83 KB | dawehner |
#45 | interdiff.txt | 1.21 KB | dawehner |
#45 | 2535774-45.patch | 6.79 KB | dawehner |
#40 | interdiff.txt | 553 bytes | dawehner |
Comments
Comment #1
msonnabaum CreditAttribution: msonnabaum commentedComment #2
dawehnerI totally agree, even we made progress recently, this function (_drupal_"log"_error()) is still horrible.
This patch logs the fatal error a little bit earlier, so that we ensure that also CLI/AJAX requests has that advantage.
I doubt that this will be green because we now should ensure each message which is error_log(ged()) which is a really great thing!
Quick tip: If you put
$config['system.logging']['error_level'] = 'verbose';
into settings.php we also print out the error/exception message + backtrace.Also adding a related issue which also makes the exception handling a little bit more sane.
Comment #4
dawehnerSo the second problem seems to be indeed a bug which was not caught before.
Comment #6
Aki Tendo CreditAttribution: Aki Tendo as a volunteer commentedWhat about
Comment #7
dawehnerMh, I don't think we want to display fatal errors unless configured.
Comment #8
Aki Tendo CreditAttribution: Aki Tendo as a volunteer commentedI'm adding a bit of code to have the error handler just throw E_RECOVERABLE_ERROR as a TypeException to match the behavior of PHP 7. That may fix this as a side effect.
Comment #9
Aki Tendo CreditAttribution: Aki Tendo as a volunteer commentedComment #10
dawehnerYeah why not.
Comment #11
dawehnerReroll.
This issue certainly improves the developer experience, this is for sure.
Comment #13
dawehnerAnother reroll
Comment #14
dawehner.
Comment #16
dawehnerHa, let's try again.
Comment #18
dawehnerSo in fact the error handler swallows at least one bug: #2587913: Passing non ascii/special character can cause a database exception in the menu active trail
The other failure is easy to fix.
Comment #19
dawehnerComment #21
Aki Tendo CreditAttribution: Aki Tendo as a volunteer commentedBe careful with the overall premise of mapping E_RECOVERABLE to PHP 7 Exceptions. E_Recoverable usually maps to PHP 7's TypeError exception, but not always. I was going over a grep of PHP 5.6's source code to find all the reasons E_RECOVERABLE can be thrown - it's not something easily done.
Comment #22
dawehner@Aki Tendo
Well, for this issue I'm really more interested in logging the errors, so people can fix their problems. Do you think doing just that is sane? We should talk about converting those errors to exceptions in another isuse.
Comment #23
Aki Tendo CreditAttribution: Aki Tendo as a volunteer commentedLogging them should work. Also, PHP 7 has to be told to throw exceptions in many cases in it's config files for backwards compatibility reasons.
Comment #24
dawehnerNow also with test coverage.
Comment #26
xjmThanks for tagging this for rc target triage! To have committers consider it for inclusion in RC, we should add a statement to the issue summary of why we need to make this change during RC, including what happens if we do not make the change and what any disruptions from it are. We can add a section
<h3>Why this should be an RC target</h3>
to the summary.Comment #27
dawehnerComment #29
jhedstromRe-roll of #24.
Comment #31
jhedstromFrom what I can tell, the fail is actually a valid failure that is only unmasked by this change. All tests are passing, but the following exception is thrown:
When I run that test without this patch, that error is also printed on the test route, but it is not causing the test to fail:
Comment #32
dawehnerOh yeah it totally is, we have a unknown failure in our testsuite. @catch would know the issue which converts the CID to a hash .
Comment #33
jhedstromI think it's #2501191: CacheCollector can result in an exception is cid + classname is too long to be used as a lock name.
Comment #34
professorbikeybike CreditAttribution: professorbikeybike commentedThis is postponed on the fix in #2501191: CacheCollector can result in an exception is cid + classname is too long to be used as a lock name, which could use review :)
Comment #35
dawehnerYeah the other issue landed! Let's get see whether this is green again.
Comment #36
catchUnilaterally tagging RC target because this is causing hidden failures (both test and real-world) in core, and doesn't touch anything outside the error handler or tests.
Comment #37
Wim LeersMissing trailing period.
I think I vaguely remember that "recoverable fatal errors" are treated differently in PHP7; IIRC they become a special kind of exceptions there. If my memory is accurate, I think we need to call that out here too?
Log fatal errors, so developers can debug them.
s/have/has/
Does this need a FQCN?
Debugging leftover.
(IIRC we don't use
debug()
, the global function, for logging errors into test output. I could be wrong though.)Comment #38
dawehnerI think those would throw basically an exception so we would deal with it like that but yeah in case we care about them, we need a general strategy. Pretty sure akitendo has an issue about that out there.
Well, its really about finding in the first place, so let's use both.
Yeah that is a leftover.
Comment #39
Wim LeersSo, I suggested an alternative sentence because:
- the verb "ensure" can be removed here without losing any meaning
- "log out to" sounds very strange
- "debug the problems" also sounds strange
- missing trailing period
So, then I suggest:
Comment #40
dawehnerThank you.
Comment #41
Aki Tendo CreditAttribution: Aki Tendo as a volunteer commentedMost recoverable fatal errors are TypeExceptions in PHP 7 if PHP 7 is configured to throw exceptions in those scenarios. For BC it doesn't do so by default.
Comment #42
dawehnerSo can we concentrate on this particular issue now?
Comment #43
claudiu.cristeaJust a nit and a remark
The indent is 2 spaces too big. The dash should align with "An error...". See https://www.drupal.org/node/1354#lists
I'm not sure if PHP is using
\n
regardless of platform or is OS aware when writing to log. I tried to look in the docs and php-src but no luck to find an answer.Comment #44
catchThere's the PHP_EOL constant.
Comment #45
dawehnerAlright, let's use that, good point.
Comment #46
Wim LeersThis was pretty much nitpicked to death; hence looks super clean now :)
Comment #47
xjmSorry to nitpick a dead patch then, but I don't actually understand the documentation here at all. I think "in case" is maybe a mistranslation? Would this documentation be correct?
Comment #48
dawehnerYeah maybe this makes more sense? Here is an undead patch.
Comment #49
claudiu.cristeaBack to RTBC.
Comment #50
Wim LeersBE WARE OF THE ZOMBIE PATCH
Comment #51
claudiu.cristeaZombie patch?
Comment #52
catchCommitted/pushed to 8.0.x, thanks!