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