Last active
June 20, 2024 03:07
-
-
Save granoeste/aaa268771e7cd78b34008529ef99d47b to your computer and use it in GitHub Desktop.
Pretty Log Printer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:convert'; | |
import 'package:logger/logger.dart'; | |
class PrettyLogPrinter extends LogPrinter { | |
static final levelColors = { | |
Level.verbose: AnsiColor.fg(AnsiColor.grey(0.5)), | |
Level.debug: AnsiColor.none(), | |
Level.info: AnsiColor.fg(12), | |
Level.warning: AnsiColor.fg(208), | |
Level.error: AnsiColor.fg(196), | |
Level.wtf: AnsiColor.fg(199), | |
}; | |
/// Matches a stacktrace line as generated on Android/iOS devices. | |
/// For example: | |
/// #1 Logger.log (package:logger/src/logger.dart:115:29) | |
static final _deviceStackTraceRegex = | |
RegExp(r'#[0-9]+[\s]+(.+) \(([^\s]+)\)'); | |
/// Matches a stacktrace line as generated by Flutter web. | |
/// For example: | |
/// packages/logger/src/printers/pretty_printer.dart 91:37 | |
static final _webStackTraceRegex = | |
RegExp(r'^((packages|dart-sdk)\/[^\s]+\/)'); | |
/// Matches a stacktrace line as generated by browser Dart. | |
/// For example: | |
/// dart:sdk_internal | |
/// package:logger/src/logger.dart | |
static final _browserStackTraceRegex = | |
RegExp(r'^(?:package:)?(dart:[^\s]+|[^\s]+)'); | |
static final _stackTraceLineRegex = RegExp(r'^(.*) \((.*)\)'); | |
final int methodCount = 1; | |
final int errorMethodCount = 8; | |
final int lineLength; | |
final bool colors; | |
final bool printCodeLine; | |
PrettyLogPrinter({ | |
this.lineLength = 120, | |
this.colors = true, | |
this.printCodeLine = false, | |
}); | |
@override | |
List<String> log(LogEvent event) { | |
var messageStr = stringifyMessage(event.message); | |
var origin = getOrigin(); | |
String? stackTraceStr; | |
if (event.stackTrace != null) { | |
stackTraceStr = formatStackTrace(event.stackTrace!, errorMethodCount); | |
} | |
var errorStr = event.error?.toString(); | |
return _formatAndPrint( | |
event.level, | |
messageStr, | |
getTime(), | |
origin, | |
errorStr, | |
stackTraceStr, | |
); | |
} | |
String getOrigin() { | |
final lines = StackTrace.current.toString().split('\n'); | |
var source = StringBuffer(); | |
var count = 0; | |
for (var line in lines) { | |
if (_discardDeviceStacktraceLine(line) || | |
_discardWebStacktraceLine(line) || | |
_discardBrowserStacktraceLine(line)) { | |
continue; | |
} | |
source.write(line.replaceFirst(RegExp(r'#\d+\s+'), '')); | |
if (++count == 1) { | |
break; | |
} | |
} | |
return source.toString(); | |
} | |
String? formatStackTrace(StackTrace stackTrace, int methodCount) { | |
var lines = stackTrace.toString().split('\n'); | |
var formatted = <String>[]; | |
var count = 0; | |
for (var line in lines) { | |
if (_discardDeviceStacktraceLine(line) || | |
_discardWebStacktraceLine(line) || | |
_discardBrowserStacktraceLine(line)) { | |
continue; | |
} | |
formatted.add('#$count ${line.replaceFirst(RegExp(r'#\d+\s+'), '')}'); | |
if (++count == methodCount) { | |
break; | |
} | |
} | |
if (formatted.isEmpty) { | |
return null; | |
} else { | |
return formatted.join('\n'); | |
} | |
} | |
bool _discardDeviceStacktraceLine(String line) { | |
var match = _deviceStackTraceRegex.matchAsPrefix(line); | |
if (match == null) { | |
return false; | |
} | |
return match.group(2)?.startsWith('package:logger') == true || | |
match.group(2)?.startsWith('package:common/pretty_log_printer.dart') == | |
true; | |
} | |
bool _discardWebStacktraceLine(String line) { | |
var match = _webStackTraceRegex.matchAsPrefix(line); | |
if (match == null) { | |
return false; | |
} | |
return match.group(1)?.startsWith('packages/logger') == true || | |
match.group(1)?.startsWith('package/common/pretty_log_printer.dart') == | |
true || | |
match.group(1)?.startsWith('dart-sdk/lib') == true; | |
} | |
bool _discardBrowserStacktraceLine(String line) { | |
var match = _browserStackTraceRegex.matchAsPrefix(line); | |
if (match == null) { | |
return false; | |
} | |
return match.group(1)?.startsWith('package:logger') == true || | |
match.group(1)?.startsWith('package:common/pretty_log_printer.dart') == | |
true || | |
match.group(1)?.startsWith('dart:') == true; | |
} | |
String _method(String line) { | |
var match = _stackTraceLineRegex.matchAsPrefix(line); | |
return match?.group(1) ?? ''; | |
} | |
String _codeLine(String line) { | |
var match = _stackTraceLineRegex.matchAsPrefix(line); | |
return match?.group(2) ?? ''; | |
} | |
String getTime() { | |
String _threeDigits(int n) { | |
if (n >= 100) return '$n'; | |
if (n >= 10) return '0$n'; | |
return '00$n'; | |
} | |
String _twoDigits(int n) { | |
if (n >= 10) return '$n'; | |
return '0$n'; | |
} | |
var now = DateTime.now(); | |
var h = _twoDigits(now.hour); | |
var min = _twoDigits(now.minute); | |
var sec = _twoDigits(now.second); | |
var ms = _threeDigits(now.millisecond); | |
return '$h:$min:$sec.$ms'; | |
} | |
String stringifyMessage(dynamic message) { | |
if (message is Map || message is Iterable) { | |
var encoder = const JsonEncoder.withIndent(' '); | |
return encoder.convert(message); | |
} else { | |
return message.toString(); | |
} | |
} | |
AnsiColor _getLevelColor(Level level) { | |
if (colors) { | |
return levelColors[level] ?? AnsiColor.none(); | |
} else { | |
return AnsiColor.none(); | |
} | |
} | |
AnsiColor _getErrorColor(Level level) { | |
if (colors) { | |
if (level == Level.wtf) { | |
return (levelColors[Level.wtf] ?? AnsiColor.none()).toBg(); | |
} else { | |
return (levelColors[Level.error] ?? AnsiColor.none()).toBg(); | |
} | |
} else { | |
return AnsiColor.none(); | |
} | |
} | |
List<String> _formatAndPrint( | |
Level level, | |
String message, | |
String time, | |
String origin, | |
String? error, | |
String? stacktrace, | |
) { | |
// This code is non trivial and a type annotation here helps understanding. | |
// ignore: omit_local_variable_types | |
List<String> buffer = []; | |
var color = _getLevelColor(level); | |
buffer.add(color( | |
'$time ${_method(origin)} $message ${printCodeLine ? _codeLine(origin) : ''}')); | |
if (error != null) { | |
var errorColor = _getErrorColor(level); | |
for (var line in error.split('\n')) { | |
buffer.add( | |
'$time ${errorColor.resetForeground}${errorColor(line)}${errorColor.resetBackground}'); | |
} | |
} | |
if (stacktrace != null) { | |
for (var line in stacktrace.split('\n')) { | |
buffer.add('$time $color $line'); | |
} | |
} | |
return buffer; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment