Skip to content

Commit a0c2369

Browse files
Add a filter setting to filter by a minimum log level (#8433)
1 parent f41c22f commit a0c2369

File tree

5 files changed

+105
-24
lines changed

5 files changed

+105
-24
lines changed

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,17 @@ class LoggingController extends DisposableController
122122

123123
@visibleForTesting
124124
static final settingFilters = <SettingFilter<LogData, Object>>[
125+
SettingFilter<LogData, Level>(
126+
name: 'Hide logs below the minimum log level',
127+
includeCallback: (LogData element, Level currentFilterValue) =>
128+
element.level >= currentFilterValue.value,
129+
enabledCallback: (Level filterValue) => filterValue >= Level.FINEST,
130+
possibleValues: Level.LEVELS
131+
// Omit Level.OFF and Level.ALL from the possible minimum levels.
132+
.where((level) => level != Level.OFF && level != Level.ALL)
133+
.toList(),
134+
defaultValue: Level.INFO,
135+
),
125136
if (serviceConnection.serviceManager.connectedApp?.isFlutterAppNow ??
126137
true) ...[
127138
ToggleFilter<LogData>(

packages/devtools_app/lib/src/shared/ui/filter.dart

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -221,10 +221,9 @@ class _FilterDialogState<T> extends State<FilterDialog<T>>
221221
],
222222
for (final filter in widget.controller._settingFilters) ...[
223223
if (filter is ToggleFilter<T>)
224-
ToggleFilterElement(filter: filter)
224+
_ToggleFilterElement(filter: filter)
225225
else
226-
// TODO(kenz): add a SettingFilterElement widget.
227-
const SizedBox.shrink(),
226+
_SettingFilterElement(filter: filter),
228227
],
229228
],
230229
),
@@ -255,8 +254,8 @@ class _FilterDialogState<T> extends State<FilterDialog<T>>
255254
}
256255
}
257256

258-
class ToggleFilterElement extends StatelessWidget {
259-
const ToggleFilterElement({super.key, required this.filter});
257+
class _ToggleFilterElement extends StatelessWidget {
258+
const _ToggleFilterElement({required this.filter});
260259

261260
final ToggleFilter filter;
262261

@@ -281,6 +280,53 @@ class ToggleFilterElement extends StatelessWidget {
281280
}
282281
}
283282

283+
class _SettingFilterElement extends StatelessWidget {
284+
const _SettingFilterElement({required this.filter});
285+
286+
final SettingFilter filter;
287+
288+
static const _leadingInset = 6.0;
289+
290+
@override
291+
Widget build(BuildContext context) {
292+
Widget content = Padding(
293+
// This padding is required to left-align [_SettingFilterElement]s with
294+
// [_ToggleFilterElement] checkboxes in the dialog.
295+
padding: const EdgeInsets.only(left: _leadingInset),
296+
child: Row(
297+
children: [
298+
Text(filter.name),
299+
const BulletSpacer(),
300+
ValueListenableBuilder(
301+
valueListenable: filter.setting,
302+
builder: (context, value, _) {
303+
return RoundedDropDownButton(
304+
value: value,
305+
items: filter.possibleValues
306+
.map(
307+
(value) => DropdownMenuItem(
308+
value: value,
309+
child: Text('$value'),
310+
),
311+
)
312+
.toList(),
313+
onChanged: (value) => filter.setting.value = value!,
314+
);
315+
},
316+
),
317+
],
318+
),
319+
);
320+
if (filter.tooltip != null) {
321+
content = DevToolsTooltip(
322+
message: filter.tooltip,
323+
child: content,
324+
);
325+
}
326+
return content;
327+
}
328+
}
329+
284330
class Filter<T> {
285331
Filter({required this.queryFilter, required this.settingFilters});
286332

packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ severity. [#8419](https://github.com/flutter/devtools/pull/8419)
4949
[#8427](https://github.com/flutter/devtools/pull/8427)
5050
![Logging filter](images/log_filter.png "Logging filter")
5151

52+
* Added support for filtering by log severity / levels. - []()
53+
![Log level filter](images/log_level_filter.png "Log level filter")
54+
5255
* Fix a bug where logs would get out of order after midnight. -
5356
[#8420](https://github.com/flutter/devtools/pull/8420)
5457

Loading

packages/devtools_app/test/logging/logging_controller_test.dart

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import 'package:devtools_app_shared/utils.dart';
1414
import 'package:devtools_test/devtools_test.dart';
1515
import 'package:devtools_test/helpers.dart';
1616
import 'package:flutter_test/flutter_test.dart';
17+
import 'package:logging/logging.dart';
1718

1819
void main() {
1920
group('LoggingController', () {
@@ -27,6 +28,7 @@ void main() {
2728
jsonEncode({'kind': 'stdout', 'message': message}),
2829
0,
2930
summary: message,
31+
level: Level.INFO.value,
3032
),
3133
);
3234
}
@@ -38,12 +40,21 @@ void main() {
3840
jsonEncode({'kind': 'gc', 'message': message}),
3941
0,
4042
summary: message,
43+
level: Level.INFO.value,
4144
),
4245
);
4346
}
4447

45-
void addLogWithKind(String kind) {
46-
controller.log(LogData(kind, jsonEncode({'foo': 'test_data'}), 0));
48+
void addLog({required String kind, Level? level, bool isError = false}) {
49+
controller.log(
50+
LogData(
51+
kind,
52+
jsonEncode({'foo': 'test_data'}),
53+
0,
54+
level: level?.value,
55+
isError: isError,
56+
),
57+
);
4758
}
4859

4960
setUp(() {
@@ -88,8 +99,8 @@ void main() {
8899
addStdoutData('abc');
89100
addStdoutData('def');
90101
addStdoutData('abc ghi');
91-
addLogWithKind('Flutter.Navigation');
92-
addLogWithKind('Flutter.Error');
102+
addLog(kind: 'Flutter.Navigation');
103+
addLog(kind: 'Flutter.Error', isError: true);
93104
addGcData('gc1');
94105
addGcData('gc2');
95106

@@ -112,8 +123,8 @@ void main() {
112123
addStdoutData('abc');
113124
addStdoutData('def');
114125
addStdoutData('abc ghi');
115-
addLogWithKind('Flutter.Navigation');
116-
addLogWithKind('Flutter.Error');
126+
addLog(kind: 'Flutter.Navigation');
127+
addLog(kind: 'Flutter.Error', isError: true);
117128
addGcData('gc1');
118129
addGcData('gc2');
119130

@@ -133,24 +144,27 @@ void main() {
133144
addStdoutData('abc');
134145
addStdoutData('def');
135146
addStdoutData('abc ghi');
136-
addLogWithKind('Flutter.Navigation');
137-
addLogWithKind('Flutter.Error');
147+
addLog(kind: 'Flutter.Navigation');
148+
addLog(kind: 'Flutter.Error', isError: true);
138149

139150
// The following logs should all be filtered by default.
140151
addGcData('gc1');
141152
addGcData('gc2');
142-
addLogWithKind('Flutter.FirstFrame');
143-
addLogWithKind('Flutter.FrameworkInitialization');
144-
addLogWithKind('Flutter.Frame');
145-
addLogWithKind('Flutter.ImageSizesForFrame');
146-
addLogWithKind('Flutter.ServiceExtensionStateChanged');
153+
addLog(kind: 'Flutter.FirstFrame');
154+
addLog(kind: 'Flutter.FrameworkInitialization');
155+
addLog(kind: 'Flutter.Frame');
156+
addLog(kind: 'Flutter.ImageSizesForFrame');
157+
addLog(kind: 'Flutter.ServiceExtensionStateChanged');
147158

148159
// At this point data is filtered by the default toggle filter values.
149160
expect(controller.data, hasLength(12));
150161
expect(controller.filteredData.value, hasLength(5));
151162

152-
// Test query filters assuming default toggle filters are all enabled.
153-
for (final filter in controller.activeFilter.value.settingFilters) {
163+
// Test query filters assuming default setting filters are all enabled.
164+
controller.activeFilter.value.settingFilters.first.setting.value =
165+
Level.INFO;
166+
for (final filter
167+
in controller.activeFilter.value.settingFilters.sublist(1)) {
154168
filter.setting.value = true;
155169
}
156170

@@ -186,12 +200,14 @@ void main() {
186200
expect(controller.data, hasLength(12));
187201
expect(controller.filteredData.value, hasLength(5));
188202

189-
// Test toggle filters.
190-
final verboseFlutterFrameworkFilter =
203+
// Test setting filters.
204+
final minimumLogLevelFilter =
191205
controller.activeFilter.value.settingFilters[0];
192-
final verboseFlutterServiceFilter =
206+
final verboseFlutterFrameworkFilter =
193207
controller.activeFilter.value.settingFilters[1];
194-
final gcFilter = controller.activeFilter.value.settingFilters[2];
208+
final verboseFlutterServiceFilter =
209+
controller.activeFilter.value.settingFilters[2];
210+
final gcFilter = controller.activeFilter.value.settingFilters[3];
195211

196212
verboseFlutterFrameworkFilter.setting.value = false;
197213
controller.setActiveFilter();
@@ -207,6 +223,11 @@ void main() {
207223
controller.setActiveFilter();
208224
expect(controller.data, hasLength(12));
209225
expect(controller.filteredData.value, hasLength(12));
226+
227+
minimumLogLevelFilter.setting.value = Level.SEVERE;
228+
controller.setActiveFilter();
229+
expect(controller.data, hasLength(12));
230+
expect(controller.filteredData.value, hasLength(1));
210231
});
211232
});
212233

0 commit comments

Comments
 (0)