1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.core;
15  
16  import java.io.OutputStream;
17  import java.io.PrintStream;
18  import java.lang.reflect.Method;
19  import java.lang.reflect.Modifier;
20  import java.util.Arrays;
21  import java.util.NoSuchElementException;
22  import java.util.Optional;
23  
24  import ch.qos.logback.core.joran.spi.ConsoleTarget;
25  import ch.qos.logback.core.status.Status;
26  import ch.qos.logback.core.status.WarnStatus;
27  import ch.qos.logback.core.util.Loader;
28  import ch.qos.logback.core.util.ReentryGuard;
29  import ch.qos.logback.core.util.ReentryGuardFactory;
30  
31  /**
32   * ConsoleAppender appends log events to <code>System.out</code> or
33   * <code>System.err</code> using a layout specified by the user. The default
34   * target is <code>System.out</code>.
35   * <p>
36   * &nbsp;
37   * </p>
38   * For more information about this appender, please refer to the online manual
39   * at http://logback.qos.ch/manual/appenders.html#ConsoleAppender
40   *
41   * @author Ceki G&uuml;lc&uuml;
42   * @author Tom SH Liu
43   * @author Ruediger Dohna
44   */
45  
46  public class ConsoleAppender<E> extends OutputStreamAppender<E> {
47  
48      protected ConsoleTarget target = ConsoleTarget.SystemOut;
49      protected boolean withJansi = false;
50  
51      private final static String AnsiConsole_CLASS_NAME = "org.fusesource.jansi.AnsiConsole";
52      private final static String JANSI2_OUT_METHOD_NAME = "out";
53      private final static String JANSI2_ERR_METHOD_NAME = "err";
54      private final static String WRAP_SYSTEM_OUT_METHOD_NAME = "wrapSystemOut";
55      private final static String WRAP_SYSTEM_ERR_METHOD_NAME = "wrapSystemErr";
56      private final static String SYSTEM_INSTALL_METHOD_NAME = "systemInstall";
57      private final static Class<?>[] ARGUMENT_TYPES = { PrintStream.class };
58  
59      private final static String CONSOLE_APPENDER_WARNING_URL = CoreConstants.CODES_URL+"#slowConsole";
60  
61      /**
62       * Sets the value of the <b>Target</b> option. Recognized values are
63       * "System.out" and "System.err". Any other value will be ignored.
64       */
65      public void setTarget(String value) {
66          ConsoleTarget t = ConsoleTarget.findByName(value.trim());
67          if (t == null) {
68              targetWarn(value);
69          } else {
70              target = t;
71          }
72      }
73  
74      /**
75       * Returns the current value of the <b>target</b> property. The default value of
76       * the option is "System.out".
77       * <p>
78       * See also {@link #setTarget}.
79       */
80      public String getTarget() {
81          return target.getName();
82      }
83  
84      private void targetWarn(String val) {
85          Status status = new WarnStatus("[" + val + "] should be one of " + Arrays.toString(ConsoleTarget.values()),
86                  this);
87          status.add(new WarnStatus("Using previously set target, System.out by default.", this));
88          addStatus(status);
89      }
90  
91      @Override
92      public void start() {
93          addInfo("NOTE: Writing to the console can be slow. Try to avoid logging to the ");
94          addInfo("console in production environments, especially in high volume systems.");
95          addInfo("See also "+CONSOLE_APPENDER_WARNING_URL);
96          OutputStream targetStream = target.getStream();
97          // enable jansi only if withJansi set to true
98          if (withJansi) {
99              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 }