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