| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /// Some notes about filtering `adb logcat` output, especially as a result of |
| /// running `adb shell` to instrument the app and test scripts, as it's |
| /// non-trivial and error-prone. |
| /// |
| /// 1. It's probably worth keeping `ActivityManager` lines unconditionally. |
| /// They are the most important ones, and they are not too verbose (for |
| /// example, they don't typically contain stack traces). |
| /// |
| /// 2. `ActivityManager` starts with the application name and process ID: |
| /// |
| /// ```txt |
| /// [stdout] 02-15 10:20:36.914 1735 1752 I ActivityManager: Start proc 6840:dev.flutter.scenarios/u0a98 for added application dev.flutter.scenarios |
| /// ``` |
| /// |
| /// The "application" comes from the file `android/app/build.gradle` under |
| /// `android > defaultConfig > applicationId`. |
| /// |
| /// 3. Once we have the process ID, we can filter the logcat output further: |
| /// |
| /// ```txt |
| /// [stdout] 02-15 10:20:37.430 6840 6840 E GeneratedPluginsRegister: Tried to automatically register plugins with FlutterEngine (io.flutter.embedding.engine.FlutterEngine@144d737) but could not find or invoke the GeneratedPluginRegistrant. |
| /// ``` |
| /// |
| /// A sample output of `adb logcat` command lives in `./sample_adb_logcat.txt`. |
| /// |
| /// See also: <https://developer.android.com/tools/logcat>. |
| library; |
| |
| import 'package:meta/meta.dart'; |
| |
| import 'logs.dart'; |
| |
| /// Represents a line of `adb logcat` output parsed into a structured form. |
| /// |
| /// For example the line: |
| /// ```txt |
| /// 02-22 13:54:39.839 549 3683 I ActivityManager: Force stopping dev.flutter.scenarios appid=10226 user=0: start instr |
| /// ``` |
| /// |
| /// ## Implementation notes |
| /// |
| /// The reason this is an extension type and not a class is partially to use the |
| /// language feature, and partially because extension types work really well |
| /// with lazy parsing. |
| extension type const AdbLogLine._(Match _match) { |
| // RegEx that parses into the following groups: |
| // 1. The time of the log message, such as `02-22 13:54:39.839`. |
| // 2. The process ID. |
| // 3. The thread ID. |
| // 4. The character representing the severity of the log message, such as `I`. |
| // 5. The tag, such as `ActivityManager`. |
| // 6. The actual log message. |
| // |
| // This regex is simple versus being more precise. Feel free to improve it. |
| static final RegExp _pattern = RegExp(r'(\d+-\d+\s[\d|:]+\.\d+)\s+(\d+)\s+(\d+)\s(\w)\s(\S+)\s*:\s*(.*)'); |
| |
| /// Parses the given [adbLogCatLine] into a structured form. |
| /// |
| /// Returns `null` if the line does not match the expected format. |
| static AdbLogLine? tryParse(String adbLogCatLine) { |
| final Match? match = _pattern.firstMatch(adbLogCatLine); |
| return match == null ? null : AdbLogLine._(match); |
| } |
| |
| /// Tries to parse the process that was started, if the log line is about it. |
| String? tryParseProcess() { |
| if (name == activityManagerTag && message.startsWith('Start proc')) { |
| // ActivityManager: Start proc 4475:dev.flutter.scenarios/u0a190 for added application ... |
| final RegExpMatch? match = RegExp('Start proc (\\d+):$flutterProcessName').firstMatch(message); |
| return match?.group(1); |
| } |
| return null; |
| } |
| |
| @visibleForTesting |
| static const String activityManagerTag = 'ActivityManager'; |
| |
| @visibleForTesting |
| static const String flutterProcessName = 'dev.flutter.scenarios'; |
| |
| @visibleForTesting |
| static const Set<String> knownNoiseTags = <String>{ |
| 'CCodec', |
| 'CCodecBufferChannel', |
| 'CCodecConfig', |
| 'Codec2Client', |
| 'ColorUtils', |
| 'DMABUFHEAPS', |
| 'Gralloc4', |
| 'MediaCodec', |
| 'MonitoringInstr', |
| 'ResourceExtractor', |
| 'UsageTrackerFacilitator', |
| 'hw-BpHwBinder', |
| 'ziparchive', |
| }; |
| |
| @visibleForTesting |
| static const Set<String> knownUsefulTags = <String>{ |
| activityManagerTag |
| }; |
| |
| @visibleForTesting |
| static const Set<String> knownUsefulErrorTags = <String>{ |
| 'androidemu', |
| 'THREAD_STATE', |
| }; |
| |
| /// Returns `true` if the log line is verbose. |
| bool isVerbose({String? filterProcessId}) => !_isRelevant(filterProcessId: filterProcessId); |
| bool _isRelevant({String? filterProcessId}) { |
| // Fatal errors are always useful. |
| if (severity == 'F') { |
| return true; |
| } |
| |
| // Verbose and debug logs are rarely useful. |
| if (severity == 'V' || severity == 'D') { |
| return false; |
| } |
| |
| if (knownNoiseTags.contains(name)) { |
| return false; |
| } |
| |
| if (knownUsefulTags.contains(name)) { |
| return true; |
| } |
| |
| if (severity == 'E' && knownUsefulErrorTags.contains(name)) { |
| return true; |
| } |
| |
| // If a process ID is specified, exclude logs _not_ from that process. |
| if (filterProcessId == null) { |
| // YOLO, let's keep it anyway. |
| return name.toLowerCase().contains('flutter') || |
| message.toLowerCase().contains('flutter'); |
| } |
| |
| return process == filterProcessId; |
| } |
| |
| /// Logs the line to the console. |
| void printFormatted() { |
| final String formatted = '$time [$severity] $name: $message'; |
| if (severity == 'W' || severity == 'E' || severity == 'F') { |
| logWarning(formatted); |
| } else if (name == 'TestRunner') { |
| logImportant(formatted); |
| } else { |
| log(formatted); |
| } |
| } |
| |
| /// The full line of `adb logcat` output. |
| String get line => _match.group(0)!; |
| |
| /// The time of the log message, such as `02-22 13:54:39.839`. |
| String get time => _match.group(1)!; |
| |
| /// The process ID. |
| String get process => _match.group(2)!; |
| |
| /// The thread ID. |
| String get thread => _match.group(3)!; |
| |
| /// The character representing the severity of the log message, such as `I`. |
| String get severity => _match.group(4)!; |
| |
| /// The tag, such as `ActivityManager`. |
| String get name => _match.group(5)!; |
| |
| /// The actual log message. |
| String get message => _match.group(6)!; |
| |
| String toDebugString() { |
| return 'AdbLogLine(time: $time, process: $process, thread: $thread, severity: $severity, name: $name, message: $message)'; |
| } |
| } |