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 java.io.IOException;
020    import java.io.OutputStream;
021    import java.io.PrintStream;
022    import java.io.Serializable;
023    import java.io.UnsupportedEncodingException;
024    import java.lang.reflect.Constructor;
025    import java.nio.charset.Charset;
026    
027    import org.apache.logging.log4j.core.Filter;
028    import org.apache.logging.log4j.core.Layout;
029    import org.apache.logging.log4j.core.config.plugins.Plugin;
030    import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
031    import org.apache.logging.log4j.core.config.plugins.PluginElement;
032    import org.apache.logging.log4j.core.config.plugins.PluginFactory;
033    import org.apache.logging.log4j.core.layout.PatternLayout;
034    import org.apache.logging.log4j.core.util.Booleans;
035    import org.apache.logging.log4j.core.util.Loader;
036    import org.apache.logging.log4j.util.PropertiesUtil;
037    
038    /**
039     * ConsoleAppender appends log events to <code>System.out</code> or
040     * <code>System.err</code> using a layout specified by the user. The
041     * default target is <code>System.out</code>.
042     * @doubt accessing System.out or .err as a byte stream instead of a writer
043     *    bypasses the JVM's knowledge of the proper encoding. (RG) Encoding
044     * is handled within the Layout. Typically, a Layout will generate a String
045     * and then call getBytes which may use a configured encoding or the system
046     * default. OTOH, a Writer cannot print byte streams.
047     */
048    @Plugin(name = "Console", category = "Core", elementType = "appender", printObject = true)
049    public final class ConsoleAppender extends AbstractOutputStreamAppender<OutputStreamManager> {
050    
051        private static final String JANSI_CLASS = "org.fusesource.jansi.WindowsAnsiOutputStream";
052        private static ConsoleManagerFactory factory = new ConsoleManagerFactory();
053    
054        /**
055         * Enumeration of console destinations.
056         */
057        public enum Target {
058            /** Standard output. */
059            SYSTEM_OUT,
060            /** Standard error output. */
061            SYSTEM_ERR
062        }
063    
064        private ConsoleAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
065                                final OutputStreamManager manager,
066                                final boolean ignoreExceptions) {
067            super(name, layout, filter, ignoreExceptions, true, manager);
068        }
069    
070        /**
071         * Create a Console Appender.
072         * @param layout The layout to use (required).
073         * @param filter The Filter or null.
074         * @param targetStr The target ("SYSTEM_OUT" or "SYSTEM_ERR"). The default is "SYSTEM_OUT".
075         * @param follow If true will follow changes to the underlying output stream.
076         * @param name The name of the Appender (required).
077         * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise
078         *               they are propagated to the caller.
079         * @return The ConsoleAppender.
080         */
081        @PluginFactory
082        public static ConsoleAppender createAppender(
083                @PluginElement("Layout") Layout<? extends Serializable> layout,
084                @PluginElement("Filter") final Filter filter,
085                @PluginAttribute(value = "target", defaultString = "SYSTEM_OUT") final String targetStr,
086                @PluginAttribute("name") final String name,
087                @PluginAttribute(value = "follow", defaultBoolean = false) final String follow,
088                @PluginAttribute(value = "ignoreExceptions", defaultBoolean = true) final String ignore) {
089            if (name == null) {
090                LOGGER.error("No name provided for ConsoleAppender");
091                return null;
092            }
093            if (layout == null) {
094                layout = PatternLayout.createDefaultLayout();
095            }
096            final boolean isFollow = Boolean.parseBoolean(follow);
097            final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
098            final Target target = targetStr == null ? Target.SYSTEM_OUT : Target.valueOf(targetStr);
099            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                final ClassLoader loader = Loader.getClassLoader();
125                // We type the parameter as a wildcard to avoid a hard reference to Jansi.
126                final Class<?> clazz = loader.loadClass(JANSI_CLASS);
127                final Constructor<?> constructor = clazz.getConstructor(OutputStream.class);
128                return (OutputStream) constructor.newInstance(printStream);
129            } catch (final ClassNotFoundException cnfe) {
130                LOGGER.debug("Jansi is not installed, cannot find {}", JANSI_CLASS);
131            } catch (final NoSuchMethodException nsme) {
132                LOGGER.warn("{} is missing the proper constructor", JANSI_CLASS);
133            } catch (final Exception ex) {
134                LOGGER.warn("Unable to instantiate {}", JANSI_CLASS);
135            }
136            return printStream;
137        }
138    
139        /**
140         * An implementation of OutputStream that redirects to the current System.err.
141         */
142        private static class SystemErrStream extends OutputStream {
143            public SystemErrStream() {
144            }
145    
146            @Override
147            public void close() {
148                // do not close sys err!
149            }
150    
151            @Override
152            public void flush() {
153                System.err.flush();
154            }
155    
156            @Override
157            public void write(final byte[] b) throws IOException {
158                System.err.write(b);
159            }
160    
161            @Override
162            public void write(final byte[] b, final int off, final int len)
163                throws IOException {
164                System.err.write(b, off, len);
165            }
166    
167            @Override
168            public void write(final int b) {
169                System.err.write(b);
170            }
171        }
172    
173        /**
174         * An implementation of OutputStream that redirects to the current System.out.
175         */
176        private static class SystemOutStream extends OutputStream {
177            public SystemOutStream() {
178            }
179    
180            @Override
181            public void close() {
182                // do not close sys out!
183            }
184    
185            @Override
186            public void flush() {
187                System.out.flush();
188            }
189    
190            @Override
191            public void write(final byte[] b) throws IOException {
192                System.out.write(b);
193            }
194    
195            @Override
196            public void write(final byte[] b, final int off, final int len)
197                throws IOException {
198                System.out.write(b, off, len);
199            }
200    
201            @Override
202            public void write(final int b) throws IOException {
203                System.out.write(b);
204            }
205        }
206    
207        /**
208         * Data to pass to factory method.
209         */
210        private static class FactoryData {
211            private final OutputStream os;
212            private final String type;
213            private final Layout<? extends Serializable> layout;
214    
215            /**
216             * Constructor.
217             * @param os The OutputStream.
218             * @param type The name of the target.
219             * @param layout A Serializable layout
220             */
221            public FactoryData(final OutputStream os, final String type, final Layout<? extends Serializable> layout) {
222                this.os = os;
223                this.type = type;
224                this.layout = layout;
225            }
226        }
227    
228        /**
229         * Factory to create the Appender.
230         */
231        private static class ConsoleManagerFactory implements ManagerFactory<OutputStreamManager, FactoryData> {
232    
233            /**
234             * Create an OutputStreamManager.
235             * @param name The name of the entity to manage.
236             * @param data The data required to create the entity.
237             * @return The OutputStreamManager
238             */
239            @Override
240            public OutputStreamManager createManager(final String name, final FactoryData data) {
241                return new OutputStreamManager(data.os, data.type, data.layout);
242            }
243        }
244    
245    }