Skip to content

Commit cd8c031

Browse files
fix: enhance logging with comprehensive levels and revision tracking (#178)
* fix: set logger level to ALL for comprehensive logging * fix: implement revision tracking in LogCapture for improved log updates * fix: optimize log management and improve performance in DebugViewer * fix: optimize log storage and retrieval in LogCapture for improved performance * test: add comprehensive unit tests for LogEntry and LogCapture functionality * fix: implement event-driven log updates in LogCapture and DebugViewer
1 parent 6af500b commit cd8c031

3 files changed

Lines changed: 632 additions & 122 deletions

File tree

lib/utils/logger.dart

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:async';
2+
import 'dart:collection';
13
import 'package:flutter/foundation.dart';
24
import 'package:logging/logging.dart';
35
import 'package:desktop_multi_window/desktop_multi_window.dart';
@@ -58,34 +60,49 @@ class LogCapture {
5860
static final LogCapture _instance = LogCapture._internal();
5961
factory LogCapture() => _instance;
6062

61-
final List<LogEntry> _logs = [];
62-
final List<LogEntry> _receivedLogs =
63-
[]; // For logs received from other windows
63+
final ListQueue<LogEntry> _logs = ListQueue<LogEntry>();
64+
final ListQueue<LogEntry> _receivedLogs =
65+
ListQueue<LogEntry>(); // For logs received from other windows
6466
static const int _maxLogs =
6567
1000; // Keep last 1000 combined logs across both buffers
6668

6769
// Cache for combined logs
6870
List<LogEntry>? _cachedCombinedLogs;
6971
bool _isCacheValid = false;
7072

73+
// Revision counter to track changes even when log count stays the same
74+
int _revision = 0;
75+
76+
// Stream controller for event-driven log updates
77+
final StreamController<int> _revisionController =
78+
StreamController<int>.broadcast();
79+
80+
/// Stream of revision updates for event-driven log monitoring
81+
Stream<int> get revisionStream => _revisionController.stream;
82+
7183
LogCapture._internal() {
72-
Logger.root.level = kDebugMode ? Level.ALL : Level.INFO;
84+
Logger.root.level = Level.ALL;
7385
Logger.root.onRecord.listen(_handleLogRecord);
7486
}
7587

7688
void _trimLogsToLimit() {
77-
while (_logs.length + _receivedLogs.length > _maxLogs) {
78-
// Remove the oldest entry from either buffer
89+
final totalLogs = _logs.length + _receivedLogs.length;
90+
if (totalLogs <= _maxLogs) return;
91+
92+
final toRemove = totalLogs - _maxLogs;
93+
94+
// Remove oldest logs efficiently (O(1) per removal with ListQueue)
95+
for (var i = 0; i < toRemove; i++) {
7996
if (_logs.isEmpty) {
80-
_receivedLogs.removeAt(0);
97+
_receivedLogs.removeFirst();
8198
} else if (_receivedLogs.isEmpty) {
82-
_logs.removeAt(0);
99+
_logs.removeFirst();
83100
} else {
84-
// Both have entries, remove from the one with the older timestamp
101+
// Remove from the buffer with the older timestamp
85102
if (_logs.first.timestamp.isBefore(_receivedLogs.first.timestamp)) {
86-
_logs.removeAt(0);
103+
_logs.removeFirst();
87104
} else {
88-
_receivedLogs.removeAt(0);
105+
_receivedLogs.removeFirst();
89106
}
90107
}
91108
}
@@ -104,6 +121,8 @@ class LogCapture {
104121
_logs.add(entry);
105122
_trimLogsToLimit();
106123
_isCacheValid = false;
124+
_revision++;
125+
_revisionController.add(_revision);
107126

108127
// Print to console
109128
if (kDebugMode) {
@@ -155,15 +174,21 @@ class LogCapture {
155174
orElse: () => Level.INFO,
156175
);
157176

158-
// Check if this log already exists in _logs or _receivedLogs to avoid duplicates
159-
// This happens when a window broadcasts to all windows including itself
160-
// or when the same remote log is received multiple times
161-
final isDuplicate = _logs.any((log) =>
177+
// Check recent logs for duplicates (broadcasts typically arrive quickly)
178+
// Only check last 50 logs instead of all logs for better performance
179+
// Use skip() to avoid creating full list copies
180+
final recentLogsToCheck =
181+
_logs.length > 50 ? _logs.skip(_logs.length - 50) : _logs;
182+
final recentReceivedToCheck = _receivedLogs.length > 50
183+
? _receivedLogs.skip(_receivedLogs.length - 50)
184+
: _receivedLogs;
185+
186+
final isDuplicate = recentLogsToCheck.any((log) =>
162187
log.timestamp == parsedTimestamp &&
163188
log.loggerName == loggerName &&
164189
log.level == level &&
165190
log.message == message) ||
166-
_receivedLogs.any((log) =>
191+
recentReceivedToCheck.any((log) =>
167192
log.timestamp == parsedTimestamp &&
168193
log.loggerName == loggerName &&
169194
log.level == level &&
@@ -188,6 +213,8 @@ class LogCapture {
188213
_receivedLogs.add(entry);
189214
_trimLogsToLimit();
190215
_isCacheValid = false;
216+
_revision++;
217+
_revisionController.add(_revision);
191218
} catch (_) {
192219
// Silently ignore malformed log data
193220
}
@@ -208,10 +235,14 @@ class LogCapture {
208235

209236
int get logCount => _logs.length + _receivedLogs.length;
210237

238+
int get revision => _revision;
239+
211240
void clear() {
212241
_logs.clear();
213242
_receivedLogs.clear();
214243
_isCacheValid = false;
244+
_revision++;
245+
_revisionController.add(_revision);
215246
}
216247
}
217248

0 commit comments

Comments
 (0)