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