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