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