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 org.apache.logging.log4j.core.Filter;
20  import org.apache.logging.log4j.core.Layout;
21  import org.apache.logging.log4j.core.config.plugins.Plugin;
22  import org.apache.logging.log4j.core.config.plugins.PluginAttr;
23  import org.apache.logging.log4j.core.config.plugins.PluginElement;
24  import org.apache.logging.log4j.core.config.plugins.PluginFactory;
25  import org.apache.logging.log4j.core.helpers.Loader;
26  import org.apache.logging.log4j.core.layout.PatternLayout;
27  import org.apache.logging.log4j.util.PropertiesUtil;
28  
29  import java.io.IOException;
30  import java.io.OutputStream;
31  import java.io.PrintStream;
32  import java.io.Serializable;
33  import java.io.UnsupportedEncodingException;
34  import java.lang.reflect.Constructor;
35  import java.nio.charset.Charset;
36  
37  /**
38   * ConsoleAppender appends log events to <code>System.out</code> or
39   * <code>System.err</code> using a layout specified by the user. The
40   * default target is <code>System.out</code>.
41   * @doubt accessing System.out or .err as a byte stream instead of a writer
42   *    bypasses the JVM's knowledge of the proper encoding. (RG) Encoding
43   * is handled within the Layout. Typically, a Layout will generate a String
44   * and then call getBytes which may use a configured encoding or the system
45   * default. OTOH, a Writer cannot print byte streams.
46   */
47  @Plugin(name = "Console", category = "Core", elementType = "appender", printObject = true)
48  public final class ConsoleAppender<T extends Serializable> extends AbstractOutputStreamAppender<T> {
49  
50      private static ConsoleManagerFactory factory = new ConsoleManagerFactory();
51  
52      /**
53       * Enumeration of console destinations.
54       */
55      public enum Target {
56          /** Standard output. */
57          SYSTEM_OUT,
58          /** Standard error output. */
59          SYSTEM_ERR
60      }
61  
62      private ConsoleAppender(final String name, final Layout<T> layout, final Filter filter,
63                              final OutputStreamManager manager,
64                              final boolean handleExceptions) {
65          super(name, layout, filter, handleExceptions, true, manager);
66      }
67  
68      /**
69       * Create a Console Appender.
70       * @param layout The layout to use (required).
71       * @param filter The Filter or null.
72       * @param t The target ("SYSTEM_OUT" or "SYSTEM_ERR"). The default is "SYSTEM_OUT".
73       * @param follow If true will follow changes to the underlying output stream.
74       * @param name The name of the Appender (required).
75       * @param suppress "true" if exceptions should be hidden from the application, "false" otherwise.
76       * The default is "true".
77       * @return The ConsoleAppender.
78       */
79      @PluginFactory
80      public static <S extends Serializable> ConsoleAppender<S> createAppender(@PluginElement("layout") Layout<S> layout,
81                                                   @PluginElement("filters") final Filter filter,
82                                                   @PluginAttr("target") final String t,
83                                                   @PluginAttr("name") final String name,
84                                                   @PluginAttr("follow") final String follow,
85                                                   @PluginAttr("suppressExceptions") final String suppress) {
86          if (name == null) {
87              LOGGER.error("No name provided for ConsoleAppender");
88              return null;
89          }
90          if (layout == null) {
91              @SuppressWarnings({"unchecked"})
92              Layout<S> l = (Layout<S>)PatternLayout.createLayout(null, null, null, null, null);
93              layout = l;
94          }
95          final boolean isFollow = follow == null ? false : Boolean.valueOf(follow);
96          final boolean handleExceptions = suppress == null ? true : Boolean.valueOf(suppress);
97          final Target target = t == null ? Target.SYSTEM_OUT : Target.valueOf(t);
98          return new ConsoleAppender<S>(name, layout, filter, getManager(isFollow, target, layout), handleExceptions);
99      }
100 
101     private static OutputStreamManager getManager(final boolean follow, final Target target, final Layout layout) {
102         final String type = target.name();
103         final OutputStream os = getOutputStream(follow, target);
104         return OutputStreamManager.getManager(target.name() + "." + follow, new FactoryData(os, type, layout), factory);
105     }
106 
107     private static OutputStream getOutputStream(final boolean follow, final Target target) {
108         final String enc = Charset.defaultCharset().name();
109         PrintStream printStream = null;
110         try {
111             printStream = target == Target.SYSTEM_OUT ?
112             follow ? new PrintStream(new SystemOutStream(), true, enc) : System.out :
113             follow ? new PrintStream(new SystemErrStream(), true, enc) : System.err;
114         } catch (UnsupportedEncodingException ex) { // should never happen
115             throw new IllegalStateException("Unsupported default encoding " + enc, ex);
116         }
117         PropertiesUtil propsUtil = PropertiesUtil.getProperties();
118         if (!propsUtil.getStringProperty("os.name").startsWith("Windows") ||
119             propsUtil.getBooleanProperty("log4j.skipJansi")) {
120             return printStream;
121         } else {
122             try {
123                 final ClassLoader loader = Loader.getClassLoader();
124                 // We type the parameter as a wildcard to avoid a hard reference to Jansi.
125                 final Class<?> clazz = loader.loadClass("org.fusesource.jansi.WindowsAnsiOutputStream");
126                 final Constructor<?> constructor = clazz.getConstructor(OutputStream.class);
127                 return (OutputStream) constructor.newInstance(printStream);
128             } catch (final ClassNotFoundException cnfe) {
129                 LOGGER.debug("Jansi is not installed");
130             } catch (final NoSuchMethodException nsme) {
131                 LOGGER.warn("WindowsAnsiOutputStream is missing the proper constructor");
132             } catch (final Exception ex) {
133                 LOGGER.warn("Unable to instantiate WindowsAnsiOutputStream");
134             }
135             return printStream;
136         }
137     }
138 
139     /**
140      * An implementation of OutputStream that redirects to the
141      * current System.err.
142      *
143      */
144     private static class SystemErrStream extends OutputStream {
145         public SystemErrStream() {
146         }
147 
148         @Override
149         public void close() {
150         }
151 
152         @Override
153         public void flush() {
154             System.err.flush();
155         }
156 
157         @Override
158         public void write(final byte[] b) throws IOException {
159             System.err.write(b);
160         }
161 
162         @Override
163         public void write(final byte[] b, final int off, final int len)
164             throws IOException {
165             System.err.write(b, off, len);
166         }
167 
168         @Override
169         public void write(final int b) {
170             System.err.write(b);
171         }
172     }
173 
174     /**
175      * An implementation of OutputStream that redirects to the
176      * current System.out.
177      *
178      */
179     private static class SystemOutStream extends OutputStream {
180         public SystemOutStream() {
181         }
182 
183         @Override
184         public void close() {
185         }
186 
187         @Override
188         public void flush() {
189             System.out.flush();
190         }
191 
192         @Override
193         public void write(final byte[] b) throws IOException {
194             System.out.write(b);
195         }
196 
197         @Override
198         public void write(final byte[] b, final int off, final int len)
199             throws IOException {
200             System.out.write(b, off, len);
201         }
202 
203         @Override
204         public void write(final int b) throws IOException {
205             System.out.write(b);
206         }
207     }
208 
209     /**
210      * Data to pass to factory method.
211      */
212     private static class FactoryData {
213         private final OutputStream os;
214         private final String type;
215         private final Layout layout;
216 
217         /**
218          * Constructor.
219          * @param os The OutputStream.
220          * @param type The name of the target.
221          */
222         public FactoryData(final OutputStream os, final String type, final Layout layout) {
223             this.os = os;
224             this.type = type;
225             this.layout = layout;
226         }
227     }
228 
229     /**
230      * Factory to create the Appender.
231      */
232     private static class ConsoleManagerFactory implements ManagerFactory<OutputStreamManager, FactoryData> {
233 
234         /**
235          * Create an OutputStreamManager.
236          * @param name The name of the entity to manage.
237          * @param data The data required to create the entity.
238          * @return The OutputStreamManager
239          */
240         @Override
241         public OutputStreamManager createManager(final String name, final FactoryData data) {
242             return new OutputStreamManager(data.os, data.type, data.layout);
243         }
244     }
245 
246 }