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