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.FileDescriptor;
020import java.io.FileOutputStream;
021import java.io.IOException;
022import java.io.OutputStream;
023import java.io.PrintStream;
024import java.io.Serializable;
025import java.io.UnsupportedEncodingException;
026import java.lang.reflect.Constructor;
027import java.nio.charset.Charset;
028import java.util.concurrent.atomic.AtomicInteger;
029
030import org.apache.logging.log4j.core.Appender;
031import org.apache.logging.log4j.core.Core;
032import org.apache.logging.log4j.core.Filter;
033import org.apache.logging.log4j.core.Layout;
034import org.apache.logging.log4j.core.config.plugins.Plugin;
035import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
036import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
037import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
038import org.apache.logging.log4j.core.layout.PatternLayout;
039import org.apache.logging.log4j.core.util.Booleans;
040import org.apache.logging.log4j.core.util.CloseShieldOutputStream;
041import org.apache.logging.log4j.core.util.Throwables;
042import org.apache.logging.log4j.util.Chars;
043import org.apache.logging.log4j.util.LoaderUtil;
044import org.apache.logging.log4j.util.PropertiesUtil;
045
046/**
047 * Appends log events to <code>System.out</code> or <code>System.err</code> using a layout specified by the user. The
048 * default target is <code>System.out</code>.
049 * <p>
050 * TODO Accessing <code>System.out</code> or <code>System.err</code> as a byte stream instead of a writer bypasses the
051 * JVM's knowledge of the proper encoding. (RG) Encoding is handled within the Layout. Typically, a Layout will generate
052 * a String and then call getBytes which may use a configured encoding or the system default. OTOH, a Writer cannot
053 * print byte streams.
054 * </p>
055 */
056@Plugin(name = ConsoleAppender.PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
057public final class ConsoleAppender extends AbstractOutputStreamAppender<OutputStreamManager> {
058
059    public static final String PLUGIN_NAME = "Console";
060    private static final String JANSI_CLASS = "org.fusesource.jansi.WindowsAnsiOutputStream";
061    private static ConsoleManagerFactory factory = new ConsoleManagerFactory();
062    private static final Target DEFAULT_TARGET = Target.SYSTEM_OUT;
063    private static final AtomicInteger COUNT = new AtomicInteger();
064
065    private final Target target;
066
067    /**
068     * Enumeration of console destinations.
069     */
070    public enum Target {
071
072        /** Standard output. */
073        SYSTEM_OUT {
074            @Override
075            public Charset getDefaultCharset() {
076                // "sun.stdout.encoding" is only set when running from the console.
077                return getCharset("sun.stdout.encoding", Charset.defaultCharset());
078            }
079        },
080
081        /** Standard error output. */
082        SYSTEM_ERR {
083            @Override
084            public Charset getDefaultCharset() {
085                // "sun.stderr.encoding" is only set when running from the console.
086                return getCharset("sun.stderr.encoding", Charset.defaultCharset());
087            }
088        };
089
090        public abstract Charset getDefaultCharset();
091
092        protected Charset getCharset(final String property, Charset defaultCharset) {
093            return new PropertiesUtil(PropertiesUtil.getSystemProperties()).getCharsetProperty(property, defaultCharset);
094        }
095
096    }
097
098    private ConsoleAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
099            final OutputStreamManager manager, final boolean ignoreExceptions, final Target target) {
100        super(name, layout, filter, ignoreExceptions, true, manager);
101        this.target = target;
102    }
103
104    /**
105     * Creates a Console Appender.
106     *
107     * @param layout The layout to use (required).
108     * @param filter The Filter or null.
109     * @param targetStr The target ("SYSTEM_OUT" or "SYSTEM_ERR"). The default is "SYSTEM_OUT".
110     * @param name The name of the Appender (required).
111     * @param follow If true will follow changes to the underlying output stream.
112     * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they
113     *            are propagated to the caller.
114     * @return The ConsoleAppender.
115     * @deprecated Deprecated in 2.7; use {@link #newBuilder()}.
116     */
117    @Deprecated
118    public static ConsoleAppender createAppender(Layout<? extends Serializable> layout,
119            final Filter filter,
120            final String targetStr,
121            final String name,
122            final String follow,
123            final String ignore) {
124        if (name == null) {
125            LOGGER.error("No name provided for ConsoleAppender");
126            return null;
127        }
128        if (layout == null) {
129            layout = PatternLayout.createDefaultLayout();
130        }
131        final boolean isFollow = Boolean.parseBoolean(follow);
132        final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
133        final Target target = targetStr == null ? DEFAULT_TARGET : Target.valueOf(targetStr);
134        return new ConsoleAppender(name, layout, filter, getManager(target, isFollow, false, layout), ignoreExceptions, target);
135    }
136
137    /**
138     * Creates a Console Appender.
139     *
140     * @param layout The layout to use (required).
141     * @param filter The Filter or null.
142     * @param target The target (SYSTEM_OUT or SYSTEM_ERR). The default is SYSTEM_OUT.
143     * @param name The name of the Appender (required).
144     * @param follow If true will follow changes to the underlying output stream.
145     * @param direct If true will write directly to {@link java.io.FileDescriptor} and bypass
146     *            {@link System#out}/{@link System#err}.
147     * @param ignoreExceptions If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they
148     *            are propagated to the caller.
149     * @return The ConsoleAppender.
150     * @deprecated Deprecated in 2.7; use {@link #newBuilder()}.
151     */
152    @Deprecated
153    public static ConsoleAppender createAppender(
154            // @formatter:off
155            Layout<? extends Serializable> layout,
156            final Filter filter,
157            Target target,
158            final String name,
159            final boolean follow,
160            final boolean direct,
161            final boolean ignoreExceptions) {
162            // @formatter:on
163        if (name == null) {
164            LOGGER.error("No name provided for ConsoleAppender");
165            return null;
166        }
167        if (layout == null) {
168            layout = PatternLayout.createDefaultLayout();
169        }
170        target = target == null ? Target.SYSTEM_OUT : target;
171        if (follow && direct) {
172            LOGGER.error("Cannot use both follow and direct on ConsoleAppender");
173            return null;
174        }
175        return new ConsoleAppender(name, layout, filter, getManager(target, follow, direct, layout), ignoreExceptions, target);
176    }
177
178    public static ConsoleAppender createDefaultAppenderForLayout(final Layout<? extends Serializable> layout) {
179        // this method cannot use the builder class without introducing an infinite loop due to DefaultConfiguration
180        return new ConsoleAppender("DefaultConsole-" + COUNT.incrementAndGet(), layout, null,
181                getDefaultManager(DEFAULT_TARGET, false, false, layout), true, DEFAULT_TARGET);
182    }
183
184    @PluginBuilderFactory
185    public static <B extends Builder<B>> B newBuilder() {
186        return new Builder<B>().asBuilder();
187    }
188
189    /**
190     * Builds ConsoleAppender instances.
191     * @param <B> The type to build
192     */
193    public static class Builder<B extends Builder<B>> extends AbstractOutputStreamAppender.Builder<B>
194            implements org.apache.logging.log4j.core.util.Builder<ConsoleAppender> {
195
196        @PluginBuilderAttribute
197        @Required
198        private Target target = DEFAULT_TARGET;
199
200        @PluginBuilderAttribute
201        private boolean follow;
202
203        @PluginBuilderAttribute
204        private boolean direct;
205
206        public B setTarget(final Target aTarget) {
207            this.target = aTarget;
208            return asBuilder();
209        }
210
211        public B setFollow(final boolean shouldFollow) {
212            this.follow = shouldFollow;
213            return asBuilder();
214        }
215
216        public B setDirect(final boolean shouldDirect) {
217            this.direct = shouldDirect;
218            return asBuilder();
219        }
220
221        @Override
222        public ConsoleAppender build() {
223            if (follow && direct) {
224                throw new IllegalArgumentException("Cannot use both follow and direct on ConsoleAppender '" + getName() + "'");
225            }
226            final Layout<? extends Serializable> layout = getOrCreateLayout(target.getDefaultCharset());
227            return new ConsoleAppender(getName(), layout, getFilter(), getManager(target, follow, direct, layout),
228                    isIgnoreExceptions(), target);
229        }
230    }
231
232    private static OutputStreamManager getDefaultManager(final Target target, final boolean follow, final boolean direct,
233            final Layout<? extends Serializable> layout) {
234        final OutputStream os = getOutputStream(follow, direct, target);
235
236        // LOG4J2-1176 DefaultConfiguration should not share OutputStreamManager instances to avoid memory leaks.
237        final String managerName = target.name() + '.' + follow + '.' + direct + "-" + COUNT.get();
238        return OutputStreamManager.getManager(managerName, new FactoryData(os, managerName, layout), factory);
239    }
240
241    private static OutputStreamManager getManager(final Target target, final boolean follow, final boolean direct,
242            final Layout<? extends Serializable> layout) {
243        final OutputStream os = getOutputStream(follow, direct, target);
244        final String managerName = target.name() + '.' + follow + '.' + direct;
245        return OutputStreamManager.getManager(managerName, new FactoryData(os, managerName, layout), factory);
246    }
247
248    private static OutputStream getOutputStream(final boolean follow, final boolean direct, final Target target) {
249        final String enc = Charset.defaultCharset().name();
250        OutputStream outputStream;
251        try {
252            // @formatter:off
253            outputStream = target == Target.SYSTEM_OUT ?
254                direct ? new FileOutputStream(FileDescriptor.out) :
255                    (follow ? new PrintStream(new SystemOutStream(), true, enc) : System.out) :
256                direct ? new FileOutputStream(FileDescriptor.err) :
257                    (follow ? new PrintStream(new SystemErrStream(), true, enc) : System.err);
258            // @formatter:on
259            outputStream = new CloseShieldOutputStream(outputStream);
260        } catch (final UnsupportedEncodingException ex) { // should never happen
261            throw new IllegalStateException("Unsupported default encoding " + enc, ex);
262        }
263        final PropertiesUtil propsUtil = PropertiesUtil.getProperties();
264        if (!propsUtil.isOsWindows() || propsUtil.getBooleanProperty("log4j.skipJansi", true) || direct) {
265            return outputStream;
266        }
267        try {
268            // We type the parameter as a wildcard to avoid a hard reference to Jansi.
269            final Class<?> clazz = LoaderUtil.loadClass(JANSI_CLASS);
270            final Constructor<?> constructor = clazz.getConstructor(OutputStream.class);
271            return new CloseShieldOutputStream((OutputStream) constructor.newInstance(outputStream));
272        } catch (final ClassNotFoundException cnfe) {
273            LOGGER.debug("Jansi is not installed, cannot find {}", JANSI_CLASS);
274        } catch (final NoSuchMethodException nsme) {
275            LOGGER.warn("{} is missing the proper constructor", JANSI_CLASS);
276        } catch (final Exception ex) {
277            LOGGER.warn("Unable to instantiate {} due to {}", JANSI_CLASS, clean(Throwables.getRootCause(ex).toString()).trim());
278        }
279        return outputStream;
280    }
281
282    private static String clean(String string) {
283                return string.replace(Chars.NUL, Chars.SPACE);
284        }
285
286        /**
287     * An implementation of OutputStream that redirects to the current System.err.
288     */
289    private static class SystemErrStream extends OutputStream {
290        public SystemErrStream() {
291        }
292
293        @Override
294        public void close() {
295            // do not close sys err!
296        }
297
298        @Override
299        public void flush() {
300            System.err.flush();
301        }
302
303        @Override
304        public void write(final byte[] b) throws IOException {
305            System.err.write(b);
306        }
307
308        @Override
309        public void write(final byte[] b, final int off, final int len) throws IOException {
310            System.err.write(b, off, len);
311        }
312
313        @Override
314        public void write(final int b) {
315            System.err.write(b);
316        }
317    }
318
319    /**
320     * An implementation of OutputStream that redirects to the current System.out.
321     */
322    private static class SystemOutStream extends OutputStream {
323        public SystemOutStream() {
324        }
325
326        @Override
327        public void close() {
328            // do not close sys out!
329        }
330
331        @Override
332        public void flush() {
333            System.out.flush();
334        }
335
336        @Override
337        public void write(final byte[] b) throws IOException {
338            System.out.write(b);
339        }
340
341        @Override
342        public void write(final byte[] b, final int off, final int len) throws IOException {
343            System.out.write(b, off, len);
344        }
345
346        @Override
347        public void write(final int b) throws IOException {
348            System.out.write(b);
349        }
350    }
351
352    /**
353     * Data to pass to factory method.Unable to instantiate
354     */
355    private static class FactoryData {
356        private final OutputStream os;
357        private final String name;
358        private final Layout<? extends Serializable> layout;
359
360        /**
361         * Constructor.
362         *
363         * @param os The OutputStream.
364         * @param type The name of the target.
365         * @param layout A Serializable layout
366         */
367        public FactoryData(final OutputStream os, final String type, final Layout<? extends Serializable> layout) {
368            this.os = os;
369            this.name = type;
370            this.layout = layout;
371        }
372    }
373
374    /**
375     * Factory to create the Appender.
376     */
377    private static class ConsoleManagerFactory implements ManagerFactory<OutputStreamManager, FactoryData> {
378
379        /**
380         * Create an OutputStreamManager.
381         *
382         * @param name The name of the entity to manage.
383         * @param data The data required to create the entity.
384         * @return The OutputStreamManager
385         */
386        @Override
387        public OutputStreamManager createManager(final String name, final FactoryData data) {
388            return new OutputStreamManager(data.os, data.name, data.layout, true);
389        }
390    }
391
392    public Target getTarget() {
393        return target;
394    }
395
396}