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 long serialVersionUID = 1L;
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 static 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, 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 follow If true will follow changes to the underlying output stream.
85       * @param name The name of the Appender (required).
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 Use {@link #createAppender(Layout, Filter, Target, String, String, boolean)}.
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, 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 follow If true will follow changes to the underlying output stream.
118      * @param name The name of the Appender (required).
119      * @param ignoreExceptions If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they
120      *            are propagated to the caller.
121      * @return The ConsoleAppender.
122      */
123     @PluginFactory
124     public static ConsoleAppender createAppender(@PluginElement("Layout") Layout<? extends Serializable> layout,
125             @PluginElement("Filter") final Filter filter,
126             @PluginAttribute(value = "target") Target target,
127             @PluginAttribute("name") final String name,
128             @PluginAttribute(value = "follow", defaultBoolean = false) final boolean follow,
129             @PluginAttribute(value = "ignoreExceptions", defaultBoolean = true) final boolean ignoreExceptions) {
130         if (name == null) {
131             LOGGER.error("No name provided for ConsoleAppender");
132             return null;
133         }
134         if (layout == null) {
135             layout = PatternLayout.createDefaultLayout();
136         }
137         target = target == null ? Target.SYSTEM_OUT : target;
138         return new ConsoleAppender(name, layout, filter, getManager(target, follow, layout), ignoreExceptions, target);
139     }
140 
141     public static ConsoleAppender createDefaultAppenderForLayout(final Layout<? extends Serializable> layout) {
142         // this method cannot use the builder class without introducing an infinite loop due to DefaultConfiguration
143         return new ConsoleAppender("DefaultConsole-" + COUNT.incrementAndGet(), layout, null,
144                 getDefaultManager(DEFAULT_TARGET, false, layout), true, DEFAULT_TARGET);
145     }
146 
147     @PluginBuilderFactory
148     public static Builder newBuilder() {
149         return new Builder();
150     }
151 
152     /**
153      * Builds ConsoleAppender instances.
154      */
155     public static class Builder implements org.apache.logging.log4j.core.util.Builder<ConsoleAppender> {
156 
157         @PluginElement("Layout")
158         @Required
159         private Layout<? extends Serializable> layout = PatternLayout.createDefaultLayout();
160 
161         @PluginElement("Filter")
162         private Filter filter;
163 
164         @PluginBuilderAttribute
165         @Required
166         private Target target = DEFAULT_TARGET;
167 
168         @PluginBuilderAttribute
169         @Required
170         private String name;
171 
172         @PluginBuilderAttribute
173         private boolean follow = false;
174 
175         @PluginBuilderAttribute
176         private boolean ignoreExceptions = true;
177 
178         public Builder setLayout(final Layout<? extends Serializable> aLayout) {
179             this.layout = aLayout;
180             return this;
181         }
182 
183         public Builder setFilter(final Filter aFilter) {
184             this.filter = aFilter;
185             return this;
186         }
187 
188         public Builder setTarget(final Target aTarget) {
189             this.target = aTarget;
190             return this;
191         }
192 
193         public Builder setName(final String aName) {
194             this.name = aName;
195             return this;
196         }
197 
198         public Builder setFollow(final boolean shouldFollow) {
199             this.follow = shouldFollow;
200             return this;
201         }
202 
203         public Builder setIgnoreExceptions(final boolean shouldIgnoreExceptions) {
204             this.ignoreExceptions = shouldIgnoreExceptions;
205             return this;
206         }
207 
208         @Override
209         public ConsoleAppender build() {
210             return new ConsoleAppender(name, layout, filter, getManager(target, follow, layout), ignoreExceptions, target);
211         }
212     }
213 
214     private static OutputStreamManager getDefaultManager(final Target target, final boolean follow,
215             final Layout<? extends Serializable> layout) {
216         final OutputStream os = getOutputStream(follow, target);
217 
218         // LOG4J2-1176 DefaultConfiguration should not share OutputStreamManager instances to avoid memory leaks.
219         final String managerName = target.name() + '.' + follow + "-" + COUNT.get();
220         return OutputStreamManager.getManager(managerName, new FactoryData(os, managerName, layout), factory);
221     }
222 
223     private static OutputStreamManager getManager(final Target target, final boolean follow,
224             final Layout<? extends Serializable> layout) {
225         final OutputStream os = getOutputStream(follow, target);
226         final String managerName = target.name() + '.' + follow;
227         return OutputStreamManager.getManager(managerName, new FactoryData(os, managerName, layout), factory);
228     }
229 
230     private static OutputStream getOutputStream(final boolean follow, final Target target) {
231         final String enc = Charset.defaultCharset().name();
232         OutputStream outputStream = null;
233         try {
234             // @formatter:off
235             outputStream = target == Target.SYSTEM_OUT ?
236                 follow ? new PrintStream(new SystemOutStream(), true, enc) : System.out :
237                 follow ? new PrintStream(new SystemErrStream(), true, enc) : System.err;
238             // @formatter:on
239             outputStream = new CloseShieldOutputStream(outputStream);
240         } catch (final UnsupportedEncodingException ex) { // should never happen
241             throw new IllegalStateException("Unsupported default encoding " + enc, ex);
242         }
243         final PropertiesUtil propsUtil = PropertiesUtil.getProperties();
244         if (!propsUtil.isOsWindows() || propsUtil.getBooleanProperty("log4j.skipJansi")) {
245             return outputStream;
246         }
247         try {
248             // We type the parameter as a wildcard to avoid a hard reference to Jansi.
249             final Class<?> clazz = Loader.loadClass(JANSI_CLASS);
250             final Constructor<?> constructor = clazz.getConstructor(OutputStream.class);
251             return new CloseShieldOutputStream((OutputStream) constructor.newInstance(outputStream));
252         } catch (final ClassNotFoundException cnfe) {
253             LOGGER.debug("Jansi is not installed, cannot find {}", JANSI_CLASS);
254         } catch (final NoSuchMethodException nsme) {
255             LOGGER.warn("{} is missing the proper constructor", JANSI_CLASS);
256         } catch (final Exception ex) {
257             LOGGER.warn("Unable to instantiate {}", JANSI_CLASS);
258         }
259         return outputStream;
260     }
261 
262     /**
263      * An implementation of OutputStream that redirects to the current System.err.
264      */
265     private static class SystemErrStream extends OutputStream {
266         public SystemErrStream() {
267         }
268 
269         @Override
270         public void close() {
271             // do not close sys err!
272         }
273 
274         @Override
275         public void flush() {
276             System.err.flush();
277         }
278 
279         @Override
280         public void write(final byte[] b) throws IOException {
281             System.err.write(b);
282         }
283 
284         @Override
285         public void write(final byte[] b, final int off, final int len) throws IOException {
286             System.err.write(b, off, len);
287         }
288 
289         @Override
290         public void write(final int b) {
291             System.err.write(b);
292         }
293     }
294 
295     /**
296      * An implementation of OutputStream that redirects to the current System.out.
297      */
298     private static class SystemOutStream extends OutputStream {
299         public SystemOutStream() {
300         }
301 
302         @Override
303         public void close() {
304             // do not close sys out!
305         }
306 
307         @Override
308         public void flush() {
309             System.out.flush();
310         }
311 
312         @Override
313         public void write(final byte[] b) throws IOException {
314             System.out.write(b);
315         }
316 
317         @Override
318         public void write(final byte[] b, final int off, final int len) throws IOException {
319             System.out.write(b, off, len);
320         }
321 
322         @Override
323         public void write(final int b) throws IOException {
324             System.out.write(b);
325         }
326     }
327 
328     /**
329      * Data to pass to factory method.
330      */
331     private static class FactoryData {
332         private final OutputStream os;
333         private final String name;
334         private final Layout<? extends Serializable> layout;
335 
336         /**
337          * Constructor.
338          * 
339          * @param os The OutputStream.
340          * @param type The name of the target.
341          * @param layout A Serializable layout
342          */
343         public FactoryData(final OutputStream os, final String type, final Layout<? extends Serializable> layout) {
344             this.os = os;
345             this.name = type;
346             this.layout = layout;
347         }
348     }
349 
350     /**
351      * Factory to create the Appender.
352      */
353     private static class ConsoleManagerFactory implements ManagerFactory<OutputStreamManager, FactoryData> {
354 
355         /**
356          * Create an OutputStreamManager.
357          * 
358          * @param name The name of the entity to manage.
359          * @param data The data required to create the entity.
360          * @return The OutputStreamManager
361          */
362         @Override
363         public OutputStreamManager createManager(final String name, final FactoryData data) {
364             return new OutputStreamManager(data.os, data.name, data.layout, true);
365         }
366     }
367 
368     public Target getTarget() {
369         return target;
370     }
371 
372 }