View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.appender;
18  
19  import java.io.FileDescriptor;
20  import java.io.FileOutputStream;
21  import java.io.IOException;
22  import java.io.OutputStream;
23  import java.io.PrintStream;
24  import java.io.Serializable;
25  import java.io.UnsupportedEncodingException;
26  import java.lang.reflect.Constructor;
27  import java.nio.charset.Charset;
28  import java.util.concurrent.atomic.AtomicInteger;
29  
30  import org.apache.logging.log4j.core.Appender;
31  import org.apache.logging.log4j.core.Core;
32  import org.apache.logging.log4j.core.Filter;
33  import org.apache.logging.log4j.core.Layout;
34  import org.apache.logging.log4j.core.config.plugins.Plugin;
35  import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
36  import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
37  import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
38  import org.apache.logging.log4j.core.layout.PatternLayout;
39  import org.apache.logging.log4j.core.util.Booleans;
40  import org.apache.logging.log4j.core.util.CloseShieldOutputStream;
41  import org.apache.logging.log4j.core.util.Throwables;
42  import org.apache.logging.log4j.util.LoaderUtil;
43  import org.apache.logging.log4j.util.PropertiesUtil;
44  
45  /**
46   * Appends log events to <code>System.out</code> or <code>System.err</code> using a layout specified by the user. The
47   * default target is <code>System.out</code>.
48   * <p>
49   * TODO Accessing <code>System.out</code> or <code>System.err</code> as a byte stream instead of a writer bypasses the
50   * JVM's knowledge of the proper encoding. (RG) Encoding is handled within the Layout. Typically, a Layout will generate
51   * a String and then call getBytes which may use a configured encoding or the system default. OTOH, a Writer cannot
52   * print byte streams.
53   * </p>
54   */
55  @Plugin(name = ConsoleAppender.PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
56  public final class ConsoleAppender extends AbstractOutputStreamAppender<OutputStreamManager> {
57  
58      public static final String PLUGIN_NAME = "Console";
59      private static final String JANSI_CLASS = "org.fusesource.jansi.WindowsAnsiOutputStream";
60      private static ConsoleManagerFactory factory = new ConsoleManagerFactory();
61      private static final Target DEFAULT_TARGET = Target.SYSTEM_OUT;
62      private static final AtomicInteger COUNT = new AtomicInteger();
63  
64      private final Target target;
65  
66      /**
67       * Enumeration of console destinations.
68       */
69      public enum Target {
70  
71          /** Standard output. */
72          SYSTEM_OUT {
73              @Override
74              public Charset getDefaultCharset() {
75                  // "sun.stdout.encoding" is only set when running from the console.
76                  return getCharset("sun.stdout.encoding", Charset.defaultCharset());
77              }
78          },
79  
80          /** Standard error output. */
81          SYSTEM_ERR {
82              @Override
83              public Charset getDefaultCharset() {
84                  // "sun.stderr.encoding" is only set when running from the console.
85                  return getCharset("sun.stderr.encoding", Charset.defaultCharset());
86              }
87          };
88  
89          public abstract Charset getDefaultCharset();
90  
91          protected Charset getCharset(final String property, Charset defaultCharset) {
92              return new PropertiesUtil(PropertiesUtil.getSystemProperties()).getCharsetProperty(property, defaultCharset);
93          }
94  
95      }
96  
97      private ConsoleAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
98              final OutputStreamManager manager, final boolean ignoreExceptions, final Target target) {
99          super(name, layout, filter, ignoreExceptions, true, manager);
100         this.target = target;
101     }
102 
103     /**
104      * Creates a Console Appender.
105      *
106      * @param layout The layout to use (required).
107      * @param filter The Filter or null.
108      * @param targetStr The target ("SYSTEM_OUT" or "SYSTEM_ERR"). The default is "SYSTEM_OUT".
109      * @param name The name of the Appender (required).
110      * @param follow If true will follow changes to the underlying output stream.
111      * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they
112      *            are propagated to the caller.
113      * @return The ConsoleAppender.
114      * @deprecated Deprecated in 2.7; use {@link #newBuilder()}.
115      */
116     @Deprecated
117     public static ConsoleAppender createAppender(Layout<? extends Serializable> layout,
118             final Filter filter,
119             final String targetStr,
120             final String name,
121             final String follow,
122             final String ignore) {
123         if (name == null) {
124             LOGGER.error("No name provided for ConsoleAppender");
125             return null;
126         }
127         if (layout == null) {
128             layout = PatternLayout.createDefaultLayout();
129         }
130         final boolean isFollow = Boolean.parseBoolean(follow);
131         final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
132         final Target target = targetStr == null ? DEFAULT_TARGET : Target.valueOf(targetStr);
133         return new ConsoleAppender(name, layout, filter, getManager(target, isFollow, false, layout), ignoreExceptions, target);
134     }
135 
136     /**
137      * Creates a Console Appender.
138      *
139      * @param layout The layout to use (required).
140      * @param filter The Filter or null.
141      * @param target The target (SYSTEM_OUT or SYSTEM_ERR). The default is SYSTEM_OUT.
142      * @param name The name of the Appender (required).
143      * @param follow If true will follow changes to the underlying output stream.
144      * @param direct If true will write directly to {@link java.io.FileDescriptor} and bypass
145      *            {@link System#out}/{@link System#err}.
146      * @param ignoreExceptions If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they
147      *            are propagated to the caller.
148      * @return The ConsoleAppender.
149      * @deprecated Deprecated in 2.7; use {@link #newBuilder()}.
150      */
151     @Deprecated
152     public static ConsoleAppender createAppender(
153             // @formatter:off
154             Layout<? extends Serializable> layout,
155             final Filter filter,
156             Target target,
157             final String name,
158             final boolean follow,
159             final boolean direct,
160             final boolean ignoreExceptions) {
161             // @formatter:on
162         if (name == null) {
163             LOGGER.error("No name provided for ConsoleAppender");
164             return null;
165         }
166         if (layout == null) {
167             layout = PatternLayout.createDefaultLayout();
168         }
169         target = target == null ? Target.SYSTEM_OUT : target;
170         if (follow && direct) {
171             LOGGER.error("Cannot use both follow and direct on ConsoleAppender");
172             return null;
173         }
174         return new ConsoleAppender(name, layout, filter, getManager(target, follow, direct, layout), ignoreExceptions, target);
175     }
176 
177     public static ConsoleAppender createDefaultAppenderForLayout(final Layout<? extends Serializable> layout) {
178         // this method cannot use the builder class without introducing an infinite loop due to DefaultConfiguration
179         return new ConsoleAppender("DefaultConsole-" + COUNT.incrementAndGet(), layout, null,
180                 getDefaultManager(DEFAULT_TARGET, false, false, layout), true, DEFAULT_TARGET);
181     }
182 
183     @PluginBuilderFactory
184     public static <B extends Builder<B>> B newBuilder() {
185         return new Builder<B>().asBuilder();
186     }
187 
188     /**
189      * Builds ConsoleAppender instances.
190      * @param <B> The type to build
191      */
192     public static class Builder<B extends Builder<B>> extends AbstractOutputStreamAppender.Builder<B>
193             implements org.apache.logging.log4j.core.util.Builder<ConsoleAppender> {
194 
195         @PluginBuilderAttribute
196         @Required
197         private Target target = DEFAULT_TARGET;
198 
199         @PluginBuilderAttribute
200         private boolean follow;
201 
202         @PluginBuilderAttribute
203         private boolean direct;
204 
205         public B setTarget(final Target aTarget) {
206             this.target = aTarget;
207             return asBuilder();
208         }
209 
210         public B setFollow(final boolean shouldFollow) {
211             this.follow = shouldFollow;
212             return asBuilder();
213         }
214 
215         public B setDirect(final boolean shouldDirect) {
216             this.direct = shouldDirect;
217             return asBuilder();
218         }
219 
220         @Override
221         public ConsoleAppender build() {
222             if (follow && direct) {
223                 throw new IllegalArgumentException("Cannot use both follow and direct on ConsoleAppender '" + getName() + "'");
224             }
225             final Layout<? extends Serializable> layout = getOrCreateLayout(target.getDefaultCharset());
226             return new ConsoleAppender(getName(), layout, getFilter(), getManager(target, follow, direct, layout),
227                     isIgnoreExceptions(), target);
228         }
229     }
230 
231     private static OutputStreamManager getDefaultManager(final Target target, final boolean follow, final boolean direct,
232             final Layout<? extends Serializable> layout) {
233         final OutputStream os = getOutputStream(follow, direct, target);
234 
235         // LOG4J2-1176 DefaultConfiguration should not share OutputStreamManager instances to avoid memory leaks.
236         final String managerName = target.name() + '.' + follow + '.' + direct + "-" + COUNT.get();
237         return OutputStreamManager.getManager(managerName, new FactoryData(os, managerName, layout), factory);
238     }
239 
240     private static OutputStreamManager getManager(final Target target, final boolean follow, final boolean direct,
241             final Layout<? extends Serializable> layout) {
242         final OutputStream os = getOutputStream(follow, direct, target);
243         final String managerName = target.name() + '.' + follow + '.' + direct;
244         return OutputStreamManager.getManager(managerName, new FactoryData(os, managerName, layout), factory);
245     }
246 
247     private static OutputStream getOutputStream(final boolean follow, final boolean direct, final Target target) {
248         final String enc = Charset.defaultCharset().name();
249         OutputStream outputStream;
250         try {
251             // @formatter:off
252             outputStream = target == Target.SYSTEM_OUT ?
253                 direct ? new FileOutputStream(FileDescriptor.out) :
254                     (follow ? new PrintStream(new SystemOutStream(), true, enc) : System.out) :
255                 direct ? new FileOutputStream(FileDescriptor.err) :
256                     (follow ? new PrintStream(new SystemErrStream(), true, enc) : System.err);
257             // @formatter:on
258             outputStream = new CloseShieldOutputStream(outputStream);
259         } catch (final UnsupportedEncodingException ex) { // should never happen
260             throw new IllegalStateException("Unsupported default encoding " + enc, ex);
261         }
262         final PropertiesUtil propsUtil = PropertiesUtil.getProperties();
263         if (!propsUtil.isOsWindows() || propsUtil.getBooleanProperty("log4j.skipJansi", true) || direct) {
264             return outputStream;
265         }
266         try {
267             // We type the parameter as a wildcard to avoid a hard reference to Jansi.
268             final Class<?> clazz = LoaderUtil.loadClass(JANSI_CLASS);
269             final Constructor<?> constructor = clazz.getConstructor(OutputStream.class);
270             return new CloseShieldOutputStream((OutputStream) constructor.newInstance(outputStream));
271         } catch (final ClassNotFoundException cnfe) {
272             LOGGER.debug("Jansi is not installed, cannot find {}", JANSI_CLASS);
273         } catch (final NoSuchMethodException nsme) {
274             LOGGER.warn("{} is missing the proper constructor", JANSI_CLASS);
275         } catch (final Exception ex) {
276             LOGGER.warn("Unable to instantiate {} due to {}", JANSI_CLASS, Throwables.getRootCause(ex).toString().trim());
277         }
278         return outputStream;
279     }
280 
281     /**
282      * An implementation of OutputStream that redirects to the current System.err.
283      */
284     private static class SystemErrStream extends OutputStream {
285         public SystemErrStream() {
286         }
287 
288         @Override
289         public void close() {
290             // do not close sys err!
291         }
292 
293         @Override
294         public void flush() {
295             System.err.flush();
296         }
297 
298         @Override
299         public void write(final byte[] b) throws IOException {
300             System.err.write(b);
301         }
302 
303         @Override
304         public void write(final byte[] b, final int off, final int len) throws IOException {
305             System.err.write(b, off, len);
306         }
307 
308         @Override
309         public void write(final int b) {
310             System.err.write(b);
311         }
312     }
313 
314     /**
315      * An implementation of OutputStream that redirects to the current System.out.
316      */
317     private static class SystemOutStream extends OutputStream {
318         public SystemOutStream() {
319         }
320 
321         @Override
322         public void close() {
323             // do not close sys out!
324         }
325 
326         @Override
327         public void flush() {
328             System.out.flush();
329         }
330 
331         @Override
332         public void write(final byte[] b) throws IOException {
333             System.out.write(b);
334         }
335 
336         @Override
337         public void write(final byte[] b, final int off, final int len) throws IOException {
338             System.out.write(b, off, len);
339         }
340 
341         @Override
342         public void write(final int b) throws IOException {
343             System.out.write(b);
344         }
345     }
346 
347     /**
348      * Data to pass to factory method.Unable to instantiate
349      */
350     private static class FactoryData {
351         private final OutputStream os;
352         private final String name;
353         private final Layout<? extends Serializable> layout;
354 
355         /**
356          * Constructor.
357          *
358          * @param os The OutputStream.
359          * @param type The name of the target.
360          * @param layout A Serializable layout
361          */
362         public FactoryData(final OutputStream os, final String type, final Layout<? extends Serializable> layout) {
363             this.os = os;
364             this.name = type;
365             this.layout = layout;
366         }
367     }
368 
369     /**
370      * Factory to create the Appender.
371      */
372     private static class ConsoleManagerFactory implements ManagerFactory<OutputStreamManager, FactoryData> {
373 
374         /**
375          * Create an OutputStreamManager.
376          *
377          * @param name The name of the entity to manage.
378          * @param data The data required to create the entity.
379          * @return The OutputStreamManager
380          */
381         @Override
382         public OutputStreamManager createManager(final String name, final FactoryData data) {
383             return new OutputStreamManager(data.os, data.name, data.layout, true);
384         }
385     }
386 
387     public Target getTarget() {
388         return target;
389     }
390 
391 }