Skip to content

Commit

Permalink
Include log metadata in logs table. (#8419)
Browse files Browse the repository at this point in the history
  • Loading branch information
kenzieschmoll authored Oct 9, 2024
1 parent bcf5cb1 commit 9e59cbb
Show file tree
Hide file tree
Showing 17 changed files with 414 additions and 214 deletions.
68 changes: 0 additions & 68 deletions packages/devtools_app/lib/src/screens/logging/_kind_column.dart

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class _LogDetailsState extends State<LogDetails>
child: Padding(
padding: const EdgeInsets.all(denseSpacing),
child: Scrollbar(
controller: scrollController,
child: SingleChildScrollView(
controller: scrollController,
child: SelectableText(
Expand Down
24 changes: 13 additions & 11 deletions packages/devtools_app/lib/src/screens/logging/_logs_table.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,52 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:devtools_app_shared/ui.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

import '../../shared/primitives/utils.dart';
import '../../shared/table/table.dart';
import '_kind_column.dart';
import '_message_column.dart';
import '_when_column.dart';
import 'logging_controller.dart';

class LogsTable extends StatelessWidget {
const LogsTable({
super.key,
required this.controller,
required this.data,
required this.selectionNotifier,
required this.searchMatchesNotifier,
required this.activeSearchMatchNotifier,
});

static final _logRowHeight = scaleByFontFactor(44.0);

final LoggingController controller;
final List<LogData> data;
final ValueNotifier<LogData?> selectionNotifier;
final ValueListenable<List<LogData>> searchMatchesNotifier;
final ValueListenable<LogData?> activeSearchMatchNotifier;

static final when = WhenColumn();
static final kind = KindColumn();
static final message = MessageColumn();
static final columns = [when, kind, message];
static final whenColumn = WhenColumn();
static final messageColumn = MessageColumn();
static final columns = [whenColumn, messageColumn];

@override
Widget build(BuildContext context) {
// TODO(kenz): use SearchableFlatTable instead.
return FlatTable<LogData>(
return SearchableFlatTable<LogData>(
searchController: controller,
keyFactory: (LogData data) => ValueKey<LogData>(data),
data: data,
dataKey: 'logs',
autoScrollContent: true,
searchMatchesNotifier: searchMatchesNotifier,
activeSearchMatchNotifier: activeSearchMatchNotifier,
columns: columns,
selectionNotifier: selectionNotifier,
defaultSortColumn: when,
defaultSortColumn: whenColumn,
defaultSortDirection: SortDirection.ascending,
secondarySortColumn: message,
secondarySortColumn: messageColumn,
rowHeight: _logRowHeight,
);
}
}
121 changes: 56 additions & 65 deletions packages/devtools_app/lib/src/screens/logging/_message_column.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,18 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:convert';

import 'package:devtools_app_shared/service.dart' show FlutterEvent;
import 'package:devtools_app_shared/ui.dart';
import 'package:flutter/material.dart';

import '../../shared/primitives/utils.dart';
import '../../shared/table/table.dart';
import '../../shared/table/table_data.dart';
import 'logging_controller.dart';
import 'metadata.dart';

@visibleForTesting
class MessageColumn extends ColumnData<LogData>
implements ColumnRenderer<LogData> {
MessageColumn() : super.wide('Message');
MessageColumn() : super.wide('Log');

@override
bool get supportsSorting => false;
Expand Down Expand Up @@ -59,67 +56,61 @@ class MessageColumn extends ColumnData<LogData>
// initial build.
bool hasDetails() => !data.details.isNullOrEmpty;

if (data.kind.caseInsensitiveEquals(FlutterEvent.frame)) {
const color = Color.fromARGB(0xff, 0x00, 0x91, 0xea);
final text = Text(
getDisplayValue(data),
overflow: TextOverflow.ellipsis,
);

double frameLength = 0.0;
try {
final int micros = (jsonDecode(data.details!) as Map)['elapsed'];
frameLength = micros * 3.0 / 1000.0;
} catch (e) {
// ignore
}

return Row(
children: <Widget>[
text,
Flexible(
child: Container(
height: 12.0,
width: frameLength,
decoration: const BoxDecoration(color: color),
return ValueListenableBuilder<bool>(
valueListenable: data.detailsComputed,
builder: (context, detailsComputed, _) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: borderPadding),
child: RichText(
text: TextSpan(
children: [
if (hasSummary)
...processAnsiTerminalCodes(
// TODO(helin24): Recompute summary length considering ansi codes.
// The current summary is generally the first 200 chars of details.
data.summary!,
theme.regularTextStyle,
),
if (hasSummary && hasDetails())
WidgetSpan(
child: Icon(
Icons.arrow_right,
size: defaultIconSize,
color: theme.colorScheme.onSurface,
),
),
if (hasDetails())
...processAnsiTerminalCodes(
detailsComputed ? data.details! : '<fetching>',
theme.subtleTextStyle,
),
],
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
),
],
);
} else {
return ValueListenableBuilder<bool>(
valueListenable: data.detailsComputed,
builder: (context, detailsComputed, _) {
return RichText(
text: TextSpan(
children: [
if (hasSummary)
...processAnsiTerminalCodes(
// TODO(helin24): Recompute summary length considering ansi codes.
// The current summary is generally the first 200 chars of details.
data.summary!,
theme.regularTextStyle,
),
if (hasSummary && hasDetails())
WidgetSpan(
child: Icon(
Icons.arrow_right,
size: defaultIconSize,
color: theme.colorScheme.onSurface,
),
),
if (hasDetails())
...processAnsiTerminalCodes(
detailsComputed ? data.details! : '<fetching>',
theme.subtleTextStyle,
),
],
Padding(
padding: const EdgeInsets.only(
top: borderPadding,
bottom: densePadding,
),
child: LayoutBuilder(
builder: (context, constraints) {
return MetadataChips(
data: data,
maxWidth: constraints.maxWidth,
);
},
),
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
);
},
);
}
],
);
},
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class WhenColumn extends ColumnData<LogData> {
WhenColumn()
: super(
'When',
fixedWidthPx: scaleByFontFactor(100),
fixedWidthPx: scaleByFontFactor(80),
);

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,8 @@ class LoggingController extends DisposableController
jsonEncode(e.extensionData!.data),
e.timestamp,
summary: summary.toDiagnosticsNode().toString(),
level: Level.SEVERE.value,
isError: true,
),
);
} else {
Expand Down Expand Up @@ -445,6 +447,7 @@ class LoggingController extends DisposableController
loggerName,
details,
e.timestamp,
level: level,
isError: isError,
summary: summary,
detailsComputer: detailsComputer,
Expand Down Expand Up @@ -626,12 +629,16 @@ class LoggingController extends DisposableController
}

extension type _LogRecord(Map<String, dynamic> json) {
int? get sequenceNumber => json['sequenceNumber'];

int? get level => json['level'];

Map<String, Object?> get loggerName => json['loggerName'];

Map<String, Object?> get message => json['message'];

Map<String, Object?> get zone => json['zone'];

Map<String, Object?> get error => json['error'];

Map<String, Object?> get stackTrace => json['stackTrace'];
Expand Down Expand Up @@ -740,10 +747,11 @@ class LogData with SearchableDataMixin {
this._details,
this.timestamp, {
this.summary,
int? level,
this.isError = false,
this.detailsComputer,
this.node,
}) {
}) : level = level ?? (isError ? Level.SEVERE.value : Level.INFO.value) {
final originalDetails = _details;
// Fetch details immediately on creation.
unawaited(
Expand All @@ -762,6 +770,7 @@ class LogData with SearchableDataMixin {
}

final String kind;
final int? level;
final int? timestamp;
final bool isError;
final String? summary;
Expand All @@ -774,7 +783,7 @@ class LogData with SearchableDataMixin {

String? get details => _details;

bool get needsComputing => !detailsComputed.value;
bool get needsComputing => !_detailsComputed.value;

ValueListenable<bool> get detailsComputed => _detailsComputed;
final _detailsComputed = ValueNotifier<bool>(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class _LoggingScreenState extends State<LoggingScreenBody>
RoundedOutlinedBorder(
clip: true,
child: LogsTable(
controller: controller,
data: controller.filteredData.value,
selectionNotifier: controller.selectedLog,
searchMatchesNotifier: controller.searchMatches,
Expand Down
Loading

0 comments on commit 9e59cbb

Please sign in to comment.