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 {
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());
}
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