1+ import 'dart:async' ;
2+ import 'dart:collection' ;
13import 'package:flutter/foundation.dart' ;
24import 'package:logging/logging.dart' ;
35import '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