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; 017import java.io.PrintStream; 018import java.lang.reflect.Method; 019import java.lang.reflect.Modifier; 020import java.util.Arrays; 021import java.util.NoSuchElementException; 022import java.util.Optional; 023 024import ch.qos.logback.core.joran.spi.ConsoleTarget; 025import ch.qos.logback.core.status.Status; 026import ch.qos.logback.core.status.WarnStatus; 027import ch.qos.logback.core.util.Loader; 028import ch.qos.logback.core.util.ReentryGuard; 029import ch.qos.logback.core.util.ReentryGuardFactory; 030 031/** 032 * ConsoleAppender appends log events to <code>System.out</code> or 033 * <code>System.err</code> using a layout specified by the user. The default 034 * target is <code>System.out</code>. 035 * <p> 036 * 037 * </p> 038 * For more information about this appender, please refer to the online manual 039 * at http://logback.qos.ch/manual/appenders.html#ConsoleAppender 040 * 041 * @author Ceki Gülcü 042 * @author Tom SH Liu 043 * @author Ruediger Dohna 044 */ 045 046public class ConsoleAppender<E> extends OutputStreamAppender<E> { 047 048 protected ConsoleTarget target = ConsoleTarget.SystemOut; 049 protected boolean withJansi = false; 050 051 private final static String AnsiConsole_CLASS_NAME = "org.fusesource.jansi.AnsiConsole"; 052 private final static String JANSI2_OUT_METHOD_NAME = "out"; 053 private final static String JANSI2_ERR_METHOD_NAME = "err"; 054 private final static String WRAP_SYSTEM_OUT_METHOD_NAME = "wrapSystemOut"; 055 private final static String WRAP_SYSTEM_ERR_METHOD_NAME = "wrapSystemErr"; 056 private final static String SYSTEM_INSTALL_METHOD_NAME = "systemInstall"; 057 private final static Class<?>[] ARGUMENT_TYPES = { PrintStream.class }; 058 059 private final static String CONSOLE_APPENDER_WARNING_URL = CoreConstants.CODES_URL+"#slowConsole"; 060 061 /** 062 * Sets the value of the <b>Target</b> option. Recognized values are 063 * "System.out" and "System.err". Any other value will be ignored. 064 */ 065 public void setTarget(String value) { 066 ConsoleTarget t = ConsoleTarget.findByName(value.trim()); 067 if (t == null) { 068 targetWarn(value); 069 } else { 070 target = t; 071 } 072 } 073 074 /** 075 * Returns the current value of the <b>target</b> property. The default value of 076 * the option is "System.out". 077 * <p> 078 * See also {@link #setTarget}. 079 */ 080 public String getTarget() { 081 return target.getName(); 082 } 083 084 private void targetWarn(String val) { 085 Status status = new WarnStatus("[" + val + "] should be one of " + Arrays.toString(ConsoleTarget.values()), 086 this); 087 status.add(new WarnStatus("Using previously set target, System.out by default.", this)); 088 addStatus(status); 089 } 090 091 @Override 092 public void start() { 093 addInfo("NOTE: Writing to the console can be slow. Try to avoid logging to the "); 094 addInfo("console in production environments, especially in high volume systems."); 095 addInfo("See also "+CONSOLE_APPENDER_WARNING_URL); 096 OutputStream targetStream = target.getStream(); 097 // enable jansi only if withJansi set to true 098 if (withJansi) { 099 targetStream = wrapWithJansi(targetStream); 100 } 101 setOutputStream(targetStream); 102 super.start(); 103 } 104 105 /** 106 * Create a ThreadLocal ReentryGuard to prevent recursive appender invocations. 107 * @return a ReentryGuard instance of type {@link ReentryGuardFactory.GuardType#THREAD_LOCAL THREAD_LOCAL}. 108 */ 109 protected ReentryGuard buildReentryGuard() { 110 return ReentryGuardFactory.makeGuard(ReentryGuardFactory.GuardType.THREAD_LOCAL); 111 } 112 113 private OutputStream wrapWithJansi(OutputStream targetStream) { 114 try { 115 addInfo("Enabling JANSI AnsiPrintStream for the console."); 116 ClassLoader classLoader = Loader.getClassLoaderOfObject(context); 117 Class<?> classObj = classLoader.loadClass(AnsiConsole_CLASS_NAME); 118 119 Method systemInstallMethod = classObj.getMethod(SYSTEM_INSTALL_METHOD_NAME); 120 if(systemInstallMethod != null) { 121 systemInstallMethod.invoke(null); 122 } 123 124// final Optional<Method> optSystemInstallMethod = Arrays.stream(classObj.getMethods()) 125// .filter(m -> m.getName().equals(SYSTEM_INSTALL_METHOD_NAME)) 126// .filter(m -> m.getParameters().length == 0) 127// .filter(m -> Modifier.isStatic(m.getModifiers())) 128// .findAny(); 129// 130// if (optSystemInstallMethod.isPresent()) { 131// final Method systemInstallMethod = optSystemInstallMethod.orElseThrow(() -> new NoSuchElementException("No systemInstall method present")); 132// systemInstallMethod.invoke(null); 133// } 134 135 // check for JAnsi 2 136 String methodNameJansi2 = target == ConsoleTarget.SystemOut ? JANSI2_OUT_METHOD_NAME 137 : JANSI2_ERR_METHOD_NAME; 138 final Optional<Method> optOutMethod = Arrays.stream(classObj.getMethods()) 139 .filter(m -> m.getName().equals(methodNameJansi2)) 140 .filter(m -> m.getParameters().length == 0) 141 .filter(m -> Modifier.isStatic(m.getModifiers())) 142 .filter(m -> PrintStream.class.isAssignableFrom(m.getReturnType())) 143 .findAny(); 144 if (optOutMethod.isPresent()) { 145 final Method outMethod = optOutMethod.orElseThrow(() -> new NoSuchElementException("No out/err method present")); 146 return (PrintStream) outMethod.invoke(null); 147 } 148 149 // JAnsi 1 150 String methodName = target == ConsoleTarget.SystemOut ? WRAP_SYSTEM_OUT_METHOD_NAME 151 : WRAP_SYSTEM_ERR_METHOD_NAME; 152 Method method = classObj.getMethod(methodName, ARGUMENT_TYPES); 153 return (OutputStream) method.invoke(null, new PrintStream(targetStream)); 154 } catch (Exception e) { 155 addWarn("Failed to create AnsiPrintStream. Falling back on the default stream.", e); 156 } 157 return targetStream; 158 } 159 160 /** 161 * @return whether to use JANSI or not. 162 */ 163 public boolean isWithJansi() { 164 return withJansi; 165 } 166 167 /** 168 * If true, this appender will output to a stream provided by the JANSI library. 169 * 170 * @param withJansi whether to use JANSI or not. 171 * @since 1.0.5 172 */ 173 public void setWithJansi(boolean withJansi) { 174 this.withJansi = withJansi; 175 } 176 177}