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