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