Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Pretty Log Printer
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