Commit 7f2efb2c authored by Ian Hickson's avatar Ian Hickson

Further improve error reporting by wrapping messages.

parent ee703da9
...@@ -135,7 +135,7 @@ class FlutterErrorDetailsForPointerEventDispatcher extends FlutterErrorDetails { ...@@ -135,7 +135,7 @@ class FlutterErrorDetailsForPointerEventDispatcher extends FlutterErrorDetails {
this.event, this.event,
this.hitTestEntry, this.hitTestEntry,
FlutterInformationCollector informationCollector, FlutterInformationCollector informationCollector,
bool silent bool silent: false
}) : super( }) : super(
exception: exception, exception: exception,
stack: stack, stack: stack,
......
...@@ -90,7 +90,7 @@ class FlutterErrorDetailsForPointerRouter extends FlutterErrorDetails { ...@@ -90,7 +90,7 @@ class FlutterErrorDetailsForPointerRouter extends FlutterErrorDetails {
this.route, this.route,
this.event, this.event,
FlutterInformationCollector informationCollector, FlutterInformationCollector informationCollector,
bool silent bool silent: false
}) : super( }) : super(
exception: exception, exception: exception,
stack: stack, stack: stack,
......
...@@ -20,7 +20,7 @@ bool debugCheckHasMaterial(BuildContext context) { ...@@ -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, ' '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' 'such as a Card, Dialog, Drawer, or Scaffold.\n'
'The specific widget that could not find a Material ancestor was:\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' 'The ownership chain for the affected widget is:\n'
' ${element.debugGetCreatorChain(10)}' ' ${element.debugGetCreatorChain(10)}'
); );
...@@ -39,7 +39,7 @@ bool debugCheckHasScaffold(BuildContext context) { ...@@ -39,7 +39,7 @@ bool debugCheckHasScaffold(BuildContext context) {
'No Scaffold widget found.\n' 'No Scaffold widget found.\n'
'${context.widget.runtimeType} widgets require a Scaffold widget ancestor.\n' '${context.widget.runtimeType} widgets require a Scaffold widget ancestor.\n'
'The specific widget that could not find a Scaffold ancestor was:\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' 'The ownership chain for the affected widget is:\n'
' ${element.debugGetCreatorChain(10)}' ' ${element.debugGetCreatorChain(10)}'
); );
......
...@@ -46,7 +46,7 @@ abstract class MultiChildLayoutDelegate { ...@@ -46,7 +46,7 @@ abstract class MultiChildLayoutDelegate {
assert(() { assert(() {
if (child == null) { if (child == null) {
throw new FlutterError( 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".' 'There is no child with the id "$childId".'
); );
} }
...@@ -60,7 +60,7 @@ abstract class MultiChildLayoutDelegate { ...@@ -60,7 +60,7 @@ abstract class MultiChildLayoutDelegate {
assert(constraints.debugAssertIsNormalized); assert(constraints.debugAssertIsNormalized);
} on AssertionError catch (exception) { } on AssertionError catch (exception) {
throw new FlutterError( 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' '$exception\n'
'The minimum width and height must be greater than or equal to zero.\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' '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 { ...@@ -889,7 +889,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
informationCollector: (StringBuffer information) { informationCollector: (StringBuffer information) {
information.writeln('The following RenderObject was being processed when the exception was fired:\n${this}'); information.writeln('The following RenderObject was being processed when the exception was fired:\n${this}');
if (debugCreator != null) 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>[]; List<String> descendants = <String>[];
const int maxDepth = 5; const int maxDepth = 5;
int depth = 0; int depth = 0;
...@@ -897,8 +897,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -897,8 +897,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
int lines = 0; int lines = 0;
void visitor(RenderObject child) { void visitor(RenderObject child) {
if (lines < maxLines) { if (lines < maxLines) {
descendants.add('${" " * depth}$child');
depth += 1; depth += 1;
descendants.add('${" " * depth}$child');
if (depth < maxDepth) if (depth < maxDepth)
child.visitChildren(visitor); child.visitChildren(visitor);
depth -= 1; depth -= 1;
...@@ -2155,7 +2155,7 @@ class FlutterErrorDetailsForRendering extends FlutterErrorDetails { ...@@ -2155,7 +2155,7 @@ class FlutterErrorDetailsForRendering extends FlutterErrorDetails {
String context, String context,
this.renderObject, this.renderObject,
FlutterInformationCollector informationCollector, FlutterInformationCollector informationCollector,
bool silent bool silent: false
}) : super( }) : super(
exception: exception, exception: exception,
stack: stack, stack: stack,
......
...@@ -124,6 +124,8 @@ class FlutterError extends AssertionError { ...@@ -124,6 +124,8 @@ class FlutterError extends AssertionError {
static int _errorCount = 0; static int _errorCount = 0;
static const int _kWrapWidth = 120;
/// Prints the given exception details to the console. /// Prints the given exception details to the console.
/// ///
/// The first time this is called, it dumps a very verbose message to the /// The first time this is called, it dumps a very verbose message to the
...@@ -135,7 +137,7 @@ class FlutterError extends AssertionError { ...@@ -135,7 +137,7 @@ class FlutterError extends AssertionError {
static void dumpErrorToConsole(FlutterErrorDetails details) { static void dumpErrorToConsole(FlutterErrorDetails details) {
assert(details != null); assert(details != null);
assert(details.exception != null); assert(details.exception != null);
bool reportError = !details.silent; bool reportError = details.silent != true; // could be null
assert(() { assert(() {
// In checked mode, we ignore the "silent" flag. // In checked mode, we ignore the "silent" flag.
reportError = true; reportError = true;
...@@ -144,19 +146,26 @@ class FlutterError extends AssertionError { ...@@ -144,19 +146,26 @@ class FlutterError extends AssertionError {
if (!reportError) if (!reportError)
return; return;
if (_errorCount == 0) { if (_errorCount == 0) {
final String header = '-- EXCEPTION CAUGHT BY ${details.library} '.toUpperCase(); final String header = '\u2550\u2550\u2561 EXCEPTION CAUGHT BY ${details.library} \u255E'.toUpperCase();
const String footer = '------------------------------------------------------------------------'; final String footer = '\u2501' * _kWrapWidth;
debugPrint('$header${"-" * (footer.length - header.length)}'); debugPrint('$header${"\u2550" * (footer.length - header.length)}');
debugPrint('The following exception was raised${ details.context != null ? " ${details.context}" : ""}:'); debugPrint('The following exception was raised${ details.context != null ? " ${details.context}" : ""}:', wrapWidth: _kWrapWidth);
debugPrint('${details.exception}'); 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) { if (details.informationCollector != null) {
StringBuffer information = new StringBuffer(); StringBuffer information = new StringBuffer();
details.informationCollector(information); details.informationCollector(information);
debugPrint(information.toString()); debugPrint(information.toString(), wrapWidth: _kWrapWidth);
} }
if (details.stack != null) { if (details.stack != null) {
debugPrint('Stack trace:'); debugPrint('Stack trace:', wrapWidth: _kWrapWidth);
debugPrint('${details.stack}$footer'); debugPrint('${details.stack}$footer'); // StackTrace objects include a trailing newline
} else { } else {
debugPrint(footer); debugPrint(footer);
} }
......
...@@ -8,13 +8,20 @@ import 'dart:collection'; ...@@ -8,13 +8,20 @@ import 'dart:collection';
/// Prints a message to the console, which you can access using the "flutter" /// Prints a message to the console, which you can access using the "flutter"
/// tool's "logs" command ("flutter logs"). /// 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 /// 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 /// are sent to avoid data loss on Android. This means that interleaving calls
/// to this function (directly or indirectly via [debugDumpRenderTree] or /// to this function (directly or indirectly via [debugDumpRenderTree] or
/// [debugDumpApp]) and to the Dart [print] method can result in out-of-order /// [debugDumpApp]) and to the Dart [print] method can result in out-of-order
/// messages in the logs. /// messages in the logs.
void debugPrint(String message) { void debugPrint(String message, { int wrapWidth }) {
_debugPrintBuffer.addAll(message.split('\n')); if (wrapWidth != null) {
_debugPrintBuffer.addAll(message.split('\n').expand((String line) => _wordWrap(line, wrapWidth)));
} else {
_debugPrintBuffer.addAll(message.split('\n'));
}
if (!_debugPrintScheduled) if (!_debugPrintScheduled)
_debugPrintTask(); _debugPrintTask();
} }
...@@ -44,7 +51,81 @@ void _debugPrintTask() { ...@@ -44,7 +51,81 @@ void _debugPrintTask() {
_debugPrintStopwatch.start(); _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() { void debugPrintStack() {
debugPrint(StackTrace.current.toString()); debugPrint(StackTrace.current.toString());
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment