Further improve error reporting by wrapping messages.
This commit is contained in:
parent
ee703da9de
commit
7f2efb2cfd
@ -135,7 +135,7 @@ class FlutterErrorDetailsForPointerEventDispatcher extends FlutterErrorDetails {
|
||||
this.event,
|
||||
this.hitTestEntry,
|
||||
FlutterInformationCollector informationCollector,
|
||||
bool silent
|
||||
bool silent: false
|
||||
}) : super(
|
||||
exception: exception,
|
||||
stack: stack,
|
||||
|
@ -90,7 +90,7 @@ class FlutterErrorDetailsForPointerRouter extends FlutterErrorDetails {
|
||||
this.route,
|
||||
this.event,
|
||||
FlutterInformationCollector informationCollector,
|
||||
bool silent
|
||||
bool silent: false
|
||||
}) : super(
|
||||
exception: exception,
|
||||
stack: stack,
|
||||
|
@ -20,7 +20,7 @@ bool debugCheckHasMaterial(BuildContext context) {
|
||||
'To introduce a Material widget, you can either directly include one, or use a widget that contains Material itself, '
|
||||
'such as a Card, Dialog, Drawer, or Scaffold.\n'
|
||||
'The specific widget that could not find a Material ancestor was:\n'
|
||||
' ${context.widget}'
|
||||
' ${context.widget}\n'
|
||||
'The ownership chain for the affected widget is:\n'
|
||||
' ${element.debugGetCreatorChain(10)}'
|
||||
);
|
||||
@ -39,7 +39,7 @@ bool debugCheckHasScaffold(BuildContext context) {
|
||||
'No Scaffold widget found.\n'
|
||||
'${context.widget.runtimeType} widgets require a Scaffold widget ancestor.\n'
|
||||
'The specific widget that could not find a Scaffold ancestor was:\n'
|
||||
' ${context.widget}'
|
||||
' ${context.widget}\n'
|
||||
'The ownership chain for the affected widget is:\n'
|
||||
' ${element.debugGetCreatorChain(10)}'
|
||||
);
|
||||
|
@ -46,7 +46,7 @@ abstract class MultiChildLayoutDelegate {
|
||||
assert(() {
|
||||
if (child == null) {
|
||||
throw new FlutterError(
|
||||
'The $this custom multichild layout delegate tried to lay out a non-existent child:\n'
|
||||
'The $this custom multichild layout delegate tried to lay out a non-existent child.\n'
|
||||
'There is no child with the id "$childId".'
|
||||
);
|
||||
}
|
||||
@ -60,7 +60,7 @@ abstract class MultiChildLayoutDelegate {
|
||||
assert(constraints.debugAssertIsNormalized);
|
||||
} on AssertionError catch (exception) {
|
||||
throw new FlutterError(
|
||||
'The $this custom multichild layout delegate provided invalid box constraints for the child with id "$childId":\n'
|
||||
'The $this custom multichild layout delegate provided invalid box constraints for the child with id "$childId".\n'
|
||||
'$exception\n'
|
||||
'The minimum width and height must be greater than or equal to zero.\n'
|
||||
'The maximum width must be greater than or equal to the minimum width.\n'
|
||||
|
@ -889,7 +889,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
||||
informationCollector: (StringBuffer information) {
|
||||
information.writeln('The following RenderObject was being processed when the exception was fired:\n${this}');
|
||||
if (debugCreator != null)
|
||||
information.writeln('This RenderObject had the following creator:\n$debugCreator');
|
||||
information.writeln('This RenderObject had the following creator:\n $debugCreator');
|
||||
List<String> descendants = <String>[];
|
||||
const int maxDepth = 5;
|
||||
int depth = 0;
|
||||
@ -897,8 +897,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
||||
int lines = 0;
|
||||
void visitor(RenderObject child) {
|
||||
if (lines < maxLines) {
|
||||
descendants.add('${" " * depth}$child');
|
||||
depth += 1;
|
||||
descendants.add('${" " * depth}$child');
|
||||
if (depth < maxDepth)
|
||||
child.visitChildren(visitor);
|
||||
depth -= 1;
|
||||
@ -2155,7 +2155,7 @@ class FlutterErrorDetailsForRendering extends FlutterErrorDetails {
|
||||
String context,
|
||||
this.renderObject,
|
||||
FlutterInformationCollector informationCollector,
|
||||
bool silent
|
||||
bool silent: false
|
||||
}) : super(
|
||||
exception: exception,
|
||||
stack: stack,
|
||||
|
@ -124,6 +124,8 @@ class FlutterError extends AssertionError {
|
||||
|
||||
static int _errorCount = 0;
|
||||
|
||||
static const int _kWrapWidth = 120;
|
||||
|
||||
/// Prints the given exception details to the console.
|
||||
///
|
||||
/// The first time this is called, it dumps a very verbose message to the
|
||||
@ -135,7 +137,7 @@ class FlutterError extends AssertionError {
|
||||
static void dumpErrorToConsole(FlutterErrorDetails details) {
|
||||
assert(details != null);
|
||||
assert(details.exception != null);
|
||||
bool reportError = !details.silent;
|
||||
bool reportError = details.silent != true; // could be null
|
||||
assert(() {
|
||||
// In checked mode, we ignore the "silent" flag.
|
||||
reportError = true;
|
||||
@ -144,19 +146,26 @@ class FlutterError extends AssertionError {
|
||||
if (!reportError)
|
||||
return;
|
||||
if (_errorCount == 0) {
|
||||
final String header = '-- EXCEPTION CAUGHT BY ${details.library} '.toUpperCase();
|
||||
const String footer = '------------------------------------------------------------------------';
|
||||
debugPrint('$header${"-" * (footer.length - header.length)}');
|
||||
debugPrint('The following exception was raised${ details.context != null ? " ${details.context}" : ""}:');
|
||||
debugPrint('${details.exception}');
|
||||
final String header = '\u2550\u2550\u2561 EXCEPTION CAUGHT BY ${details.library} \u255E'.toUpperCase();
|
||||
final String footer = '\u2501' * _kWrapWidth;
|
||||
debugPrint('$header${"\u2550" * (footer.length - header.length)}');
|
||||
debugPrint('The following exception was raised${ details.context != null ? " ${details.context}" : ""}:', wrapWidth: _kWrapWidth);
|
||||
debugPrint('${details.exception}', wrapWidth: _kWrapWidth);
|
||||
if ((details.exception is AssertionError) && (details.exception is! FlutterError)) {
|
||||
debugPrint('Either the assertion indicates an error in the framework itself, or we should '
|
||||
'provide substantially more information in this error message to help you determine '
|
||||
'and fix the underlying cause.', wrapWidth: _kWrapWidth);
|
||||
debugPrint('In either case, please report this assertion by filing a bug on GitHub:', wrapWidth: _kWrapWidth);
|
||||
debugPrint(' https://github.com/flutter/flutter/issues/new');
|
||||
}
|
||||
if (details.informationCollector != null) {
|
||||
StringBuffer information = new StringBuffer();
|
||||
details.informationCollector(information);
|
||||
debugPrint(information.toString());
|
||||
debugPrint(information.toString(), wrapWidth: _kWrapWidth);
|
||||
}
|
||||
if (details.stack != null) {
|
||||
debugPrint('Stack trace:');
|
||||
debugPrint('${details.stack}$footer');
|
||||
debugPrint('Stack trace:', wrapWidth: _kWrapWidth);
|
||||
debugPrint('${details.stack}$footer'); // StackTrace objects include a trailing newline
|
||||
} else {
|
||||
debugPrint(footer);
|
||||
}
|
||||
|
@ -8,13 +8,20 @@ import 'dart:collection';
|
||||
/// Prints a message to the console, which you can access using the "flutter"
|
||||
/// tool's "logs" command ("flutter logs").
|
||||
///
|
||||
/// If a wrapWidth is provided, each line of the message is word-wrapped to that
|
||||
/// width. (Lines may be separated by newline characters, as in '\n'.)
|
||||
///
|
||||
/// This function very crudely attempts to throttle the rate at which messages
|
||||
/// are sent to avoid data loss on Android. This means that interleaving calls
|
||||
/// to this function (directly or indirectly via [debugDumpRenderTree] or
|
||||
/// [debugDumpApp]) and to the Dart [print] method can result in out-of-order
|
||||
/// messages in the logs.
|
||||
void debugPrint(String message) {
|
||||
_debugPrintBuffer.addAll(message.split('\n'));
|
||||
void debugPrint(String message, { int wrapWidth }) {
|
||||
if (wrapWidth != null) {
|
||||
_debugPrintBuffer.addAll(message.split('\n').expand((String line) => _wordWrap(line, wrapWidth)));
|
||||
} else {
|
||||
_debugPrintBuffer.addAll(message.split('\n'));
|
||||
}
|
||||
if (!_debugPrintScheduled)
|
||||
_debugPrintTask();
|
||||
}
|
||||
@ -44,7 +51,81 @@ void _debugPrintTask() {
|
||||
_debugPrintStopwatch.start();
|
||||
}
|
||||
}
|
||||
final RegExp _indentPattern = new RegExp('^ *(?:[-+*] |[0-9]+[.):] )?');
|
||||
enum _WordWrapParseMode { inSpace, inWord, atBreak }
|
||||
Iterable<String> _wordWrap(String message, int width) sync* {
|
||||
if (message.length < width) {
|
||||
yield message;
|
||||
return;
|
||||
}
|
||||
Match prefixMatch = _indentPattern.matchAsPrefix(message);
|
||||
String prefix = ' ' * prefixMatch.group(0).length;
|
||||
int start = 0;
|
||||
int startForLengthCalculations = 0;
|
||||
bool addPrefix = false;
|
||||
int index = prefix.length;
|
||||
_WordWrapParseMode mode = _WordWrapParseMode.inSpace;
|
||||
int lastWordStart;
|
||||
int lastWordEnd;
|
||||
while (true) {
|
||||
switch (mode) {
|
||||
case _WordWrapParseMode.inSpace: // at start of break point (or start of line); can't break until next break
|
||||
while ((index < message.length) && (message[index] == ' '))
|
||||
index += 1;
|
||||
lastWordStart = index;
|
||||
mode = _WordWrapParseMode.inWord;
|
||||
break;
|
||||
case _WordWrapParseMode.inWord: // looking for a good break point
|
||||
while ((index < message.length) && (message[index] != ' '))
|
||||
index += 1;
|
||||
mode = _WordWrapParseMode.atBreak;
|
||||
break;
|
||||
case _WordWrapParseMode.atBreak: // at start of break point
|
||||
if ((index - startForLengthCalculations > width) || (index == message.length)) {
|
||||
// we are over the width line, so break
|
||||
if ((index - startForLengthCalculations <= width) || (lastWordEnd == null)) {
|
||||
// we should use this point, before either it doesn't actually go over the end (last line), or it does, but there was no earlier break point
|
||||
lastWordEnd = index;
|
||||
}
|
||||
if (addPrefix) {
|
||||
yield prefix + message.substring(start, lastWordEnd);
|
||||
} else {
|
||||
yield message.substring(start, lastWordEnd);
|
||||
addPrefix = true;
|
||||
}
|
||||
if (lastWordEnd >= message.length)
|
||||
return;
|
||||
// just yielded a line
|
||||
if (lastWordEnd == index) {
|
||||
// we broke at current position
|
||||
// eat all the spaces, then set our start point
|
||||
while ((index < message.length) && (message[index] == ' '))
|
||||
index += 1;
|
||||
start = index;
|
||||
mode = _WordWrapParseMode.inWord;
|
||||
} else {
|
||||
// we broke at the previous break point, and we're at the start of a new one
|
||||
assert(lastWordStart > lastWordEnd);
|
||||
start = lastWordStart;
|
||||
mode = _WordWrapParseMode.atBreak;
|
||||
}
|
||||
startForLengthCalculations = start - prefix.length;
|
||||
assert(addPrefix);
|
||||
lastWordEnd = null;
|
||||
} else {
|
||||
// save this break point, we're not yet over the line width
|
||||
lastWordEnd = index;
|
||||
// skip to the end of this break point
|
||||
mode = _WordWrapParseMode.inSpace;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Dump the current stack to the console using [debugPrint].
|
||||
///
|
||||
/// The current stack is obtained using [StackTrace.current].
|
||||
void debugPrintStack() {
|
||||
debugPrint(StackTrace.current.toString());
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user