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.lang.reflect.Constructor;
034    
035    /**
036     * ConsoleAppender appends log events to <code>System.out</code> or
037     * <code>System.err</code> using a layout specified by the user. The
038     * default target is <code>System.out</code>.
039     * @doubt accessing System.out or .err as a byte stream instead of a writer
040     *    bypasses the JVM's knowledge of the proper encoding. (RG) Encoding
041     * is handled within the Layout. Typically, a Layout will generate a String
042     * and then call getBytes which may use a configured encoding or the system
043     * default. OTOH, a Writer cannot print byte streams.
044     */
045    @Plugin(name = "Console", category = "Core", elementType = "appender", printObject = true)
046    public final class ConsoleAppender<T extends Serializable> extends AbstractOutputStreamAppender<T> {
047    
048        private static ConsoleManagerFactory factory = new ConsoleManagerFactory();
049    
050        /**
051         * Enumeration of console destinations.
052         */
053        public enum Target {
054            /** Standard output. */
055            SYSTEM_OUT,
056            /** Standard error output. */
057            SYSTEM_ERR
058        }
059    
060        private ConsoleAppender(final String name, final Layout<T> layout, final Filter filter,
061                                final OutputStreamManager manager,
062                                final boolean handleExceptions) {
063            super(name, layout, filter, handleExceptions, true, manager);
064        }
065    
066        /**
067         * Create a Console Appender.
068         * @param layout The layout to use (required).
069         * @param filter The Filter or null.
070         * @param t The target ("SYSTEM_OUT" or "SYSTEM_ERR"). The default is "SYSTEM_OUT".
071         * @param follow If true will follow changes to the underlying output stream.
072         * @param name The name of the Appender (required).
073         * @param suppress "true" if exceptions should be hidden from the application, "false" otherwise.
074         * The default is "true".
075         * @return The ConsoleAppender.
076         */
077        @PluginFactory
078        public static <S extends Serializable> ConsoleAppender<S> createAppender(@PluginElement("layout") Layout<S> layout,
079                                                     @PluginElement("filters") final Filter filter,
080                                                     @PluginAttr("target") final String t,
081                                                     @PluginAttr("name") final String name,
082                                                     @PluginAttr("follow") final String follow,
083                                                     @PluginAttr("suppressExceptions") final String suppress) {
084            if (name == null) {
085                LOGGER.error("No name provided for ConsoleAppender");
086                return null;
087            }
088            if (layout == null) {
089                @SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"})
090                Layout<S> l = (Layout<S>)PatternLayout.createLayout(null, null, null, null);
091                layout = l;
092            }
093            final boolean isFollow = follow == null ? false : Boolean.valueOf(follow);
094            final boolean handleExceptions = suppress == null ? true : Boolean.valueOf(suppress);
095            final Target target = t == null ? Target.SYSTEM_OUT : Target.valueOf(t);
096            return new ConsoleAppender<S>(name, layout, filter, getManager(isFollow, target), handleExceptions);
097        }
098    
099        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    }