Skip to content

Commit 6c09fb8

Browse files
royorangeclaude
andcommitted
fix: optimize Logger package output handling in Console module
- Extract core message for list display (e.g., "🐛 Debug message") - Clean ANSI escape sequences from full output - Use single regex pattern for better performance - Show "Full Output" label for Logger formatted content - Preserve box-drawing characters in detail view - Skip stack traces and timestamps in list view 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 38bf26e commit 6c09fb8

File tree

2 files changed

+90
-43
lines changed

2 files changed

+90
-43
lines changed

lib/src/core/dev_logger.dart

Lines changed: 75 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,11 @@ class DevLogger {
467467
return LogLevel.info;
468468
}
469469

470+
// Compiled regex for ANSI escape sequences - reused for performance
471+
static final _ansiCodePattern = RegExp(
472+
r'(?:\x1B\[[0-9;]*m|\[(?:38;5;)?\d+(?:;\d+)*m)',
473+
);
474+
470475
/// Flush the Logger buffer as a single log entry
471476
///
472477
/// This method is critical for handling Logger package's multi-line output.
@@ -476,63 +481,94 @@ class DevLogger {
476481
/// This method:
477482
/// 1. Combines all buffered lines into a single log entry
478483
/// 2. Detects the appropriate log level from emoji/text indicators
479-
/// 3. Cleans up box-drawing characters and ANSI color codes
480-
/// 4. Triggers FAB update via MonitoringDataProvider
484+
/// 3. Extracts the core message for list display
485+
/// 4. Preserves full content in stackTrace for detail view
486+
/// 5. Triggers FAB update via MonitoringDataProvider
481487
///
482488
/// IMPORTANT: Must call MonitoringDataProvider.instance.triggerUpdate()
483489
/// after adding the log, otherwise FAB won't update for Logger package logs.
484490
void _flushLoggerBuffer() {
485491
if (_loggerBuffer.isEmpty) return;
486492

487-
// Combine all lines
488-
final combinedMessage = _loggerBuffer.join('\n');
493+
// Combine all lines for full content
494+
final fullMessage = _loggerBuffer.join('\n');
489495

490496
// Detect log level from the combined message
491-
// 使用更健壮的检测策略
492-
LogLevel detectedLevel = _detectLogLevel(combinedMessage);
493-
494-
// Clean up the message for display
495-
String cleanMessage = combinedMessage
496-
// Remove box drawing characters but keep emojis
497-
.replaceAll(RegExp(r'[┌─├│└╟╚╔╗╝═║╠┄]'), '')
498-
// Remove ANSI escape sequences (color codes) but preserve the content
499-
.replaceAll(RegExp(r'\x1B\[[0-9;]*m'), '')
500-
.replaceAll(RegExp(r'\[38;5;\d+m'), '')
501-
.replaceAll(RegExp(r'\[\d+m'), '')
502-
.replaceAll('[0m', '')
503-
.split('\n')
504-
.map((line) => line.trim())
505-
.where((line) => line.isNotEmpty)
506-
.join('\n');
507-
508-
// Extract main message and stack trace if present
509-
String? mainMessage;
510-
String? stackTrace;
511-
512-
final lines = cleanMessage.split('\n');
513-
if (lines.isNotEmpty) {
514-
// First line is usually the main message
515-
mainMessage = lines[0];
497+
LogLevel detectedLevel = _detectLogLevel(fullMessage);
498+
499+
// Extract the core message for list display
500+
String? coreMessage;
501+
502+
// Find the line with emoji or actual log content (not stack trace or timestamps)
503+
for (final line in _loggerBuffer) {
504+
// Remove box drawing characters and ANSI codes for checking
505+
final cleanLine = line
506+
.replaceAll(RegExp(r'[┌─├│└╟╚╔╗╝═║╠┄]'), '')
507+
.replaceAll(_ansiCodePattern, '')
508+
.trim();
516509

517-
// If there are more lines and they look like stack trace, treat them as such
518-
if (lines.length > 1) {
519-
final stackLines = lines.sublist(1);
520-
if (stackLines.any((line) => line.contains('#') || line.contains('package:'))) {
521-
stackTrace = stackLines.join('\n');
522-
} else {
523-
// Not a stack trace, include in main message
524-
mainMessage = cleanMessage;
510+
if (cleanLine.isEmpty) continue;
511+
512+
// Skip stack trace lines
513+
if (cleanLine.startsWith('#') ||
514+
(cleanLine.contains('.dart:') && !cleanLine.contains(RegExp(r'[\u{1F300}-\u{1F9FF}]', unicode: true))) ||
515+
(cleanLine.contains('package:') && !cleanLine.contains(RegExp(r'[\u{1F300}-\u{1F9FF}]', unicode: true)))) {
516+
continue;
517+
}
518+
519+
// Skip timestamp lines (e.g., "16:49:04.562 (+0:00:01.083551)")
520+
if (RegExp(r'^\d{2}:\d{2}:\d{2}\.\d+').hasMatch(cleanLine)) {
521+
continue;
522+
}
523+
524+
// This line likely contains the actual log message
525+
// Logger package messages often have emojis like 🐛, 💡, ⛔, etc.
526+
if (cleanLine.contains(RegExp(r'[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]', unicode: true)) ||
527+
(!cleanLine.startsWith('#') && cleanLine.length > 2)) {
528+
coreMessage = cleanLine;
529+
break;
530+
}
531+
}
532+
533+
// If no core message found, use the first non-empty, non-decoration line
534+
if (coreMessage == null || coreMessage.isEmpty) {
535+
for (final line in _loggerBuffer) {
536+
final cleanLine = line
537+
.replaceAll(RegExp(r'[┌─├│└╟╚╔╗╝═║╠┄]'), '')
538+
.replaceAll(_ansiCodePattern, '')
539+
.trim();
540+
if (cleanLine.isNotEmpty && !cleanLine.startsWith('#')) {
541+
coreMessage = cleanLine;
542+
break;
525543
}
526544
}
527545
}
528546

547+
// Fallback to cleaned full message if still no core message
548+
if (coreMessage == null || coreMessage.isEmpty) {
549+
coreMessage = fullMessage
550+
.replaceAll(RegExp(r'[┌─├│└╟╚╔╗╝═║╠┄]'), '')
551+
.replaceAll(_ansiCodePattern, '')
552+
.split('\n')
553+
.map((line) => line.trim())
554+
.firstWhere((line) => line.isNotEmpty, orElse: () => 'Logger output');
555+
}
556+
557+
// Clean ANSI codes from full message while preserving box drawing characters
558+
// Use a single comprehensive regex for better performance
559+
final cleanedFullMessage = fullMessage.replaceAll(
560+
RegExp(r'(?:\x1B\[[0-9;]*m|\[(?:38;5;)?\d+(?:;\d+)*m)'),
561+
'',
562+
);
563+
529564
// Create a single log entry
565+
// Use core message for display in list, cleaned full content in stackTrace for detail view
530566
final entry = LogEntry(
531567
timestamp: _loggerBufferStartTime ?? DateTime.now(),
532568
level: detectedLevel,
533-
message: mainMessage ?? cleanMessage,
569+
message: coreMessage,
534570
error: null,
535-
stackTrace: stackTrace,
571+
stackTrace: cleanedFullMessage, // Store cleaned formatted output for detail view
536572
);
537573

538574
_logs.addLast(entry);

packages/flutter_dev_panel_console/lib/src/ui/widgets/log_item.dart

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -293,14 +293,14 @@ class LogDetailSheet extends StatelessWidget {
293293
),
294294
],
295295

296-
// 堆栈跟踪
296+
// 堆栈跟踪或 Logger 格式化输出
297297
if (log.stackTrace != null) ...[
298298
const SizedBox(height: 16),
299299
_buildSection(
300300
context,
301-
title: 'StackTrace',
301+
title: _isLoggerFormattedOutput(log.stackTrace!) ? 'Full Output' : 'StackTrace',
302302
content: log.stackTrace!,
303-
icon: Icons.layers,
303+
icon: _isLoggerFormattedOutput(log.stackTrace!) ? Icons.format_align_left : Icons.layers,
304304
isMonospace: true,
305305
),
306306
],
@@ -356,15 +356,26 @@ class LogDetailSheet extends StatelessWidget {
356356
child: SelectableText(
357357
content,
358358
style: theme.textTheme.bodyMedium?.copyWith(
359-
fontFamily: isMonospace ? 'monospace' : null,
359+
fontFamily: isMonospace ? 'Courier New, monospace' : null,
360360
color: isError ? Colors.red : null,
361+
fontSize: isMonospace ? 13 : null,
362+
height: isMonospace ? 1.4 : null,
361363
),
362364
),
363365
),
364366
],
365367
);
366368
}
367369

370+
/// Check if the content is Logger package formatted output
371+
bool _isLoggerFormattedOutput(String content) {
372+
return content.contains('┌') ||
373+
content.contains('├') ||
374+
content.contains('│') ||
375+
content.contains('└') ||
376+
content.contains('┄');
377+
}
378+
368379
void _copyToClipboard(BuildContext context) {
369380
final text = StringBuffer();
370381
text.writeln('[${log.levelText}] ${log.formattedTime}');

0 commit comments

Comments
 (0)