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