    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.PluginBuilderAttribute;
    032    import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
    033    import org.apache.logging.log4j.core.config.plugins.PluginElement;
    034    import org.apache.logging.log4j.core.config.plugins.PluginFactory;
    035    import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
    036    import org.apache.logging.log4j.core.layout.PatternLayout;
    037    import org.apache.logging.log4j.core.util.Booleans;
    038    import org.apache.logging.log4j.core.util.Loader;
    039    import org.apache.logging.log4j.util.PropertiesUtil;
    040
    041    /**
    042     * ConsoleAppender appends log events to <code>System.out</code> or
    043     * <code>System.err</code> using a layout specified by the user. The
    044     * default target is <code>System.out</code>.
    045     * TODO accessing System.out or .err as a byte stream instead of a writer
    046     *    bypasses the JVM's knowledge of the proper encoding. (RG) Encoding
    047     * is handled within the Layout. Typically, a Layout will generate a String
    048     * and then call getBytes which may use a configured encoding or the system
    049     * default. OTOH, a Writer cannot print byte streams.
    050     */
    051    @Plugin(name = "Console", category = "Core", elementType = "appender", printObject = true)
    052    public final class ConsoleAppender extends AbstractOutputStreamAppender<OutputStreamManager> {
    053
    054        private static final long serialVersionUID = 1L;
    055        private static final String JANSI_CLASS = "org.fusesource.jansi.WindowsAnsiOutputStream";
    056        private static ConsoleManagerFactory factory = new ConsoleManagerFactory();
    057        private static final Target DEFAULT_TARGET = Target.SYSTEM_OUT;
    058
    059        /**
    060         * Enumeration of console destinations.
    061         */
    062        public enum Target {
    063            /** Standard output. */
    064            SYSTEM_OUT,
    065            /** Standard error output. */
    066            SYSTEM_ERR
    067        }
    068
    069        private ConsoleAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
    070                                final OutputStreamManager manager,
    071                                final boolean ignoreExceptions) {
    072            super(name, layout, filter, ignoreExceptions, true, manager);
    073        }
    074
    075        /**
    076         * Create a Console Appender.
    077         * @param layout The layout to use (required).
    078         * @param filter The Filter or null.
    079         * @param targetStr The target ("SYSTEM_OUT" or "SYSTEM_ERR"). The default is "SYSTEM_OUT".
    080         * @param follow If true will follow changes to the underlying output stream.
    081         * @param name The name of the Appender (required).
    082         * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise
    083         *               they are propagated to the caller.
    084         * @return The ConsoleAppender.
    085         */
    086        @PluginFactory
    087        public static ConsoleAppender createAppender(
    088                @PluginElement("Layout") Layout<? extends Serializable> layout,
    089                @PluginElement("Filter") final Filter filter,
    090                @PluginAttribute(value = "target", defaultString = "SYSTEM_OUT") final String targetStr,
    091                @PluginAttribute("name") final String name,
    092                @PluginAttribute(value = "follow", defaultBoolean = false) final String follow,
    093                @PluginAttribute(value = "ignoreExceptions", defaultBoolean = true) final String ignore) {
    094            if (name == null) {
    095                LOGGER.error("No name provided for ConsoleAppender");
    096                return null;
    097            }
    098            if (layout == null) {
    099                layout = PatternLayout.createDefaultLayout();
    100            }
    101            final boolean isFollow = Boolean.parseBoolean(follow);
    102            final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
    103            final Target target = targetStr == null ? DEFAULT_TARGET : Target.valueOf(targetStr);
    104            return new ConsoleAppender(name, layout, filter, getManager(isFollow, target, layout), ignoreExceptions);
    105        }
    106
    107        public static ConsoleAppender createDefaultAppenderForLayout(final Layout<? extends Serializable> layout) {
    108            // this method cannot use the builder class without introducing an infinite loop due to DefaultConfiguration
    109            return new ConsoleAppender("Console", layout, null, getManager(false, DEFAULT_TARGET, layout), true);
    110        }
    111
    112        @PluginBuilderFactory
    113        public static Builder newBuilder() {
    114            return new Builder();
    115        }
    116
    117        public static class Builder implements org.apache.logging.log4j.core.util.Builder<ConsoleAppender> {
    118
    119            @PluginElement("Layout")
    120            @Required
    121            private Layout<? extends Serializable> layout = PatternLayout.createDefaultLayout();
    122
    123            @PluginElement("Filter")
    124            private Filter filter;
    125
    126            @PluginBuilderAttribute
    127            @Required
    128            private Target target = DEFAULT_TARGET;
    129
    130            @PluginBuilderAttribute
    131            @Required
    132            private String name;
    133
    134            @PluginBuilderAttribute
    135            private boolean follow = false;
    136
    137            @PluginBuilderAttribute
    138            private boolean ignoreExceptions = true;
    139
    140            public Builder setLayout(final Layout<? extends Serializable> layout) {
    141                this.layout = layout;
    142                return this;
    143            }
    144
    145            public Builder setFilter(final Filter filter) {
    146                this.filter = filter;
    147                return this;
    148            }
    149
    150            public Builder setTarget(final Target target) {
    151                this.target = target;
    152                return this;
    153            }
    154
    155            public Builder setName(final String name) {
    156                this.name = name;
    157                return this;
    158            }
    159
    160            public Builder setFollow(final boolean follow) {
    161                this.follow = follow;
    162                return this;
    163            }
    164
    165            public Builder setIgnoreExceptions(final boolean ignoreExceptions) {
    166                this.ignoreExceptions = ignoreExceptions;
    167                return this;
    168            }
    169
    170            @Override
    171            public ConsoleAppender build() {
    172                return new ConsoleAppender(name, layout, filter, getManager(follow, target, layout), ignoreExceptions);
    173            }
    174        }
    175
    176        private static OutputStreamManager getManager(final boolean follow, final Target target, final Layout<? extends Serializable> layout) {
    177            final String type = target.name();
    178            final OutputStream os = getOutputStream(follow, target);
    179            return OutputStreamManager.getManager(target.name() + '.' + follow, new FactoryData(os, type, layout), factory);
    180        }
    181
    182        private static OutputStream getOutputStream(final boolean follow, final Target target) {
    183            final String enc = Charset.defaultCharset().name();
    184            OutputStream outputStream = null;
    185            try {
    186                // @formatter:off
    187                outputStream = target == Target.SYSTEM_OUT ?
    188                    follow ? new PrintStream(new SystemOutStream(), true, enc) : System.out :
    189                    follow ? new PrintStream(new SystemErrStream(), true, enc) : System.err;
    190                // @formatter:on
    191                outputStream = new CloseShieldOutputStream(outputStream);
    192            } catch (final UnsupportedEncodingException ex) { // should never happen
    193                throw new IllegalStateException("Unsupported default encoding " + enc, ex);
    194            }
    195            final PropertiesUtil propsUtil = PropertiesUtil.getProperties();
    196            if (!propsUtil.getStringProperty("os.name").startsWith("Windows")
    197                    || propsUtil.getBooleanProperty("log4j.skipJansi")) {
    198                return outputStream;
    199            }
    200            try {
    201                // We type the parameter as a wildcard to avoid a hard reference to Jansi.
    202                final Class<?> clazz = Loader.loadClass(JANSI_CLASS);
    203                final Constructor<?> constructor = clazz.getConstructor(OutputStream.class);
    204                return new CloseShieldOutputStream((OutputStream) constructor.newInstance(outputStream));
    205            } catch (final ClassNotFoundException cnfe) {
    206                LOGGER.debug("Jansi is not installed, cannot find {}", JANSI_CLASS);
    207            } catch (final NoSuchMethodException nsme) {
    208                LOGGER.warn("{} is missing the proper constructor", JANSI_CLASS);
    209            } catch (final Exception ex) {
    210                LOGGER.warn("Unable to instantiate {}", JANSI_CLASS);
    211            }
    212            return outputStream;
    213        }
    214
    215        /**
    216         * An implementation of OutputStream that redirects to the current System.err.
    217         */
    218        private static class SystemErrStream extends OutputStream {
    219            public SystemErrStream() {
    220            }
    221
    222            @Override
    223            public void close() {
    224                // do not close sys err!
    225            }
    226
    227            @Override
    228            public void flush() {
    229                System.err.flush();
    230            }
    231
    232            @Override
    233            public void write(final byte[] b) throws IOException {
    234                System.err.write(b);
    235            }
    236
    237            @Override
    238            public void write(final byte[] b, final int off, final int len)
    239                throws IOException {
    240                System.err.write(b, off, len);
    241            }
    242
    243            @Override
    244            public void write(final int b) {
    245                System.err.write(b);
    246            }
    247        }
    248
    249        /**
    250         * An implementation of OutputStream that redirects to the current System.out.
    251         */
    252        private static class SystemOutStream extends OutputStream {
    253            public SystemOutStream() {
    254            }
    255
    256            @Override
    257            public void close() {
    258                // do not close sys out!
    259            }
    260
    261            @Override
    262            public void flush() {
    263                System.out.flush();
    264            }
    265
    266            @Override
    267            public void write(final byte[] b) throws IOException {
    268                System.out.write(b);
    269            }
    270
    271            @Override
    272            public void write(final byte[] b, final int off, final int len)
    273                throws IOException {
    274                System.out.write(b, off, len);
    275            }
    276
    277            @Override
    278            public void write(final int b) throws IOException {
    279                System.out.write(b);
    280            }
    281        }
    282
    283        /**
    284         * A delegating OutputStream that does not close its delegate.
    285         */
    286        private static class CloseShieldOutputStream extends OutputStream {
    287
    288            private final OutputStream delegate;
    289
    290            public CloseShieldOutputStream(final OutputStream delegate) {
    291                this.delegate = delegate;
    292            }
    293
    294            @Override
    295            public void close() {
    296                // do not close delegate
    297            }
    298
    299            @Override
    300            public void flush() throws IOException {
    301                delegate.flush();
    302            }
    303
    304            @Override
    305            public void write(final byte[] b) throws IOException {
    306                delegate.write(b);
    307            }
    308
    309            @Override
    310            public void write(final byte[] b, final int off, final int len)
    311                    throws IOException {
    312                delegate.write(b, off, len);
    313            }
    314
    315            @Override
    316            public void write(final int b) throws IOException {
    317                delegate.write(b);
    318            }
    319        }
    320
    321        /**
    322         * Data to pass to factory method.
    323         */
    324        private static class FactoryData {
    325            private final OutputStream os;
    326            private final String type;
    327            private final Layout<? extends Serializable> layout;
    328
    329            /**
    330             * Constructor.
    331             * @param os The OutputStream.
    332             * @param type The name of the target.
    333             * @param layout A Serializable layout
    334             */
    335            public FactoryData(final OutputStream os, final String type, final Layout<? extends Serializable> layout) {
    336                this.os = os;
    337                this.type = type;
    338                this.layout = layout;
    339            }
    340        }
    341
    342        /**
    343         * Factory to create the Appender.
    344         */
    345        private static class ConsoleManagerFactory implements ManagerFactory<OutputStreamManager, FactoryData> {
    346
    347            /**
    348             * Create an OutputStreamManager.
    349             * @param name The name of the entity to manage.
    350             * @param data The data required to create the entity.
    351             * @return The OutputStreamManager
    352             */
    353            @Override
    354            public OutputStreamManager createManager(final String name, final FactoryData data) {
    355                return new OutputStreamManager(data.os, data.type, data.layout);
    356            }
    357        }
    358
    359    }