logger
logger copied to clipboard
Colors not working in Android Studio
^[[38;5;196m⛔ - FormatException: Unexpected end of input (at character 1) [11:10:59 AM]: <== ^
Is this on Android or iOS? It may be related to #1
I have the same issue. Could it be due to Android Studio? Maybe coloured output is not supported there and only works with Visual Studio Code.
EDIT: I worked on a custom logger myself before I found your much better work 😁. There I tried various ways out to colour the console output, but nothing worked. That's why I guess it is an issue with Android Studio.
@phpmaple @Tyxz I'm working on a solution but this could take some time. In the meantime you can try out the new on device console or disable colors:
var logger = Logger(
printer: PrettyPrinter(
colors: false,
)
Hey, this tool is absolutely excellent, but unfortunately i am using Android Studio. Will u fix the ansi-color problem in Android Studio?
@Creolophus Yes, I'm currently working on a solution.
@leisim Hi, any news?)
Congrats, awesome project 🤘
@leisim Same problem on Ubuntu Android Studio, is it related to fonts configuration? Emojis also don't work.
The terminal in IntelliJ works just fine. I guess it's an issue w/ the console (the thing that appears when you hit run).
While we wait for a fix, you can work around temporarily using the Grep Console plugin: you can configure it to search for the following expressions:
.*38;5;199m.*for WTF messages.*38;5;196m.*for ERROR messages.*38;5;208m.*for WARN messages.*38;5;12m.*for INFO messages.*38;5;244m.*for VERBOSE messages
and choose background/foreground colors of your choose. Thus, you can continue use this awesome library and still have colors in your Android Studio console.
It's a little workaround, I know, but hey, as long as it works!
That's really creative!
While we wait for a fix, you can work around temporarily using the Grep Console plugin: you can configure it to search for the following expressions:
.*38;5;199m.*for WTF messages.*38;5;196m.*for ERROR messages.*38;5;208m.*for WARN messages.*38;5;12m.*for INFO messages.*38;5;244m.*for VERBOSE messagesand choose background/foreground colors of your choose. Thus, you can continue use this awesome library and still have colors in your Android Studio console.
It's a little workaround, I know, but hey, as long as it works!
tks!
@leisim Hello, thanks for all, it's very usefull. @giovannilattanzio Thanks aswell.
It's a good solution but ANSI code's stay in console and it's not very beautifull.

So, I found a quick solution. Replace ANSI codes by strings like ERROR, FATAL, WARNING.... and add a boolean to set if we prefere to see labels or colors. And with Grep Console plugin, colors are good.

pretty_printer.dart
import 'dart:convert';
import 'package:logger/src/logger.dart';
import 'package:logger/src/log_printer.dart';
import 'package:logger/src/ansi_color.dart';
/// Default implementation of [LogPrinter].
///
/// Outut looks like this:
/// ```
/// ┌──────────────────────────
/// │ Error info
/// ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
/// │ Method stack history
/// ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
/// │ Log message
/// └──────────────────────────
/// ```
class PrettyPrinter extends LogPrinter {
static const topLeftCorner = '┌';
static const bottomLeftCorner = '└';
static const middleCorner = '├';
static const verticalLine = '│';
static const doubleDivider = "─";
static const singleDivider = "┄";
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),
};
static final levelPrefix = {
Level.verbose: AnsiColor.txt(""),
Level.debug: AnsiColor.txt( " DEBUG "),
Level.info: AnsiColor.txt( " INFO "),
Level.warning: AnsiColor.txt( "WARNING "),
Level.error: AnsiColor.txt( " ERROR "),
Level.wtf: AnsiColor.txt( " FATAL "),
};
static final levelEmojis = {
Level.verbose: '',
Level.debug: '🐛 ',
Level.info: '💡 ',
Level.warning: '⚠️ ',
Level.error: '⛔ ',
Level.wtf: '👾 ',
};
static final stackTraceRegex = RegExp(r'#[0-9]+[\s]+(.+) \(([^\s]+)\)');
static DateTime _startTime;
final int methodCount;
final int errorMethodCount;
final int lineLength;
final bool colors;
final bool prefix;
final bool printEmojis;
final bool printTime;
String _topBorder = '';
String _middleBorder = '';
String _bottomBorder = '';
PrettyPrinter({
this.methodCount = 2,
this.errorMethodCount = 8,
this.lineLength = 120,
this.colors = true,
this.prefix = false,
this.printEmojis = true,
this.printTime = false,
}) {
_startTime ??= DateTime.now();
var doubleDividerLine = StringBuffer();
var singleDividerLine = StringBuffer();
for (int i = 0; i < lineLength - 1; i++) {
doubleDividerLine.write(doubleDivider);
singleDividerLine.write(singleDivider);
}
_topBorder = "$topLeftCorner$doubleDividerLine";
_middleBorder = "$middleCorner$singleDividerLine";
_bottomBorder = "$bottomLeftCorner$doubleDividerLine";
}
@override
List<String> log(LogEvent event) {
var messageStr = stringifyMessage(event.message);
String stackTraceStr;
if (event.stackTrace == null) {
if (methodCount > 0) {
stackTraceStr = formatStackTrace(StackTrace.current, methodCount);
}
} else if (errorMethodCount > 0) {
stackTraceStr = formatStackTrace(event.stackTrace, errorMethodCount);
}
var errorStr = event.error?.toString();
String timeStr;
if (printTime) {
timeStr = getTime();
}
return _formatAndPrint(
event.level,
messageStr,
timeStr,
errorStr,
stackTraceStr,
);
}
String formatStackTrace(StackTrace stackTrace, int methodCount) {
var lines = stackTrace.toString().split("\n");
var formatted = <String>[];
var count = 0;
for (var line in lines) {
var match = stackTraceRegex.matchAsPrefix(line);
if (match != null) {
if (match.group(2).startsWith('package:logger')) {
continue;
}
var newLine = "#$count ${match.group(1)} (${match.group(2)})";
formatted.add(newLine.replaceAll('<anonymous closure>', '()'));
if (++count == methodCount) {
break;
}
} else {
formatted.add(line);
}
}
if (formatted.isEmpty) {
return null;
} else {
return formatted.join('\n');
}
}
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();
String h = _twoDigits(now.hour);
String min = _twoDigits(now.minute);
String sec = _twoDigits(now.second);
String ms = _threeDigits(now.millisecond);
var timeSinceStart = now.difference(_startTime).toString();
return "$h:$min:$sec.$ms (+$timeSinceStart)";
}
String stringifyMessage(dynamic message) {
if (message is Map || message is Iterable) {
var encoder = JsonEncoder.withIndent(' ');
return encoder.convert(message);
} else {
return message.toString();
}
}
AnsiColor _getLevelColor(Level level) {
if (colors) {
return levelColors[level];
} else {
if (prefix) {
return levelPrefix[level];
} else {
return AnsiColor.none();
}
}
}
AnsiColor _getErrorColor(Level level) {
if (colors) {
if (level == Level.wtf) {
return levelColors[Level.wtf].toBg();
} else {
return levelColors[Level.error].toBg();
}
} else {
if (prefix) {
return levelPrefix[level];
} else {
return AnsiColor.none();
}
}
}
String _getEmoji(Level level) {
if (printEmojis) {
return levelEmojis[level];
} else {
return "";
}
}
List<String> _formatAndPrint(
Level level,
String message,
String time,
String error,
String stacktrace,
) {
List<String> buffer = [];
var color = _getLevelColor(level);
buffer.add(color(_topBorder));
if (error != null) {
var errorColor = _getErrorColor(level);
for (var line in error.split('\n')) {
buffer.add(
color('$verticalLine ') +
errorColor.resetForeground +
errorColor(line) +
errorColor.resetBackground,
);
}
buffer.add(color(_middleBorder));
}
if (stacktrace != null) {
for (var line in stacktrace.split('\n')) {
buffer.add('$color$verticalLine $line');
}
buffer.add(color(_middleBorder));
}
if (time != null) {
buffer..add(color('$verticalLine $time'))..add(color(_middleBorder));
}
var emoji = _getEmoji(level);
for (var line in message.split('\n')) {
buffer.add(color('$verticalLine $emoji$line'));
}
buffer.add(color(_bottomBorder));
return buffer;
}
}
ansi_color.dart
class AnsiColor {
/// ANSI Control Sequence Introducer, signals the terminal for new settings.
static const ansiEsc = '\x1B[';
/// Reset all colors and options for current SGRs to terminal defaults.
static const ansiDefault = "${ansiEsc}0m";
final int fg;
final int bg;
final bool color;
final String prefix;
AnsiColor.none()
: fg = null,
bg = null,
color = false,
prefix = "";
AnsiColor.fg(this.fg)
: bg = null,
color = true,
prefix = "";
AnsiColor.bg(this.bg)
: fg = null,
color = true,
prefix = "";
AnsiColor.txt(this.prefix)
: fg = null,
bg = null,
color = false;
String toString() {
if (color) {
if (fg != null) {
return "${ansiEsc}38;5;${fg}m";
} else if (bg != null) {
return "${ansiEsc}48;5;${bg}m";
} else {
return "";
}
} else {
return prefix;
}
}
String call(String msg) {
if (color) {
return "${this}$msg$ansiDefault";
} else {
return "${this}$msg";
}
}
AnsiColor toFg() => AnsiColor.fg(bg);
AnsiColor toBg() => AnsiColor.bg(fg);
/// Defaults the terminal's foreground color without altering the background.
String get resetForeground => color ? "${ansiEsc}39m" : "";
/// Defaults the terminal's background color without altering the foreground.
String get resetBackground => color ? "${ansiEsc}49m" : "";
static int grey(double level) => 232 + (level.clamp(0.0, 1.0) * 23).round();
}
Hi all - so I came up with a slightly tweaked workaround for this as well. I wrote a PrefixPrinter class which you can use to decorate the PrettyPrinter class with colors turned off.
class PrefixPrinter extends LogPrinter {
final LogPrinter _realPrinter;
Map<Level, String> _prefixMap;
PrefixPrinter(this._realPrinter,
{debug, verbose, wtf, info, warning, error, nothing}) : super() {
_prefixMap = {
Level.debug: debug ?? ' DEBUG ',
Level.verbose: verbose ?? 'VERBOSE ',
Level.wtf: wtf ?? ' WTF ',
Level.info: info ?? ' INFO ',
Level.warning: warning ?? 'WARNING ',
Level.error: error ?? ' ERROR ',
Level.nothing: nothing ?? 'NOTHING',
};
}
@override
List<String> log(LogEvent event) {
return _realPrinter.log(event).map((s) => '${_prefixMap[event.level]}$s').toList();
}
}
So using that you could create a printer like:
PrefixPrinter(PrettyPrinter(colors: false));

and you don't have to grep (or see) the ansi strings or copy/change the existing PrettyPrinter class. Hopefully this helps! And thanks for this thread in helping me find the issue/workaround in the first place.
so do i have to customize the classes to fix this issue?
@mohadel92 The solution I mention included some classes that were merged and released in 0.9.2. You can check out the pull request that has some examples of how you'd use this in https://github.com/leisim/logger/pull/44
But the idea was that you don't have to customize the classes, this additional class just decorates the existing. Here is a simple example:
final logger = Logger(
printer: PrefixPrinter(
PrettyPrinter(
methodCount: 2,
colors: false,
printTime: true
)
)
);
Then you just need to grep the prefixes (see README). Note this was specifically targeting AndroidStudio / JetBrains IDEs.
Thanks @PandaProgParis it's working with me perfectly 👍