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