View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements. See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache license, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License. You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the license for the specific language governing permissions and
    15   * limitations under the license.
    16   */
    17  package org.apache.logging.log4j.core.appender;
    18
    19  import java.io.IOException;
    20  import java.io.OutputStream;
    21  import java.io.PrintStream;
    22  import java.io.Serializable;
    23  import java.io.UnsupportedEncodingException;
    24  import java.lang.reflect.Constructor;
    25  import java.nio.charset.Charset;
    26
    27  import org.apache.logging.log4j.core.Filter;
    28  import org.apache.logging.log4j.core.Layout;
    29  import org.apache.logging.log4j.core.config.plugins.Plugin;
    30  import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
    31  import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
    32  import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
    33  import org.apache.logging.log4j.core.config.plugins.PluginElement;
    34  import org.apache.logging.log4j.core.config.plugins.PluginFactory;
    35  import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
    36  import org.apache.logging.log4j.core.layout.PatternLayout;
    37  import org.apache.logging.log4j.core.util.Booleans;
    38  import org.apache.logging.log4j.core.util.Loader;
    39  import org.apache.logging.log4j.util.PropertiesUtil;
    40
    41  /**
    42   * ConsoleAppender appends log events to <code>System.out</code> or
    43   * <code>System.err</code> using a layout specified by the user. The
    44   * default target is <code>System.out</code>.
    45   * TODO accessing System.out or .err as a byte stream instead of a writer
    46   *    bypasses the JVM's knowledge of the proper encoding. (RG) Encoding
    47   * is handled within the Layout. Typically, a Layout will generate a String
    48   * and then call getBytes which may use a configured encoding or the system
    49   * default. OTOH, a Writer cannot print byte streams.
    50   */
    51  @Plugin(name = "Console", category = "Core", elementType = "appender", printObject = true)
    52  public final class ConsoleAppender extends AbstractOutputStreamAppender<OutputStreamManager> {
    53
    54      private static final long serialVersionUID = 1L;
    55      private static final String JANSI_CLASS = "org.fusesource.jansi.WindowsAnsiOutputStream";
    56      private static ConsoleManagerFactory factory = new ConsoleManagerFactory();
    57      private static final Target DEFAULT_TARGET = Target.SYSTEM_OUT;
    58
    59      /**
    60       * Enumeration of console destinations.
    61       */
    62      public enum Target {
    63          /** Standard output. */
    64          SYSTEM_OUT,
    65          /** Standard error output. */
    66          SYSTEM_ERR
    67      }
    68
    69      private ConsoleAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
    70                              final OutputStreamManager manager,
    71                              final boolean ignoreExceptions) {
    72          super(name, layout, filter, ignoreExceptions, true, manager);
    73      }
    74
    75      /**
    76       * Create a Console Appender.
    77       * @param layout The layout to use (required).
    78       * @param filter The Filter or null.
    79       * @param targetStr The target ("SYSTEM_OUT" or "SYSTEM_ERR"). The default is "SYSTEM_OUT".
    80       * @param follow If true will follow changes to the underlying output stream.
    81       * @param name The name of the Appender (required).
    82       * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise
    83       *               they are propagated to the caller.
    84       * @return The ConsoleAppender.
    85       */
    86      @PluginFactory
    87      public static ConsoleAppender createAppender(
    88              @PluginElement("Layout") Layout<? extends Serializable> layout,
    89              @PluginElement("Filter") final Filter filter,
    90              @PluginAttribute(value = "target", defaultString = "SYSTEM_OUT") final String targetStr,
    91              @PluginAttribute("name") final String name,
    92              @PluginAttribute(value = "follow", defaultBoolean = false) final String follow,
    93              @PluginAttribute(value = "ignoreExceptions", defaultBoolean = true) final String ignore) {
    94          if (name == null) {
    95              LOGGER.error("No name provided for ConsoleAppender");
    96              return null;
    97          }
    98          if (layout == null) {
    99              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 }