JansiConsoleAppender.java
/*
* Logback: the reliable, generic, fast and flexible logging framework.
* Copyright (C) 1999-2026, QOS.ch. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v2.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation.
*/
package ch.qos.logback.core;
import java.io.OutputStream;
import org.jline.jansi.AnsiConsole;
import ch.qos.logback.core.joran.spi.ConsoleTarget;
/**
* A {@link ConsoleAppender} that always writes through JLine's
* {@link AnsiConsole}, enabling ANSI sequences on platforms that need Jansi
* (notably Windows).
* <p>
* Unlike {@link ConsoleAppender}'s deprecated {@code withJansi} path, this
* class overrides {@link #wrapTarget(OutputStream)} and calls
* {@link AnsiConsole} directly (no reflection). It requires
* {@code org.jline:jansi-core} on the classpath.
* </p>
* <p>
* {@link AnsiConsole#systemInstall()} is paired with
* {@link AnsiConsole#systemUninstall()} on {@link #stop()} when this appender
* performed the install. Console streams are still only flushed on stop (not
* closed); see {@link ConsoleAppender#closeOutputStream()}.
* </p>
*
* @param <E> the type of logging events
* @author Ceki Gülcü
* @since 1.6.3
* @see AnsiConsole
* @see ConsoleAppender#wrapTarget(OutputStream)
*/
public class JansiConsoleAppender<E> extends ConsoleAppender<E> {
/**
* True after this instance has successfully called
* {@link AnsiConsole#systemInstall()} and until the matching
* {@link AnsiConsole#systemUninstall()} on {@link #stop()}.
*/
private boolean installedByThisAppender;
/**
* Flushes the console stream (via {@link ConsoleAppender#stop()}), then
* undoes {@link AnsiConsole#systemInstall()} if this appender performed it.
*/
@Override
public void stop() {
try {
super.stop();
} finally {
uninstallAnsiConsoleIfInstalledByThisAppender();
}
}
/**
* Installs Jansi and returns {@link AnsiConsole#out()} or
* {@link AnsiConsole#err()} according to the configured target.
* <p>
* Does not use the deprecated {@code withJansi} / {@code wrapWithJansi}
* path. {@link AnsiConsole#systemInstall()} is invoked at most once per
* install ownership of this instance.
* </p>
*/
@Override
protected OutputStream wrapTarget(OutputStream targetStream) {
try {
addInfo("Enabling JANSI AnsiPrintStream via org.jline.jansi.AnsiConsole.");
if (!installedByThisAppender) {
AnsiConsole.systemInstall();
installedByThisAppender = true;
}
if (target == ConsoleTarget.SystemErr) {
return AnsiConsole.err();
} else {
return AnsiConsole.out();
}
} catch (Exception e) {
addWarn("Failed to create AnsiPrintStream. Falling back on the default stream.", e);
return targetStream;
}
}
private void uninstallAnsiConsoleIfInstalledByThisAppender() {
if (!installedByThisAppender) {
return;
}
installedByThisAppender = false;
try {
AnsiConsole.systemUninstall();
addInfo("Uninstalled JANSI AnsiConsole previously installed by this appender.");
} catch (RuntimeException e) {
addWarn("Failed to uninstall AnsiConsole.", e);
}
}
}