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