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 * &nbsp;
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&uuml;lc&uuml;
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}