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.lang.reflect.Constructor;
033    
034    /**
035     * ConsoleAppender appends log events to <code>System.out</code> or
036     * <code>System.err</code> using a layout specified by the user. The
037     * default target is <code>System.out</code>.
038     * @doubt accessing System.out or .err as a byte stream instead of a writer
039     *    bypasses the JVM's knowledge of the proper encoding. (RG) Encoding
040     * is handled within the Layout. Typically, a Layout will generate a String
041     * and then call getBytes which may use a configured encoding or the system
042     * default. OTOH, a Writer cannot print byte streams.
043     */
044    @Plugin(name = "Console", type = "Core", elementType = "appender", printObject = true)
045    public final class ConsoleAppender extends AbstractOutputStreamAppender {
046    
047        private static ConsoleManagerFactory factory = new ConsoleManagerFactory();
048    
049        /**
050         * Enumeration of console destinations.
051         */
052        public enum Target {
053            /** Standard output. */
054            SYSTEM_OUT,
055            /** Standard error output. */
056            SYSTEM_ERR
057        }
058    
059        private ConsoleAppender(final String name, final Layout layout, final Filter filter,
060                                final OutputStreamManager manager,
061                                final boolean handleExceptions) {
062            super(name, layout, filter, handleExceptions, true, manager);
063        }
064    
065        /**
066         * Create a Console Appender.
067         * @param layout The layout to use (required).
068         * @param filter The Filter or null.
069         * @param t The target ("SYSTEM_OUT" or "SYSTEM_ERR"). The default is "SYSTEM_OUT".
070         * @param follow If true will follow changes to the underlying output stream.
071         * @param name The name of the Appender (required).
072         * @param suppress "true" if exceptions should be hidden from the application, "false" otherwise.
073         * The default is "true".
074         * @return The ConsoleAppender.
075         */
076        @PluginFactory
077        public static ConsoleAppender createAppender(@PluginElement("layout") Layout layout,
078                                                     @PluginElement("filters") final Filter filter,
079                                                     @PluginAttr("target") final String t,
080                                                     @PluginAttr("name") final String name,
081                                                     @PluginAttr("follow") final String follow,
082                                                     @PluginAttr("suppressExceptions") final String suppress) {
083            if (name == null) {
084                LOGGER.error("No name provided for ConsoleAppender");
085                return null;
086            }
087            if (layout == null) {
088                layout = PatternLayout.createLayout(null, null, null, null);
089            }
090            final boolean isFollow = follow == null ? false : Boolean.valueOf(follow);
091            final boolean handleExceptions = suppress == null ? true : Boolean.valueOf(suppress);
092            final Target target = t == null ? Target.SYSTEM_OUT : Target.valueOf(t);
093            return new ConsoleAppender(name, layout, filter, getManager(isFollow, target), handleExceptions);
094        }
095    
096        private static OutputStreamManager getManager(final boolean follow, final Target target) {
097            final String type = target.name();
098            final OutputStream os = getOutputStream(follow, target);
099            return OutputStreamManager.getManager(target.name() + "." + follow, new FactoryData(os, type), factory);
100        }
101    
102        private static OutputStream getOutputStream(final boolean follow, final Target target) {
103            final PrintStream printStream = target == Target.SYSTEM_OUT ?
104                follow ? new PrintStream(new SystemOutStream()) : System.out :
105                follow ? new PrintStream(new SystemErrStream()) : System.err;
106            PropertiesUtil propsUtil = PropertiesUtil.getProperties();
107            if (!propsUtil.getStringProperty("os.name").startsWith("Windows") ||
108                propsUtil.getBooleanProperty("log4j.skipJansi")) {
109                return printStream;
110            } else {
111                try {
112                    final ClassLoader loader = Loader.getClassLoader();
113                    // We type the parameter as a wildcard to avoid a hard reference to Jansi.
114                    final Class<?> clazz = loader.loadClass("org.fusesource.jansi.WindowsAnsiOutputStream");
115                    final Constructor<?> constructor = clazz.getConstructor(OutputStream.class);
116                    return (OutputStream) constructor.newInstance(printStream);
117                } catch (final ClassNotFoundException cnfe) {
118                    LOGGER.debug("Jansi is not installed");
119                } catch (final NoSuchMethodException nsme) {
120                    LOGGER.warn("WindowsAnsiOutputStream is missing the proper constructor");
121                } catch (final Exception ex) {
122                    LOGGER.warn("Unable to instantiate WindowsAnsiOutputStream");
123                }
124                return printStream;
125            }
126        }
127    
128        /**
129         * An implementation of OutputStream that redirects to the
130         * current System.err.
131         *
132         */
133        private static class SystemErrStream extends OutputStream {
134            public SystemErrStream() {
135            }
136    
137            @Override
138            public void close() {
139            }
140    
141            @Override
142            public void flush() {
143                System.err.flush();
144            }
145    
146            @Override
147            public void write(final byte[] b) throws IOException {
148                System.err.write(b);
149            }
150    
151            @Override
152            public void write(final byte[] b, final int off, final int len)
153                throws IOException {
154                System.err.write(b, off, len);
155            }
156    
157            @Override
158            public void write(final int b) {
159                System.err.write(b);
160            }
161        }
162    
163        /**
164         * An implementation of OutputStream that redirects to the
165         * current System.out.
166         *
167         */
168        private static class SystemOutStream extends OutputStream {
169            public SystemOutStream() {
170            }
171    
172            @Override
173            public void close() {
174            }
175    
176            @Override
177            public void flush() {
178                System.out.flush();
179            }
180    
181            @Override
182            public void write(final byte[] b) throws IOException {
183                System.out.write(b);
184            }
185    
186            @Override
187            public void write(final byte[] b, final int off, final int len)
188                throws IOException {
189                System.out.write(b, off, len);
190            }
191    
192            @Override
193            public void write(final int b) throws IOException {
194                System.out.write(b);
195            }
196        }
197    
198        /**
199         * Data to pass to factory method.
200         */
201        private static class FactoryData {
202            private final OutputStream os;
203            private final String type;
204    
205            /**
206             * Constructor.
207             * @param os The OutputStream.
208             * @param type The name of the target.
209             */
210            public FactoryData(final OutputStream os, final String type) {
211                this.os = os;
212                this.type = type;
213            }
214        }
215    
216        /**
217         * Factory to create the Appender.
218         */
219        private static class ConsoleManagerFactory implements ManagerFactory<OutputStreamManager, FactoryData> {
220    
221            /**
222             * Create an OutputStreamManager.
223             * @param name The name of the entity to manage.
224             * @param data The data required to create the entity.
225             * @return The OutputStreamManager
226             */
227            public OutputStreamManager createManager(final String name, final FactoryData data) {
228                return new OutputStreamManager(data.os, data.type);
229            }
230        }
231    
232    }