64 lines
2.2 KiB
Dart
64 lines
2.2 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
|
|
/// Severity of a captured log line, used only to tint it in the Debug tab.
|
|
enum LogLevel { info, error }
|
|
|
|
/// A single captured console line, stamped with the wall-clock time it arrived.
|
|
@immutable
|
|
class LogEntry {
|
|
const LogEntry(this.time, this.text, this.level);
|
|
|
|
final DateTime time;
|
|
final String text;
|
|
final LogLevel level;
|
|
|
|
/// `HH:MM:SS.mmm` — enough resolution to correlate bursts while streaming.
|
|
String get timeLabel {
|
|
String two(int n) => n.toString().padLeft(2, '0');
|
|
return '${two(time.hour)}:${two(time.minute)}:${two(time.second)}'
|
|
'.${time.millisecond.toString().padLeft(3, '0')}';
|
|
}
|
|
}
|
|
|
|
/// In-memory ring buffer of console/system output, surfaced in the Debug tab so
|
|
/// the app's logs can be read (and copied) on-device during beta testing —
|
|
/// there's no attached debugger on a TestFlight/sideloaded build.
|
|
///
|
|
/// A process-wide singleton because the capture hooks (the zone `print`
|
|
/// override and `FlutterError.onError`) are installed in `main()`, outside the
|
|
/// widget/provider tree. The UI listens via [ListenableBuilder].
|
|
class LogStore extends ChangeNotifier {
|
|
LogStore._();
|
|
static final LogStore instance = LogStore._();
|
|
|
|
/// Keep the tail bounded so a chatty session can't grow memory without limit.
|
|
static const int _maxEntries = 3000;
|
|
|
|
final List<LogEntry> _entries = <LogEntry>[];
|
|
|
|
/// Newest-last, read-only view for the UI.
|
|
List<LogEntry> get entries => List.unmodifiable(_entries);
|
|
|
|
int get length => _entries.length;
|
|
|
|
void add(String text, {LogLevel level = LogLevel.info}) {
|
|
// A single print can carry embedded newlines; split so each shows as its
|
|
// own row (and the timestamp lines up per line).
|
|
final now = DateTime.now();
|
|
for (final line in text.split('\n')) {
|
|
_entries.add(LogEntry(now, line, level));
|
|
}
|
|
final overflow = _entries.length - _maxEntries;
|
|
if (overflow > 0) _entries.removeRange(0, overflow);
|
|
notifyListeners();
|
|
}
|
|
|
|
void clear() {
|
|
_entries.clear();
|
|
notifyListeners();
|
|
}
|
|
|
|
/// The whole buffer as plain text, for copy-to-clipboard / sharing.
|
|
String asText() =>
|
|
_entries.map((e) => '${e.timeLabel} ${e.text}').join('\n');
|
|
}
|