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.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 {
|
|||||||
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) {
|
|||||||
'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) {
|
|||||||
'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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
|
|
||||||
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 {
|
|||||||
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 {
|
|||||||
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';
|
|||||||
/// 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() {
|
|||||||
_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());
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user