Problem/Motivation
When an exception is logged to the dblog by \Drupal\Core\EventSubscriber\ExceptionLoggingSubscriber::onError() or \Drupal\Core\Utility\Error::logException(), there is no backtrace on the log event details page and it is very difficult to find the problem in code.
Steps to reproduce
Throw an exception from a controller and look at the message logged by dblog module.
Proposed resolution
Option A) When logging an exception, include the @backtrace_string placeholder in the log message - this key is already part of the "decoded" exception array. Note this placeholder has become something like a "standard" syntax for core log messages, so custom/contrib loggers can easily process or ignore it as desired. See for example dblog module which wraps the @backtrace_string with <pre class="backtrace"></pre> tags.
Option B) [Chosen resolution] Do not add @backtrace_string to log messages; instead, because @backtrace_string is already part of the context array passed to loggers, dblog module can display the backtrace as a separate row at the bottom of the log details page. See #28
Remaining tasks
User interface changes
dblog module: PHP errors and some throwables already contained a backtrace in the log message - this is now expanded to an additional large set of throwables.
API changes
none
Data model changes
none
Release notes snippet
| Comment | File | Size | Author |
|---|---|---|---|
| #74 | 3175449-74.patch | 4.65 KB | mfb |
Issue fork drupal-3175449
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
super_romeo commentedComment #7
smustgrave commentedThank you for the MR quick fix.
This could use an issue summary update. What is being fixed and why.
Also with a new feature a test will be needed.
Comment #8
mfbShouldn't
$erroralready contain$error['@backtrace_string']so there would be no need to add it to the array?Comment #9
Ankit.Gupta commentedComment #10
mfbTo avoid code duplication, could the string be something like this:
Error::DEFAULT_ERROR_MESSAGE . ' <pre class="backtrace">@backtrace_string</pre>'And as I mentioned, there should be no need to add another element to the $error array - the backtrace string is already there.
Moving this to 10.1.x where new features need to land.
Comment #11
medha kumariRerolled the patch #9 in Drupal 10.1.x and changes comment #10 .
Comment #12
mfb$error should still be passed in as an argument
Comment #13
mfbIt looks like I was wrong about adding the
<pre>tag. Apparently, dblog module adds this tag itself when rendering a log message containing@backtrace_stringSo, this line should read:
$this->logger->get('php')->log($error['severity_level'], Error::DEFAULT_ERROR_MESSAGE . ' @backtrace_string', $error);Comment #14
_pratik_Comment #15
_pratik_@mfb updated patch with the changes you suggested.
Please check.
Comment #16
smustgrave commentedMoving back to NW for the issue summary update and tests
Comment #17
mfbFor anyone who wants to work on tests, I'd suggest adding a functional test to dblog module, using one of the error_test module routes which throw exceptions
Comment #18
smustgrave commentedMay have time this week to try a test as I just used that error_test module for another issue.
May need help with the IS update.
Comment #19
smustgrave commentedHere's a test.
Leaving in NW for the issue summary update.
Comment #20
mfbComment #21
mfbThis is looking good to me so moving to RTBC
Comment #22
alexpottThis test being in the dblog module feels wrong. Because with the dblog module you do have the full backtrace. It'll be in the variables column.
I think the point of this change is that other logging services (for example syslog) don't save all the variables. So for me we should be testing this outside of dblog.
Comment #23
alexpottI think a unit test would suffice here.
Comment #24
mfbYou make a good point @alexpott, and it brings up a question: Since dblog is already storing the @backtrace_string in the variables database column, we could simply display @backtrace_string as a new row at the bottom of the dblog event details page, without "forcing" all loggers to ingest @backtrace_string as part of the log message? I could go either way on this; but as @backtrace_string already does appear in *some* log messages, the current approach of the patch feels pretty reasonable. In any case, I'll work on unit test.
Comment #25
mfbHere's a unit test checking for the correct log message; didn't remove the new dblog functional test as it seems fine to have both.
Comment #26
alexpottThe question in #24 is a good one.
I think we should only be adding the backtrace for critical errors. Like I don't think we should be adding backtraces for \Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException for example.
And we should have concerns about swamping logs.
So yeah I think we should have the ability to display the backtrace in dblog if we have the backtrace_string in the context variables - even if it is not in the message. Great idea @mfb
Comment #27
alexpottAnd then we won't need to make the change to
ExceptionLoggingSubscriberComment #28
mfbHere's the alternate approach to not add @backtrace_string to the log message, and instead display it as a new row at the bottom of the dblog event details page.
Comment #29
alexpott@mfb #28 looks nice!
I think we should only do this if the string does not have the @backtrace_string in it - otherwise the page is going to have a lot of duplicate content.
Comment #30
mfbResolves #29
Comment #31
nitin shrivastava commentedFix command failures #30
Comment #32
alexpottThis should not be here.
Comment #34
mfbResolved #32
Comment #35
smustgrave commentedThis issue is being reviewed by the kind folks in Slack, #needs-review-queue-initiative. We are working to keep the size of Needs Review queue [2700+ issues] to around 400 (1 month or less), following Review a patch or merge request as a guide.
#33 appears (from what I can tell) to be a rebase.
Since there were a number of changes since I added a test case in #19 think I'm good to review.
So reviewing #34
Tested this using the error_test module and visiting /error-test/trigger-exception
Checking the logs and I see
with no backtrace
Applying fix
I see the same issue with the backtrace.
Looks good!
Comment #36
mfbCreated a draft change record @ https://www.drupal.org/node/3340781
Comment #37
alexpottI don't think we should be making these changes here. This will alter the syslog output.
Comment #38
mfbWell, I'm unclear on the logic of sending @backtrace_string to syslog here, but not for most throwables which are logged by the ExceptionLoggingSubscriber. Note that previously PHP triggered lots of old-fashioned PHP errors/warnings/etc. but now throwing an Error is more common, and they go to ExceptionLoggingSubscriber most of the time. And throwables are the main thing that you would expect to have a backtrace for.
My understanding is anything thrown during normal request handling is logged by ExceptionLoggingSubscriber; and outside of that _drupal_log_error() is used (e.g. running a script via drush with drupal bootstrapped).
We could have syslog start logging @backtrace_string? Or have a new syslog config toggle to add @backtrace_string to the syslog message? That way it could finally be logged for most throwabes.
Note that when you send @backtrace_string to syslog you actually get multiple syslog messages, one for each line :/ So I'm not sure everyone appreciates it, but if syslog is your only logging then I guess that's where you want to find backtraces..
Comment #39
alexpott@mfb let’s open a follow-up to discuss that. I don’t think that working that out needs to impact us making this improvement in dblog.
Comment #40
mfbOk removed those changes, and updated the change record to match.
Comment #41
mfbFiled a followup @ #3340963: Sort out @backtrace_string logging by Syslog module
I don't use syslog module so I don't have strong feelings how it should work, but I do maintain two contrib logger modules, so more sanity around this is helpful :)
Comment #42
smustgrave commentedMoving back to RTBC but could some strike out in the issue summary which option wasn't chosen?
Comment #43
mfbComment #45
mfbUnrelated failure
Comment #46
vladimiraus+1. Moving patch to MR.
Comment #47
quietone commentedThere are reasons to convert a patch to an MR but this was already RTBC and doing so it was not needed. There are no comments why the MR was done. This MR is against 9.4.x, where this patch is not eligible. It also generated two unnecessary test runs. To be sure there were no changes I downloaded both the MR and patch. They are the same. Therefor, credit has been removed per How is credit granted for Drupal core issues.
Also, the latest patch is no longer being tested every two day. Since I have already downloaded the patch, I will upload again to be sure that the automatic testing restarts.
Comment #50
mfbThat was an unrelated failure, back to RTBC
Comment #51
quietone commentedI am doing triage on the core RTBC queue.
I read the issue summary, comments and the patch. The proposed resolution matches the patch. The remaining tasks is a bit of date but lets see if I find anything still to do.
I don't know the error handling system so I applied the patch and ran the tests. The output in the logs is just another row for the backtrace and it was formatted correctly. I then copied the trigger_error line from the test to another form and went to that form. The resulting error does not have a backtrace. Why? To answer that I read the CR. I guess the form I was using doesn't use ExceptionLoggingSubscriber?
Now that I have read the CR I see that the branch/version are out of date. The CR mentions adding '@backtrace_string' in the context array. This should be accompanied by a before and after example. Along with anything else one needs to do to use this functionality.
Oh, and the title should explain what the change is. Maybe something like 'Backtrace can be logged when using ExceptionLoggingSubscriber or watchdog_exception()"?
For updating the CR I am adding the tag and setting to NW.
And from reading the patch there were two small things.
Should be "Tests" not "Test".
This reads as if any error logged will have a backtrace which isn't true
Comment #52
mfb@quietone thanks for the review, I tried to clarify the change record. Let me know what you think.
I also updated the test method description to clarify that this specific test is about the backtrace being displayed when a throwable is logged.
The idea is that with this change, Database Logging module will display a backtrace in many common cases. When
trigger_error()is called, both the log message and context should contain@backtrace_stringand therefore the backtrace should be displayed as part of the log message. This was already the case, and is tested by\Drupal\Tests\dblog\Functional\DbLogTest::testBacktrace(). When a throwable is logged, the context should contain@backtrace_string, although the log message will not, and therefore the backtrace should be displayed at the bottom of the details page. This is the new behavior, and is tested by\Drupal\Tests\dblog\Functional\DbLogTest::testOnError().@quietone could you clarify the situation where there is no backtrace so we can double check all this?
(By the way, in followup issue #3340963: Sort out @backtrace_string logging by Syslog module, we can discuss if it makes sense to remove
@backtrace_stringfrom the log message and provide it only in the context, under the reasoning that such a string is too long for a log message, and loggers can retrieve it from the context if/when they want it, as dblog is doing in this case.)Comment #53
mfbRemoved remaining tasks (until next review :)
Comment #54
dagmar@mfb Any particular reason why
Can be calculated inside the
eventDetailsmethod? The logic here seems a bit odd to follow. Also with change from #52::formatMessagewould do more than just formatting the message.Comment #55
mfbOk, I added more documentation re: the
formatMessage()method not just formatting the message, but also adding a backtrace property to the row if there is a @backtrace_string that is not used in the message.In the
formatMessage()method, the variables are unserialized and the backtrace markup object is created for use in the message. It would be redundant to repeat these two steps again in theeventDetails()method. If we need to further refactor this code, it would be nice to figure out a way to do that without redundancy.Comment #56
aaronbaumanlatest MR applies cleanly to 10.1 and works great, thanks y'all!
+1 RTBC from me
Would love to see this feature in an upcoming D10 release, and not have to maintain this patch for the next couple years... do we need a "needs backport" tag for that?
Comment #57
dagmar@mfb I see. Thanks!
Are we re-introducing #2371141: XSS vulnerability when displaying exception backtrace because we are not sanitizing the output? I would love to see a test case for this new addition.
Comment #59
mfbAdded an assertion that the backtrace is HTML-encoded
Comment #60
smustgrave commentedSeems #57 has been addressed. Remarking this one.
Comment #61
silverham commented+1 for interest on getting this one commited.
Comment #62
xjmThere are two open merge requests and a patch in the issue summary. There should be only one canonical patch or merge request listed.
Please close the non-canonical merge request(s) and hide non-canonical patches. If you don't have permission to close merge requests, please hide any non-canonical patches and then document which merge request(s) should be closed in an issue comment and the IS. This will allow a committer to close them for you. Thanks!
Comment #63
smustgrave commented#62 also MR is saying unmeragble so will need manual rebase it seems.
Comment #64
mfbMy latest work on this is patch #59; please close both MRs thank you!
Comment #67
xjmDone, thanks @mfb!
Comment #68
xjmSome things to review and fix -- mostly small docs stuff.
Should there be a new restricted-access permission for viewing backtraces? Or is whatever current permission is required sufficient?
I feel like some things already have backtraces in the description... or am I thinking of devel? If I am thinking of devel, it merely needs a CR. If I'm actually remembering other backtraces in the log, then we also need core issue followups for those cases.
I think the added information should be in a separate paragraph in the row description. (I also think the list of properties should be in a list, but that's out of scope here.)
"Throwable" is a Drupalism used in like five places that we might try to remove from the dictionary. Could we just say "logged exception"?
Nit: "Login" is a noun. "Log in" is a verb. So:
I don't think this comment is helpful as-is. It should either be removed or expanded with more detail.
OTOH, this could use an inline comment. explaining what's going on.
As could this.
As could this. Especially the last line. I had a brief moment of panic with the positive assertion and the JS before I re-read and caught the
htmlspecialchars(). 😛 I would prefer that assertions contain a string literal rather than functions operating on a string.The new parameter needs a typehint and documentation in the docblock.
I think "function call" would work here (rather than "function calling")?
Comment #69
mfbRemoved the "Needs change record" tag because this issue already has a change record. Feel free to criticize and/or re-draft it though :)
I don't think a new permission is necessary, because A) some Drupal core logging mechanisms already make backtraces available as part of the log message, so this is not the first time that site admins have access to exception backtraces, it would just be happening more often now; and B) PHP, assuming it is configured correctly (!), keeps backtraces free of sensitive data via two configs:
zend.exception_ignore_args(should be enabled on production to leave function arguments out of the backtrace) andzend.exception_string_param_max_len(should be zero on production, such that if arguments are added to the backtrace, strings will be cutoff at zero characters).Yes, some things already have a backtrace in the log message: 1) When a \Throwable is thrown early or late in the request lifecycle, it is caught by Drupal's exception handler; and 2) when a PHP error/warning is triggered (these are triggered not thrown), it is caught by Drupal's error handler; both of these mechanisms add the backtrace to the log message.
This issue is addressing two other logging mechanisms. There is no backtrace added to the log message 1) when a \Throwable is thrown in the middle of the request (e.g. in a controller) and caught by \Drupal\Core\EventSubscriber\ExceptionLoggingSubscriber::onError(), or 2) when a developer uses \Drupal\Core\Utility\Error::logException() to manually log a \Throwable. The backtrace is however in the log context in both cases, and thus available to loggers such as dblog, which is what we're taking advantage of in this patch.
This was already explained to some extent in the change record, but we could flesh it out further if need be.
As far as followups: We could e.g. remove backtrace strings where they do already appear in log messages, and switch to the mechanism used in this patch. I like the idea, but I'm not sure there is consensus; some people might be relying on the currently-existing backtraces in their log messages in Syslog or wherever they are logging. Personally, I prefer to have short log messages in Syslog, and some other system for storing backtraces and other large blobs of data ("crash dumps"). I did previously open this related/followup issue for further discussion re: Syslog module: #3340963: Sort out @backtrace_string logging by Syslog module. I think anyone who really cares about logging, and wants to customize it, can install a module like Monolog and go to town, so they're not inclined to worry about how core logs out-of-the-box.
Done.
Changed to \Throwable (a PHP
classinterface, not a Drupalism). "Logged exception" is unclear because we are talking about both \Exception and \Error objects, i.e. \Throwable objects.Fixed
Changed to clearly say what scenario we are testing: "Load a page that throws an exception in the controller, and includes its function arguments in the exception backtrace."
Added "Load details page for the most recent event logged by the "php" logger."
Added that - I think - this is just checking that there's a vaguely-correct dblog-event table with a "Type" header cell? Idk lol.
Added explanation and changed to string literal.
Added types and docblock documentation.
I changed "function calling arguments" to "function arguments" to make it less verbose.
Comment #70
mfbOops I fixed the wrong case of "Login" to "Log in" :p And a typo
Comment #71
smustgrave commentedBelieve the feedback has been addressed
Comment #72
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue.
While you are making the above changes, we recommend that you convert this patch to a merge request. Merge requests are preferred over patches. Be sure to hide the old patch files as well. (Converting an issue to a merge request without other contributions to the issue will not receive credit.)
Comment #73
xjmThanks @mfb; the explanation in #69 is helpful.
Regarding the permissions, I verified that the permission required for dblog pages is
access site reports, which is already a restricted access permission, so that's fine.That said, I noticed there's outstanding feedback from @alexpott that has not been addressed it. It was recommended to only add backtraces for critical messages to avoid blowing up the logs. I can see the value in wanting to have backtraces for non-critical messages; however, I agree with the log size concern also. Maybe a (non-UI) configuration setting would be helpful here? Tagging for framework manager review once we get a proposed solution.
Meanwhile, a couple nitpicks escaped the cleanups above:
This is missing a return typehint.
Also still missing the word "as".
Still missing the word "the".
Comment #74
mfb@xjm This version of the patch is the response to @alexpott's concern. At this point there should not be cause for concern about log size, as we decided to display the @backtrace_string that dblog has already been storing in the variables database column this whole time (it just was not being displayed in these cases).
So there is no size increase here. There is an increase of information available in the UI, but it's helpfully in its own table row, down at the bottom of the page.
Any code doing logging always has the option of removing the @backtrace_string from the context array if it will not be helpful. But I'm not aware of any places where we should do that at this time.
Fixed up those nitpicks
Comment #75
mfb@xjm @alexpott Looking at core/lib/Drupal/Core/EventSubscriber/ExceptionLoggingSubscriber.php I do see one place where we could consider removing @backtrace_string from the context array: The
ExceptionLoggingSubscriber::onClientError()method.It's already removed in the
ExceptionLoggingSubscriber::on403()method.This would clean up the UI by removing the backtrace for miscellaneous 4xx errors (e.g.
\Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException) logged by this mechanism, and would reduce storage size of dblog module going forward, for sites that have been getting lots of non-404/403 4xx errors.Could also be decided in a followup if we're not sure.
Comment #76
mfbThe @backtrace_string was removed for 403 errors in #3167390: ExceptionLoggingSubscriber should not log backtrace string on access denied exceptions. Looks like there was no discussion at that time re: whether or not it'd be a good idea to remove for other 4xx errors.
Comment #77
smustgrave commentedThink this would be a nice feature to have for 10.2.
#75 mentions we could do more in a follow up potentially.
Comment #78
alexpottThe fix in #74 looks great. It's not adding storing anything new - it's just surfacing information we already have - nice. And the follow-ups to consider removing where it is not helpful are great too.
Comment #79
xjmThanks @alexpott.
Let's add an additional followup for #75 and then I think this is ready to go.
Comment #80
longwaveOpened #3402173: Remove @backtrace_string in ExceptionLoggingSubscriber::onClientError()
Comment #81
smustgrave commentedOpened follow up for everyone.
Comment #82
smustgrave commentedAh @longwave beat me. I'll close mine out.
Comment #83
xjmSaving credits.
Comment #84
xjmI had a brief moment of concern that this could pollute other tests. What we usually do is save the existing settings and restore them after the test has run.
However, since this is in the test module rather than the test itself, it's maybe altering the child site PHP settings rather than the parent site settings. Maybe? I do not actually know if the PHP settings are shared between the test runner and test child site or not. We have no other examples of adjusting the ini settings in a test module. I'll ask. Leaving RTBC since this might be fine.
Comment #85
xjmPer @longwave:
So we should be all good here.
Comment #88
xjmAlright, finally committed to 11.x and 10.2.x as a UI change. Also published the change record. Thanks everyone for your dedicated work on this!
Comment #89
alexpottComment #91
jksloan2974 commentedThe later patches only work when using dblog. We try not to use this because of the database writes. We will use syslog or jsonlog. So patch #19 would work best for us.
Comment #92
xjmAmending attribution.