Skip to content

Commit 613684f

Browse files
authored
fix long log details (#10011)
1 parent bdb7c05 commit 613684f

3 files changed

Lines changed: 165 additions & 12 deletions

File tree

‎packages/devtools_app/lib/src/screens/logging/logging_controller.dart‎

Lines changed: 75 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -538,23 +538,23 @@ class LoggingController extends DevToolsScreenController
538538
_isNotNull(stackTrace)) {
539539
detailsComputer = () async {
540540
// Get the full string value of the message.
541-
String result = await _retrieveFullStringValue(
541+
final fullMessage = await _retrieveFullStringValue(
542542
service,
543543
e.isolate!,
544544
messageRef,
545545
);
546546

547+
String? fullError;
547548
// Get information about the error object. Some users of the
548549
// dart:developer log call may pass a data payload in the `error`
549550
// field, encoded as a json encoded string, so handle that case.
550551
if (_isNotNull(error)) {
551552
if (error!.valueAsString != null) {
552-
final errorString = await _retrieveFullStringValue(
553+
fullError = await _retrieveFullStringValue(
553554
service,
554555
e.isolate!,
555556
error,
556557
);
557-
result += '\n\n$errorString';
558558
} else {
559559
// Call `toString()` on the error object and display that.
560560
final toStringResult = await service!.invoke(
@@ -566,25 +566,28 @@ class LoggingController extends DevToolsScreenController
566566
);
567567

568568
if (toStringResult is ErrorRef) {
569-
final errorString = _valueAsString(error);
570-
result += '\n\n$errorString';
569+
fullError = _valueAsString(error);
571570
} else if (toStringResult is InstanceRef) {
572-
final str = await _retrieveFullStringValue(
571+
fullError = await _retrieveFullStringValue(
573572
service,
574573
e.isolate!,
575574
toStringResult,
576575
);
577-
result += '\n\n$str';
578576
}
579577
}
580578
}
581579

582580
// Get info about the stackTrace object.
583-
if (_isNotNull(stackTrace)) {
584-
result += '\n\n${_valueAsString(stackTrace)}';
585-
}
586-
587-
return result;
581+
final fullStackTrace = _isNotNull(stackTrace)
582+
? _valueAsString(stackTrace)
583+
: null;
584+
585+
return computeDeveloperLogDetailsJson(
586+
e.json!,
587+
fullMessage: fullMessage,
588+
fullError: fullError,
589+
fullStackTrace: fullStackTrace,
590+
);
588591
};
589592
}
590593

@@ -966,6 +969,66 @@ String? _valueAsString(InstanceRef? ref) {
966969
: ref.valueAsString;
967970
}
968971

972+
/// Returns a JSON-encoded Logging event with full string values applied.
973+
@visibleForTesting
974+
String computeDeveloperLogDetailsJson(
975+
Map<String, dynamic> eventJson, {
976+
required String fullMessage,
977+
String? fullError,
978+
String? fullStackTrace,
979+
}) {
980+
final detailsJson = Map<String, dynamic>.of(eventJson);
981+
final logRecord = detailsJson['logRecord'];
982+
if (logRecord is Map<String, dynamic>) {
983+
final logRecordCopy = Map<String, dynamic>.of(logRecord);
984+
985+
logRecordCopy['message'] = _applyFullStringToInstanceJson(
986+
logRecordCopy['message'],
987+
fullMessage,
988+
);
989+
990+
if (fullError != null) {
991+
final errorJson = logRecordCopy['error'];
992+
if (errorJson is Map<String, dynamic> &&
993+
errorJson['valueAsString'] != null) {
994+
logRecordCopy['error'] = _applyFullStringToInstanceJson(
995+
errorJson,
996+
fullError,
997+
);
998+
} else {
999+
logRecordCopy['errorAsString'] = fullError;
1000+
}
1001+
}
1002+
1003+
if (fullStackTrace != null) {
1004+
final stackJson = logRecordCopy['stackTrace'];
1005+
if (stackJson is Map<String, dynamic> &&
1006+
stackJson['valueAsString'] != null) {
1007+
logRecordCopy['stackTrace'] = _applyFullStringToInstanceJson(
1008+
stackJson,
1009+
fullStackTrace,
1010+
);
1011+
} else {
1012+
logRecordCopy['stackTraceAsString'] = fullStackTrace;
1013+
}
1014+
}
1015+
1016+
detailsJson['logRecord'] = logRecordCopy;
1017+
}
1018+
1019+
return jsonEncode(detailsJson);
1020+
}
1021+
1022+
Object? _applyFullStringToInstanceJson(Object? instanceJson, String fullValue) {
1023+
if (instanceJson is! Map<String, dynamic>) return instanceJson;
1024+
return <String, dynamic>{
1025+
...instanceJson,
1026+
'valueAsString': fullValue,
1027+
'valueAsStringIsTruncated': false,
1028+
'length': fullValue.length,
1029+
};
1030+
}
1031+
9691032
/// A log data object that includes optional summary information about whether
9701033
/// the log entry represents an error entry, the log entry kind, and more
9711034
/// detailed data for the entry.

‎packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ To learn more about DevTools, check out the
7272

7373
* Correct time units and cumulative nature of GC events.
7474
[#9890](https://github.com/flutter/devtools/pull/9890)
75+
* Fixed a bug where long `dart:developer` log messages lost their structured
76+
Details view. -
77+
[#10011](https://github.com/flutter/devtools/pull/10011),
7578

7679
## App size tool updates
7780

‎packages/devtools_app/test/screens/logging/logging_controller_test.dart‎

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,93 @@ void main() {
504504
expect(nullDetails.prettyPrinted(), null);
505505
},
506506
);
507+
508+
test('computeDeveloperLogDetailsJson keeps structured details', () async {
509+
const truncatedPreview = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.';
510+
const fullMessage =
511+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.'
512+
'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.'
513+
'signature-padding-to-make-this-longer-than-vm-preview';
514+
515+
final eventJson = <String, dynamic>{
516+
'type': 'Event',
517+
'kind': 'Logging',
518+
'logRecord': {
519+
'type': 'LogRecord',
520+
'level': 800,
521+
'message': {
522+
'type': '@Instance',
523+
'kind': 'String',
524+
'valueAsString': truncatedPreview,
525+
'valueAsStringIsTruncated': true,
526+
'length': fullMessage.length,
527+
},
528+
'error': {'type': '@Instance', 'kind': 'Null'},
529+
'stackTrace': {'type': '@Instance', 'kind': 'Null'},
530+
},
531+
};
532+
533+
final details = computeDeveloperLogDetailsJson(
534+
eventJson,
535+
fullMessage: fullMessage,
536+
);
537+
final log = LogData('log', details, 1);
538+
await log.compute();
539+
540+
final decoded = jsonDecode(log.details!) as Map<String, dynamic>;
541+
expect(decoded['kind'], 'Logging');
542+
expect(decoded['logRecord'], isA<Map>());
543+
544+
final message =
545+
(decoded['logRecord'] as Map)['message'] as Map<String, dynamic>;
546+
expect(message['valueAsString'], fullMessage);
547+
expect(message['valueAsStringIsTruncated'], false);
548+
expect(message['length'], fullMessage.length);
549+
expect(jsonDecode(log.encodedDetails), isA<Map>());
550+
});
551+
552+
test('computeDeveloperLogDetailsJson updates error and stackTrace', () {
553+
final eventJson = <String, dynamic>{
554+
'type': 'Event',
555+
'kind': 'Logging',
556+
'logRecord': {
557+
'message': {
558+
'valueAsString': 'short',
559+
'valueAsStringIsTruncated': false,
560+
'length': 5,
561+
},
562+
'error': {
563+
'valueAsString': 'err...',
564+
'valueAsStringIsTruncated': true,
565+
'length': 20,
566+
},
567+
'stackTrace': {
568+
'valueAsString': '#0 foo',
569+
'valueAsStringIsTruncated': false,
570+
'length': 6,
571+
},
572+
},
573+
};
574+
575+
final details = computeDeveloperLogDetailsJson(
576+
eventJson,
577+
fullMessage: 'short',
578+
fullError: 'error-full-message',
579+
fullStackTrace: '#0 foo\n#1 bar',
580+
);
581+
final decoded = jsonDecode(details) as Map<String, dynamic>;
582+
final logRecord = decoded['logRecord'] as Map<String, dynamic>;
583+
584+
expect(
585+
(logRecord['error'] as Map)['valueAsString'],
586+
'error-full-message',
587+
);
588+
expect((logRecord['error'] as Map)['valueAsStringIsTruncated'], false);
589+
expect(
590+
(logRecord['stackTrace'] as Map)['valueAsString'],
591+
'#0 foo\n#1 bar',
592+
);
593+
});
507594
});
508595

509596
group('StdoutEventHandler', () {

0 commit comments

Comments
 (0)