001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements. See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache license, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License. You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the license for the specific language governing permissions and
015     * limitations under the license.
016     */
017    package org.apache.logging.log4j.core.appender;
018    
019    import org.apache.logging.log4j.core.Filter;
020    import org.apache.logging.log4j.core.Layout;
021    import org.apache.logging.log4j.core.config.plugins.Plugin;
022    import org.apache.logging.log4j.core.config.plugins.PluginAttr;
023    import org.apache.logging.log4j.core.config.plugins.PluginElement;
024    import org.apache.logging.log4j.core.config.plugins.PluginFactory;
025    import org.apache.logging.log4j.core.helpers.Loader;
026    import org.apache.logging.log4j.core.layout.PatternLayout;
027    import org.apache.logging.log4j.util.PropertiesUtil;
028    
029    import java.io.IOException;
030    import java.io.OutputStream;
031    import java.io.PrintStream;
032    import java.io.Serializable;
033    import java.io.UnsupportedEncodingException;
034    import java.lang.reflect.Constructor;
035    import java.nio.charset.Charset;
036    
037    /**
038     * ConsoleAppender appends log events to <code>System.out</code> or
039     * <code>System.err</code> using a layout specified by the user. The
040     * default target is <code>System.out</code>.
041     * @doubt accessing System.out or .err as a byte stream instead of a writer
042     *    bypasses the JVM's knowledge of the proper encoding. (RG) Encoding
043     * is handled within the Layout. Typically, a Layout will generate a String
044     * and then call getBytes which may use a configured encoding or the system
045     * default. OTOH, a Writer cannot print byte streams.
046     */
047    @Plugin(name = "Console", category = "Core", elementType = "appender", printObject = true)
048    public final class ConsoleAppender<T extends Serializable> extends AbstractOutputStreamAppender<T> {
049    
050        private static ConsoleManagerFactory factory = new ConsoleManagerFactory();
051    
052        /**
053         * Enumeration of console destinations.
054         */
055        public enum Target {
056            /** Standard output. */
057            SYSTEM_OUT,
058            /** Standard error output. */
059            SYSTEM_ERR
060        }
061    
062        private ConsoleAppender(final String name, final Layout<T> layout, final Filter filter,
063                                final OutputStreamManager manager,
064                                final boolean handleExceptions) {
065            super(name, layout, filter, handleExceptions, true, manager);
066        }
067    
068        /**
069         * Create a Console Appender.
070         * @param layout The layout to use (required).
071         * @param filter The Filter or null.
072         * @param t The target ("SYSTEM_OUT" or "SYSTEM_ERR"). The default is "SYSTEM_OUT".
073         * @param follow If true will follow changes to the underlying output stream.
074         * @param name The name of the Appender (required).
075         * @param suppress "true" if exceptions should be hidden from the application, "false" otherwise.
076         * The default is "true".
077         * @return The ConsoleAppender.
078         */
079        @PluginFactory
080        public static <S extends Serializable> ConsoleAppender<S> createAppender(@PluginElement("layout") Layout<S> layout,
081                                                     @PluginElement("filters") final Filter filter,
082                                                     @PluginAttr("target") final String t,
083                                                     @PluginAttr("name") final String name,
084                                                     @PluginAttr("follow") final String follow,
085                                                     @PluginAttr("suppressExceptions") final String suppress) {
086            if (name == null) {
087                LOGGER.error("No name provided for ConsoleAppender");
088                return null;
089            }
090            if (layout == null) {
091                @SuppressWarnings({"unchecked"})
092                Layout<S> l = (Layout<S>)PatternLayout.createLayout(null, null, null, null, null);
093                layout = l;
094            }
095            final boolean isFollow = follow == null ? false : Boolean.valueOf(follow);
096            final boolean handleExceptions = suppress == null ? true : Boolean.valueOf(suppress);
097            final Target target = t == null ? Target.SYSTEM_OUT : Target.valueOf(t);
098            return new ConsoleAppender<S>(name, layout, filter, getManager(isFollow, target, layout), handleExceptions);
099        }
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    }