Logback: use colored output only when logging to a real terminal

24,319

Solution 1

You have two problems here:

how to detect if you should use colors or not

This is not trivial. As this answer suggests you could use JNI call to isatty to detect if you're connected to a terminal, but it's a lot of work for fairly low-priority feature.

how to conditionally use colors in logback

That is actually quite easy (official docs), remember that you need janino for this to work:

<configuration>
    <appender name="COLOR" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%date] %highlight([%level]) [%logger{10} %file:%line] %msg%n</pattern>
            <!--             ^^^^^^^^^^ -->
        </encoder>
    </appender>
    <appender name="NOCOLOR" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%date] [%level] [%logger{10} %file:%line] %msg%n</pattern>
        </encoder>
    </appender>
    <root level="debug">
        <!-- to use enable this mode pass -Dcolor to jvm -->
        <if condition='isDefined("color")'>
            <then>
                    <appender-ref ref="COLOR"/>
            </then>
            <else>
                    <appender-ref ref="NOCOLOR"/>
            </else>
        </if>
    </root>
</configuration>

Solution 2

On my side, I found a workaround by creating a custom PatternLayout. Its job is to replace default color converters by converters doing nothing when the terminal doesn't support color.

As :

  • I also use Picocli
  • and in my experience, the Picocli ANSI color support detection heuristic is pretty good.

I use it to know when the color converter should be removed. (but you could use any other heuristic or using an environmental variable like in https://stackoverflow.com/a/36790201/5088764 without the need of janino dependency)

The code looks like this :

/**
 * A Logback Pattern Layout that uses Picocli ANSI color
 * heuristic to apply ANSI color only on the terminal which
 * supports it.
 */
public class ColorAwarePatternLayout extends PatternLayout {
   static {
      if (!Ansi.AUTO.enabled()) { // Usage of Picocli heuristic
          DEFAULT_CONVERTER_MAP.put("black", NoColorConverter.class.getName());
          DEFAULT_CONVERTER_MAP.put("red", NoColorConverter.class.getName());
          DEFAULT_CONVERTER_MAP.put("green", NoColorConverter.class.getName());
          DEFAULT_CONVERTER_MAP.put("yellow", NoColorConverter.class.getName());
          DEFAULT_CONVERTER_MAP.put("blue", NoColorConverter.class.getName());
          DEFAULT_CONVERTER_MAP.put("magenta", NoColorConverter.class.getName());
          DEFAULT_CONVERTER_MAP.put("cyan", NoColorConverter.class.getName());
          DEFAULT_CONVERTER_MAP.put("white", NoColorConverter.class.getName());
          DEFAULT_CONVERTER_MAP.put("gray", NoColorConverter.class.getName());
          DEFAULT_CONVERTER_MAP.put("boldRed", NoColorConverter.class.getName());
          DEFAULT_CONVERTER_MAP.put("boldGreen", NoColorConverter.class.getName());
          DEFAULT_CONVERTER_MAP.put("boldYellow", NoColorConverter.class.getName());
          DEFAULT_CONVERTER_MAP.put("boldBlue", NoColorConverter.class.getName());
          DEFAULT_CONVERTER_MAP.put("boldMagenta", NoColorConverter.class.getName());
          DEFAULT_CONVERTER_MAP.put("boldCyan", NoColorConverter.class.getName());
          DEFAULT_CONVERTER_MAP.put("boldWhite", NoColorConverter.class.getName());
          DEFAULT_CONVERTER_MAP.put("highlight", NoColorConverter.class.getName());
      }
   }
}
public class NoColorConverter<E> extends CompositeConverter<E> {
   @Override
   protected String transform(E event, String in) {
      return in;
   }
}
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="your.package.ColorAwarePatternLayout">
                <pattern>%gray(%30.30logger{0}) %gray(%d) [%highlight(%p)] %m%n</pattern>
            </layout>
        </encoder>
    </appender>

    <root level="WARN">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

(See https://github.com/eclipse/leshan/pull/1068)

Solution 3

Actually filtering out ANSI escape sequences if the underlying terminal is not compatible with it is quite easy. You do not need to write any code for this.

According to the Logback documentation here you only need to set the withJansi property to true this way:

<configuration debug="true">
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <withJansi>true</withJansi>
    <encoder>
      <pattern>[%thread] %highlight(%-5level) %cyan(%logger{15}) - %msg %n</pattern>
    </encoder>
  </appender>
  <root level="DEBUG">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

That is it.

Share:
24,319

Related videos on Youtube

Admin
Author by

Admin

Updated on July 17, 2022

Comments

  • Admin
    Admin almost 2 years

    In my Logback configuration I have the following lines:

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
      <encoder>
        <pattern>%highlight(...) %msg%n</pattern>
      </encoder>
      <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
        <level>WARN</level>
      </filter>
    </appender>
    

    This makes warnings and errors show up in terminal, colored, while main log file can contain much more information, e.g. INFO and DEBUG levels.

    Generally, this works fine. But, when I run it from Emacs or any other "not really a terminal" program, coloring commands show out as ASCII escape sequences, e.g. ^[[31m for warning highlighting. Is it somehow possible to make Logback only use ANSI coloring when connected to a real terminal?