-
-
Notifications
You must be signed in to change notification settings - Fork 9.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[HttpKernel][Debug] Fix missing trace on deprecations collected during bootstrapping & silenced errors #23007
Conversation
$logs = array(); | ||
foreach (unserialize(file_get_contents($file)) as $log) { | ||
$log['context'] = array('exception' => new SilencedErrorContext($log['type'], $log['file'], $log['line'])); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not adding trance support for the SilencedErrorContext instead of using reflection on ErrorException ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hesitated. I've finally opted for the \ErrorException
as we already get for runtime deprecations.
Also SilencedErrorContext
implements JsonSerializable
; I wasn't convinced what to do with the trace regarding this. If it's cleaned from any object (by not having objects and arguments in the trace) it's ok. But by adding a $trace
argument, we can't assume the trace is safe and would need to be cleaned (hence not really a DTO anymore)...or simply ignored in the serialized version.
Do deprecations catched by the error handler have their trace properly already ? You haven't changed this code path |
This might explode memory. |
They did. So I didn't dig deeper in the debug component and the usage of the
I think we're safe enough, as we ignore args and objects (which anyway is required to me as I serialize the trace). But perhaps set a limit on |
Works for us 👍
|
@stof : I updated the PR to use back the SilencedErrorContext DTO. Actually, the trace was already available for runtime deprecations because they were thrown, and thus convert to
|
@@ -687,4 +676,23 @@ protected function getFatalErrorHandlers() | |||
new ClassNotFoundFatalErrorHandler(), | |||
); | |||
} | |||
|
|||
private function cleanTrace($backtrace, $type, $file, $line, $throw, $excludeSelf = true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rename $excludeSelf to $offset (integer)?
I'd remove the default value also
@@ -407,7 +407,8 @@ public function handleError($type, $message, $file, $line) | |||
$errorAsException = self::$toStringException; | |||
self::$toStringException = null; | |||
} elseif (!$throw && !($type & $level)) { | |||
$errorAsException = new SilencedErrorContext($type, $file, $line); | |||
$lightTrace = $this->tracedErrors & $type ? $this->cleanTrace($backtrace ?: debug_backtrace(), $type, $file, $line, false, false) : array(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)
public function JsonSerialize() | ||
{ | ||
return array( | ||
'severity' => $this->severity, | ||
'file' => $this->file, | ||
'line' => $this->line, | ||
'trace' => array_map(function ($item) { | ||
unset($item['object'], $item['args']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these should not be injected in the first place to I'd remove that filtering
@@ -545,11 +545,21 @@ protected function initializeContainer() | |||
return $previousHandler ? $previousHandler($type, $message, $file, $line) : false; | |||
} | |||
|
|||
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this provide value at all?
it looks like duplication to me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't get this comment :/ . I need to call debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)
here to get the trace to be added to the array we persist in appDevDebugProjectContainerDeprecations.log
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm suggesting we don't need this trace at all: it's always the same so it provides no value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, nevermind, this is in an error handler so the stack is dynamic.
@@ -93,12 +92,12 @@ public static function castSilencedErrorContext(SilencedErrorContext $e, array $ | |||
$a[$s] = new ConstStub(self::$errorTypes[$a[$s]], $a[$s]); | |||
} | |||
|
|||
$trace = array( | |||
$trace = $a[$sPrefix.'trace'] ?: array(array( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is not compatible with older version of Debug, an isset check is missing
I'm still worried about the perf impact, both in terms of memory and speed. Deprecations can be thousands, this may add overhead to a hot code path. I was about to propose deduplicating silenced notices based solely on the message. To prevent any mem leak, we should have a fixed size dedup map, keyed by messages. Then we could add a counter to silenced error objects. Or any better idea. The main point is doing as less as possible here :) |
For the record, I made some comparisons for runtimes silenced errors:
So indeed, dedup silenced notices based on their type, file, line and message looks like a good tradeoff to me.
|
Here is one more (unfinished) step: --- a/src/Symfony/Component/Debug/ErrorHandler.php
+++ b/src/Symfony/Component/Debug/ErrorHandler.php
@@ -408,13 +408,17 @@ class ErrorHandler
$errorAsException = self::$toStringException;
self::$toStringException = null;
} elseif (!$throw && !($type & $level)) {
- $cacheKey = md5("$type/$line/$file\0$message", true);
- if (isset(self::$silencedErrorCache[$cacheKey])) {
- $errorAsException = self::$silencedErrorCache[$cacheKey];
- } else {
- $lightTrace = $this->tracedErrors & $type ? $this->cleanTrace(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), $type, $file, $line, false) : array();
- $errorAsException = self::$silencedErrorCache[$cacheKey] = new SilencedErrorContext($type, $file, $line, $lightTrace);
+ if (isset(self::$silencedErrorCache[$message])) {
+ self::$silencedErrorCache[$message]->count += 1;
+
+ return;
+ }
+
+ if (100 < count(self::$silencedErrorCache)) {
+ self::$silencedErrorCache = array();
}
+ $lightTrace = $this->tracedErrors & $type ? $this->cleanTrace(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), $type, $file, $line, false) : array();
+ $errorAsException = self::$silencedErrorCache[$message] = new SilencedErrorContext($type, $file, $line, $lightTrace);
} else {
if ($scope) {
$errorAsException = new ContextErrorException($logMessage, 0, $type, $file, $line, $context); |
Is just using |
md5 removal is on purpose, that's a "heavy" call. |
Better? --- a/src/Symfony/Component/Debug/ErrorHandler.php
+++ b/src/Symfony/Component/Debug/ErrorHandler.php
@@ -408,13 +408,17 @@ class ErrorHandler
$errorAsException = self::$toStringException;
self::$toStringException = null;
} elseif (!$throw && !($type & $level)) {
- $cacheKey = md5("$type/$line/$file\0$message", true);
- if (isset(self::$silencedErrorCache[$cacheKey])) {
- $errorAsException = self::$silencedErrorCache[$cacheKey];
- } else {
- $lightTrace = $this->tracedErrors & $type ? $this->cleanTrace(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), $type, $file, $line, false) : array();
- $errorAsException = self::$silencedErrorCache[$cacheKey] = new SilencedErrorContext($type, $file, $line, $lightTrace);
+ if (100 < ++self::$silencedErrorCounter)) {
+ self::$silencedErrorCache = array();
+ self::$silencedErrorCounter = 1;
+ } elseif (isset(self::$silencedErrorCache[$message])) {
+ self::$silencedErrorCache[$message]->count += 1;
+
+ return;
}
+ $lightTrace = $this->tracedErrors & $type ? $this->cleanTrace(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), $type, $file, $line, false) : array();
+ $errorAsException = self::$silencedErrorCache[$message] = new SilencedErrorContext($type, $file, $line, $lightTrace);
} else {
if ($scope) {
$errorAsException = new ContextErrorException($logMessage, 0, $type, $file, $line, $context); |
@@ -100,6 +100,8 @@ class ErrorHandler | |||
private static $stackedErrors = array(); | |||
private static $stackedErrorLevels = array(); | |||
private static $toStringException = null; | |||
private static $silencedErrorCache = array(); | |||
private static $silencedErrorCounter = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be intialized at 1 (same value as when reinit below)
@@ -407,7 +409,17 @@ public function handleError($type, $message, $file, $line) | |||
$errorAsException = self::$toStringException; | |||
self::$toStringException = null; | |||
} elseif (!$throw && !($type & $level)) { | |||
$errorAsException = new SilencedErrorContext($type, $file, $line); | |||
if (!isset(self::$silencedErrorCache[$message]) && ++self::$silencedErrorCounter > 100) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd remove the first isset check here, to still keep 1/100 duplicates
); | ||
$sanitizedLogs[$log['message']] = $log; | ||
|
||
continue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we keep 1/100 as I just proposed, this will need to account for this possibility
…g bootstrapping & silenced errors
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 (deps=high will be fixed when merging into 3.4)
Thank you @ogizanagi. |
…ected during bootstrapping & silenced errors (ogizanagi) This PR was merged into the 3.3 branch. Discussion ---------- [HttpKernel][Debug] Fix missing trace on deprecations collected during bootstrapping & silenced errors | Q | A | ------------- | --- | Branch? | 3.3 <!-- see comment below --> | Bug fix? | yes | New feature? | no <!-- don't forget updating src/**/CHANGELOG.md files --> | BC breaks? | no | Deprecations? | no <!-- don't forget updating UPGRADE-*.md files --> | Tests pass? | yes | Fixed tickets | #22958 <!-- #-prefixed issue number(s), if any --> | License | MIT | Doc PR | N/A |Before|After| |--|--| |<img width="1086" alt="screenshot 2017-06-01 a 10 12 07" src="https://cloud.githubusercontent.com/assets/2211145/26670940/feb51b52-46b3-11e7-806f-e23e2eb248c1.PNG">|<img width="1094" alt="screenshot 2017-06-01 a 10 13 39" src="https://cloud.githubusercontent.com/assets/2211145/26670941/feb8bd66-46b3-11e7-8e54-cc4959487b7a.PNG">| (failures unrelated or deps=high fixed when merged in upper branches) Commits ------- 21ef065 [HttpKernel][Debug] Fix missing trace on deprecations collected during bootstrapping & silenced errors
(failures unrelated or deps=high fixed when merged in upper branches)