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