001/* 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2026, QOS.ch. All rights reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under 006 * either the terms of the Eclipse Public License v2.0 as published by 007 * the Eclipse Foundation 008 * 009 * or (per the licensee's choosing) 010 * 011 * under the terms of the GNU Lesser General Public License version 2.1 012 * as published by the Free Software Foundation. 013 */ 014package ch.qos.logback.core; 015 016import java.io.OutputStream; 017 018import org.jline.jansi.AnsiConsole; 019 020import ch.qos.logback.core.joran.spi.ConsoleTarget; 021 022/** 023 * A {@link ConsoleAppender} that always writes through JLine's 024 * {@link AnsiConsole}, enabling ANSI sequences on platforms that need Jansi 025 * (notably Windows). 026 * <p> 027 * Unlike {@link ConsoleAppender}'s deprecated {@code withJansi} path, this 028 * class overrides {@link #wrapTarget(OutputStream)} and calls 029 * {@link AnsiConsole} directly (no reflection). It requires 030 * {@code org.jline:jansi-core} on the classpath. 031 * </p> 032 * <p> 033 * {@link AnsiConsole#systemInstall()} is paired with 034 * {@link AnsiConsole#systemUninstall()} on {@link #stop()} when this appender 035 * performed the install. Console streams are still only flushed on stop (not 036 * closed); see {@link ConsoleAppender#closeOutputStream()}. 037 * </p> 038 * 039 * @param <E> the type of logging events 040 * @author Ceki Gülcü 041 * @since 1.6.3 042 * @see AnsiConsole 043 * @see ConsoleAppender#wrapTarget(OutputStream) 044 */ 045public class JansiConsoleAppender<E> extends ConsoleAppender<E> { 046 047 /** 048 * True after this instance has successfully called 049 * {@link AnsiConsole#systemInstall()} and until the matching 050 * {@link AnsiConsole#systemUninstall()} on {@link #stop()}. 051 */ 052 private boolean installedByThisAppender; 053 054 /** 055 * Flushes the console stream (via {@link ConsoleAppender#stop()}), then 056 * undoes {@link AnsiConsole#systemInstall()} if this appender performed it. 057 */ 058 @Override 059 public void stop() { 060 try { 061 super.stop(); 062 } finally { 063 uninstallAnsiConsoleIfInstalledByThisAppender(); 064 } 065 } 066 067 /** 068 * Installs Jansi and returns {@link AnsiConsole#out()} or 069 * {@link AnsiConsole#err()} according to the configured target. 070 * <p> 071 * Does not use the deprecated {@code withJansi} / {@code wrapWithJansi} 072 * path. {@link AnsiConsole#systemInstall()} is invoked at most once per 073 * install ownership of this instance. 074 * </p> 075 */ 076 @Override 077 protected OutputStream wrapTarget(OutputStream targetStream) { 078 try { 079 addInfo("Enabling JANSI AnsiPrintStream via org.jline.jansi.AnsiConsole."); 080 if (!installedByThisAppender) { 081 AnsiConsole.systemInstall(); 082 installedByThisAppender = true; 083 } 084 if (target == ConsoleTarget.SystemErr) { 085 return AnsiConsole.err(); 086 } else { 087 return AnsiConsole.out(); 088 } 089 } catch (Exception e) { 090 addWarn("Failed to create AnsiPrintStream. Falling back on the default stream.", e); 091 return targetStream; 092 } 093 } 094 095 private void uninstallAnsiConsoleIfInstalledByThisAppender() { 096 if (!installedByThisAppender) { 097 return; 098 } 099 installedByThisAppender = false; 100 try { 101 AnsiConsole.systemUninstall(); 102 addInfo("Uninstalled JANSI AnsiConsole previously installed by this appender."); 103 } catch (RuntimeException e) { 104 addWarn("Failed to uninstall AnsiConsole.", e); 105 } 106 } 107 108}