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