logger icon indicating copy to clipboard operation
logger copied to clipboard

Colors not working in Android Studio

Open phpmaple opened this issue 6 years ago • 18 comments

^[[38;5;196m⛔ - FormatException: Unexpected end of input (at character 1) [11:10:59 AM]: <== ^

phpmaple avatar Jul 04 '19 03:07 phpmaple

Is this on Android or iOS? It may be related to #1

simc avatar Jul 04 '19 07:07 simc

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.

Tyxz avatar Jul 05 '19 14:07 Tyxz

@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,
)

simc avatar Jul 05 '19 17:07 simc

Hey, this tool is absolutely excellent, but unfortunately i am using Android Studio. Will u fix the ansi-color problem in Android Studio?

Creolophus avatar Jul 16 '19 09:07 Creolophus

@Creolophus Yes, I'm currently working on a solution.

simc avatar Jul 16 '19 09:07 simc

@leisim Hi, any news?)

NarHakobyan avatar Sep 17 '19 18:09 NarHakobyan

@NarHakobyan

Sorry have been busy with hive lately.

simc avatar Sep 17 '19 19:09 simc

Congrats, awesome project 🤘

NarHakobyan avatar Sep 17 '19 21:09 NarHakobyan

@leisim Same problem on Ubuntu Android Studio, is it related to fonts configuration? Emojis also don't work.

erabti avatar Dec 11 '19 11:12 erabti

The terminal in IntelliJ works just fine. I guess it's an issue w/ the console (the thing that appears when you hit run).

saif97 avatar Dec 17 '19 14:12 saif97

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!

giovannilattanzio avatar Jan 16 '20 12:01 giovannilattanzio

That's really creative!

haarts avatar Jan 31 '20 08:01 haarts

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!

tks!

dp543831577 avatar Apr 16 '20 11:04 dp543831577

@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. image

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.

image

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();
}

PandaProgParis avatar Apr 19 '20 19:04 PandaProgParis

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));

image

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.

tkutcher avatar May 09 '20 09:05 tkutcher

so do i have to customize the classes to fix this issue?

mohadel92 avatar Nov 30 '20 08:11 mohadel92

@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.

tkutcher avatar Nov 30 '20 13:11 tkutcher

Thanks @PandaProgParis it's working with me perfectly 👍

Hakim-Allaoui avatar Dec 25 '20 13:12 Hakim-Allaoui