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.validation.constraints.Required;
35  import org.apache.logging.log4j.core.layout.PatternLayout;
36  import org.apache.logging.log4j.core.util.Booleans;
37  import org.apache.logging.log4j.core.util.Loader;
38  import org.apache.logging.log4j.util.PropertiesUtil;
39  
40  /**
41   * ConsoleAppender appends log events to <code>System.out</code> or
42   * <code>System.err</code> using a layout specified by the user. The
43   * default target is <code>System.out</code>.
44   * TODO accessing System.out or .err as a byte stream instead of a writer
45   *    bypasses the JVM's knowledge of the proper encoding. (RG) Encoding
46   * is handled within the Layout. Typically, a Layout will generate a String
47   * and then call getBytes which may use a configured encoding or the system
48   * default. OTOH, a Writer cannot print byte streams.
49   */
50  @Plugin(name = "Console", category = "Core", elementType = "appender", printObject = true)
51  public final class ConsoleAppender extends AbstractOutputStreamAppender<OutputStreamManager> {
52  
53      private static final long serialVersionUID = 1L;
54      private static final String JANSI_CLASS = "org.fusesource.jansi.WindowsAnsiOutputStream";
55      private static ConsoleManagerFactory factory = new ConsoleManagerFactory();
56  
57      /**
58       * Enumeration of console destinations.
59       */
60      public 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,
69                              final boolean ignoreExceptions) {
70          super(name, layout, filter, ignoreExceptions, true, manager);
71      }
72  
73      /**
74       * Create a Console Appender.
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
81       *               they are propagated to the caller.
82       * @return The ConsoleAppender.
83       */
84      public static ConsoleAppender createAppender(
85              @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 ? Target.SYSTEM_OUT : Target.valueOf(targetStr);
101         return new ConsoleAppender(name, layout, filter, getManager(isFollow, target, 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(false, Target.SYSTEM_OUT, layout), true);
107     }
108 
109     @PluginBuilderFactory
110     public static Builder newBuilder() {
111         return new Builder();
112     }
113 
114     public static class Builder implements org.apache.logging.log4j.core.util.Builder<ConsoleAppender> {
115 
116         @PluginElement("Layout")
117         @Required
118         private Layout<? extends Serializable> layout = PatternLayout.createDefaultLayout();
119 
120         @PluginElement("Filter")
121         private Filter filter;
122 
123         @PluginBuilderAttribute
124         @Required
125         private Target target = Target.SYSTEM_OUT;
126 
127         @PluginBuilderAttribute
128         @Required
129         private String name;
130 
131         @PluginBuilderAttribute
132         private boolean follow = false;
133 
134         @PluginBuilderAttribute
135         private boolean ignoreExceptions = true;
136 
137         public Builder setLayout(final Layout<? extends Serializable> layout) {
138             this.layout = layout;
139             return this;
140         }
141 
142         public Builder setFilter(final Filter filter) {
143             this.filter = filter;
144             return this;
145         }
146 
147         public Builder setTarget(final Target target) {
148             this.target = target;
149             return this;
150         }
151 
152         public Builder setName(final String name) {
153             this.name = name;
154             return this;
155         }
156 
157         public Builder setFollow(final boolean follow) {
158             this.follow = follow;
159             return this;
160         }
161 
162         public Builder setIgnoreExceptions(final boolean ignoreExceptions) {
163             this.ignoreExceptions = ignoreExceptions;
164             return this;
165         }
166 
167         @Override
168         public ConsoleAppender build() {
169             return new ConsoleAppender(name, layout, filter, getManager(follow, target, layout), ignoreExceptions);
170         }
171     }
172 
173     private static OutputStreamManager getManager(final boolean follow, final Target target, final Layout<? extends Serializable> layout) {
174         final String type = target.name();
175         final OutputStream os = getOutputStream(follow, target);
176         return OutputStreamManager.getManager(target.name() + '.' + follow, new FactoryData(os, type, layout), factory);
177     }
178 
179     private static OutputStream getOutputStream(final boolean follow, final Target target) {
180         final String enc = Charset.defaultCharset().name();
181         PrintStream printStream = null;
182         try {
183             printStream = target == Target.SYSTEM_OUT ?
184             follow ? new PrintStream(new SystemOutStream(), true, enc) : System.out :
185             follow ? new PrintStream(new SystemErrStream(), true, enc) : System.err;
186         } catch (final UnsupportedEncodingException ex) { // should never happen
187             throw new IllegalStateException("Unsupported default encoding " + enc, ex);
188         }
189         final PropertiesUtil propsUtil = PropertiesUtil.getProperties();
190         if (!propsUtil.getStringProperty("os.name").startsWith("Windows") ||
191             propsUtil.getBooleanProperty("log4j.skipJansi")) {
192             return printStream;
193         }
194         try {
195             // We type the parameter as a wildcard to avoid a hard reference to Jansi.
196             final Class<?> clazz = Loader.loadClass(JANSI_CLASS);
197             final Constructor<?> constructor = clazz.getConstructor(OutputStream.class);
198             return (OutputStream) constructor.newInstance(printStream);
199         } catch (final ClassNotFoundException cnfe) {
200             LOGGER.debug("Jansi is not installed, cannot find {}", JANSI_CLASS);
201         } catch (final NoSuchMethodException nsme) {
202             LOGGER.warn("{} is missing the proper constructor", JANSI_CLASS);
203         } catch (final Exception ex) {
204             LOGGER.warn("Unable to instantiate {}", JANSI_CLASS);
205         }
206         return printStream;
207     }
208 
209     /**
210      * An implementation of OutputStream that redirects to the current System.err.
211      */
212     private static class SystemErrStream extends OutputStream {
213         public SystemErrStream() {
214         }
215 
216         @Override
217         public void close() {
218             // do not close sys err!
219         }
220 
221         @Override
222         public void flush() {
223             System.err.flush();
224         }
225 
226         @Override
227         public void write(final byte[] b) throws IOException {
228             System.err.write(b);
229         }
230 
231         @Override
232         public void write(final byte[] b, final int off, final int len)
233             throws IOException {
234             System.err.write(b, off, len);
235         }
236 
237         @Override
238         public void write(final int b) {
239             System.err.write(b);
240         }
241     }
242 
243     /**
244      * An implementation of OutputStream that redirects to the current System.out.
245      */
246     private static class SystemOutStream extends OutputStream {
247         public SystemOutStream() {
248         }
249 
250         @Override
251         public void close() {
252             // do not close sys out!
253         }
254 
255         @Override
256         public void flush() {
257             System.out.flush();
258         }
259 
260         @Override
261         public void write(final byte[] b) throws IOException {
262             System.out.write(b);
263         }
264 
265         @Override
266         public void write(final byte[] b, final int off, final int len)
267             throws IOException {
268             System.out.write(b, off, len);
269         }
270 
271         @Override
272         public void write(final int b) throws IOException {
273             System.out.write(b);
274         }
275     }
276 
277     /**
278      * Data to pass to factory method.
279      */
280     private static class FactoryData {
281         private final OutputStream os;
282         private final String type;
283         private final Layout<? extends Serializable> layout;
284 
285         /**
286          * Constructor.
287          * @param os The OutputStream.
288          * @param type The name of the target.
289          * @param layout A Serializable layout
290          */
291         public FactoryData(final OutputStream os, final String type, final Layout<? extends Serializable> layout) {
292             this.os = os;
293             this.type = type;
294             this.layout = layout;
295         }
296     }
297 
298     /**
299      * Factory to create the Appender.
300      */
301     private static class ConsoleManagerFactory implements ManagerFactory<OutputStreamManager, FactoryData> {
302 
303         /**
304          * Create an OutputStreamManager.
305          * @param name The name of the entity to manage.
306          * @param data The data required to create the entity.
307          * @return The OutputStreamManager
308          */
309         @Override
310         public OutputStreamManager createManager(final String name, final FactoryData data) {
311             return new OutputStreamManager(data.os, data.type, data.layout);
312         }
313     }
314 
315 }