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 */
017package org.apache.logging.log4j.core.appender;
018
019import java.io.IOException;
020import java.io.OutputStream;
021import java.io.PrintStream;
022import java.io.Serializable;
023import java.io.UnsupportedEncodingException;
024import java.lang.reflect.Constructor;
025import java.nio.charset.Charset;
026
027import org.apache.logging.log4j.core.Filter;
028import org.apache.logging.log4j.core.Layout;
029import org.apache.logging.log4j.core.config.plugins.Plugin;
030import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
031import org.apache.logging.log4j.core.config.plugins.PluginElement;
032import org.apache.logging.log4j.core.config.plugins.PluginFactory;
033import org.apache.logging.log4j.core.layout.PatternLayout;
034import org.apache.logging.log4j.core.util.Booleans;
035import org.apache.logging.log4j.core.util.Loader;
036import 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)
049public 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            // We type the parameter as a wildcard to avoid a hard reference to Jansi.
125            final Class<?> clazz = Loader.loadClass(JANSI_CLASS);
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, cannot find {}", JANSI_CLASS);
130        } catch (final NoSuchMethodException nsme) {
131            LOGGER.warn("{} is missing the proper constructor", JANSI_CLASS);
132        } catch (final Exception ex) {
133            LOGGER.warn("Unable to instantiate {}", JANSI_CLASS);
134        }
135        return printStream;
136    }
137
138    /**
139     * An implementation of OutputStream that redirects to the current System.err.
140     */
141    private static class SystemErrStream extends OutputStream {
142        public SystemErrStream() {
143        }
144
145        @Override
146        public void close() {
147            // do not close sys err!
148        }
149
150        @Override
151        public void flush() {
152            System.err.flush();
153        }
154
155        @Override
156        public void write(final byte[] b) throws IOException {
157            System.err.write(b);
158        }
159
160        @Override
161        public void write(final byte[] b, final int off, final int len)
162            throws IOException {
163            System.err.write(b, off, len);
164        }
165
166        @Override
167        public void write(final int b) {
168            System.err.write(b);
169        }
170    }
171
172    /**
173     * An implementation of OutputStream that redirects to the current System.out.
174     */
175    private static class SystemOutStream extends OutputStream {
176        public SystemOutStream() {
177        }
178
179        @Override
180        public void close() {
181            // do not close sys out!
182        }
183
184        @Override
185        public void flush() {
186            System.out.flush();
187        }
188
189        @Override
190        public void write(final byte[] b) throws IOException {
191            System.out.write(b);
192        }
193
194        @Override
195        public void write(final byte[] b, final int off, final int len)
196            throws IOException {
197            System.out.write(b, off, len);
198        }
199
200        @Override
201        public void write(final int b) throws IOException {
202            System.out.write(b);
203        }
204    }
205
206    /**
207     * Data to pass to factory method.
208     */
209    private static class FactoryData {
210        private final OutputStream os;
211        private final String type;
212        private final Layout<? extends Serializable> layout;
213
214        /**
215         * Constructor.
216         * @param os The OutputStream.
217         * @param type The name of the target.
218         * @param layout A Serializable layout
219         */
220        public FactoryData(final OutputStream os, final String type, final Layout<? extends Serializable> layout) {
221            this.os = os;
222            this.type = type;
223            this.layout = layout;
224        }
225    }
226
227    /**
228     * Factory to create the Appender.
229     */
230    private static class ConsoleManagerFactory implements ManagerFactory<OutputStreamManager, FactoryData> {
231
232        /**
233         * Create an OutputStreamManager.
234         * @param name The name of the entity to manage.
235         * @param data The data required to create the entity.
236         * @return The OutputStreamManager
237         */
238        @Override
239        public OutputStreamManager createManager(final String name, final FactoryData data) {
240            return new OutputStreamManager(data.os, data.type, data.layout);
241        }
242    }
243
244}