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.Filter;
031import org.apache.logging.log4j.core.Layout;
032import org.apache.logging.log4j.core.config.plugins.Plugin;
033import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
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.PluginElement;
037import org.apache.logging.log4j.core.config.plugins.PluginFactory;
038import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
039import org.apache.logging.log4j.core.layout.PatternLayout;
040import org.apache.logging.log4j.core.util.Booleans;
041import org.apache.logging.log4j.core.util.CloseShieldOutputStream;
042import org.apache.logging.log4j.core.util.Loader;
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 System.out or .err as a byte stream instead of a writer bypasses the JVM's knowledge of the proper
050 * encoding. (RG) Encoding is handled within the Layout. Typically, a Layout will generate a String and then call
051 * getBytes which may use a configured encoding or the system default. OTOH, a Writer cannot print byte streams.
052 */
053@Plugin(name = "Console", category = "Core", elementType = "appender", printObject = true)
054public final class ConsoleAppender extends AbstractOutputStreamAppender<OutputStreamManager> {
055
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        /** Standard error output. */
070        SYSTEM_ERR
071    }
072
073    private ConsoleAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
074            final OutputStreamManager manager, final boolean ignoreExceptions, final Target target) {
075        super(name, layout, filter, ignoreExceptions, true, manager);
076        this.target = target;
077    }
078
079    /**
080     * Creates a Console Appender.
081     *
082     * @param layout The layout to use (required).
083     * @param filter The Filter or null.
084     * @param targetStr The target ("SYSTEM_OUT" or "SYSTEM_ERR"). The default is "SYSTEM_OUT".
085     * @param name The name of the Appender (required).
086     * @param follow If true will follow changes to the underlying output stream.
087     * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they
088     *            are propagated to the caller.
089     * @return The ConsoleAppender.
090     * @deprecated Use {@link #createAppender(Layout, Filter, Target, String, boolean, boolean, boolean)}.
091     */
092    @Deprecated
093    public static ConsoleAppender createAppender(Layout<? extends Serializable> layout,
094            final Filter filter,
095            final String targetStr,
096            final String name,
097            final String follow,
098            final String ignore) {
099        if (name == null) {
100            LOGGER.error("No name provided for ConsoleAppender");
101            return null;
102        }
103        if (layout == null) {
104            layout = PatternLayout.createDefaultLayout();
105        }
106        final boolean isFollow = Boolean.parseBoolean(follow);
107        final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
108        final Target target = targetStr == null ? DEFAULT_TARGET : Target.valueOf(targetStr);
109        return new ConsoleAppender(name, layout, filter, getManager(target, isFollow, false, layout), ignoreExceptions, target);
110    }
111
112    /**
113     * Creates a Console Appender.
114     *
115     * @param layout The layout to use (required).
116     * @param filter The Filter or null.
117     * @param target The target (SYSTEM_OUT or SYSTEM_ERR). The default is SYSTEM_OUT.
118     * @param name The name of the Appender (required).
119     * @param follow If true will follow changes to the underlying output stream.
120     * @param direct If true will write directly to {@link java.io.FileDescriptor} and bypass
121     *            {@link System#out}/{@link System#err}.
122     * @param ignoreExceptions If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they
123     *            are propagated to the caller.
124     * @return The ConsoleAppender.
125     */
126    @PluginFactory
127    public static ConsoleAppender createAppender(
128            // @formatter:off
129            @PluginElement("Layout") Layout<? extends Serializable> layout,
130            @PluginElement("Filter") final Filter filter,
131            @PluginAttribute(value = "target") Target target,
132            @PluginAttribute("name") final String name,
133            @PluginAttribute(value = "follow", defaultBoolean = false) final boolean follow,
134            @PluginAttribute(value = "direct", defaultBoolean = false) final boolean direct,
135            @PluginAttribute(value = "ignoreExceptions", defaultBoolean = true) 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 Builder newBuilder() {
160        return new Builder();
161    }
162
163    /**
164     * Builds ConsoleAppender instances.
165     */
166    public static class Builder implements org.apache.logging.log4j.core.util.Builder<ConsoleAppender> {
167
168        @PluginElement("Layout")
169        @Required
170        private Layout<? extends Serializable> layout = PatternLayout.createDefaultLayout();
171
172        @PluginElement("Filter")
173        private Filter filter;
174
175        @PluginBuilderAttribute
176        @Required
177        private Target target = DEFAULT_TARGET;
178
179        @PluginBuilderAttribute
180        @Required
181        private String name;
182
183        @PluginBuilderAttribute
184        private boolean follow = false;
185
186        @PluginBuilderAttribute
187        private boolean direct = false;
188
189        @PluginBuilderAttribute
190        private boolean ignoreExceptions = true;
191
192        public Builder setLayout(final Layout<? extends Serializable> aLayout) {
193            this.layout = aLayout;
194            return this;
195        }
196
197        public Builder setFilter(final Filter aFilter) {
198            this.filter = aFilter;
199            return this;
200        }
201
202        public Builder setTarget(final Target aTarget) {
203            this.target = aTarget;
204            return this;
205        }
206
207        public Builder setName(final String aName) {
208            this.name = aName;
209            return this;
210        }
211
212        public Builder setFollow(final boolean shouldFollow) {
213            this.follow = shouldFollow;
214            return this;
215        }
216
217        public Builder setDirect(final boolean shouldDirect) {
218            this.direct = shouldDirect;
219            return this;
220        }
221
222        public Builder setIgnoreExceptions(final boolean shouldIgnoreExceptions) {
223            this.ignoreExceptions = shouldIgnoreExceptions;
224            return this;
225        }
226
227        @Override
228        public ConsoleAppender build() {
229            if (follow && direct) {
230                throw new IllegalArgumentException("Cannot use both follow and direct on ConsoleAppender");
231            }
232            return new ConsoleAppender(name, layout, filter, getManager(target, follow, direct, layout), ignoreExceptions, target);
233        }
234    }
235
236    private static OutputStreamManager getDefaultManager(final Target target, final boolean follow, final boolean direct,
237            final Layout<? extends Serializable> layout) {
238        final OutputStream os = getOutputStream(follow, direct, target);
239
240        // LOG4J2-1176 DefaultConfiguration should not share OutputStreamManager instances to avoid memory leaks.
241        final String managerName = target.name() + '.' + follow + '.' + direct + "-" + COUNT.get();
242        return OutputStreamManager.getManager(managerName, new FactoryData(os, managerName, layout), factory);
243    }
244
245    private static OutputStreamManager getManager(final Target target, final boolean follow, final boolean direct,
246            final Layout<? extends Serializable> layout) {
247        final OutputStream os = getOutputStream(follow, direct, target);
248        final String managerName = target.name() + '.' + follow + '.' + direct;
249        return OutputStreamManager.getManager(managerName, new FactoryData(os, managerName, layout), factory);
250    }
251
252    private static OutputStream getOutputStream(final boolean follow, final boolean direct, final Target target) {
253        final String enc = Charset.defaultCharset().name();
254        OutputStream outputStream;
255        try {
256            // @formatter:off
257            outputStream = target == Target.SYSTEM_OUT ?
258                direct ? new FileOutputStream(FileDescriptor.out) :
259                    (follow ? new PrintStream(new SystemOutStream(), true, enc) : System.out) :
260                direct ? new FileOutputStream(FileDescriptor.err) :
261                    (follow ? new PrintStream(new SystemErrStream(), true, enc) : System.err);
262            // @formatter:on
263            outputStream = new CloseShieldOutputStream(outputStream);
264        } catch (final UnsupportedEncodingException ex) { // should never happen
265            throw new IllegalStateException("Unsupported default encoding " + enc, ex);
266        }
267        final PropertiesUtil propsUtil = PropertiesUtil.getProperties();
268        if (!propsUtil.isOsWindows() || propsUtil.getBooleanProperty("log4j.skipJansi") || direct) {
269            return outputStream;
270        }
271        try {
272            // We type the parameter as a wildcard to avoid a hard reference to Jansi.
273            final Class<?> clazz = Loader.loadClass(JANSI_CLASS);
274            final Constructor<?> constructor = clazz.getConstructor(OutputStream.class);
275            return new CloseShieldOutputStream((OutputStream) constructor.newInstance(outputStream));
276        } catch (final ClassNotFoundException cnfe) {
277            LOGGER.debug("Jansi is not installed, cannot find {}", JANSI_CLASS);
278        } catch (final NoSuchMethodException nsme) {
279            LOGGER.warn("{} is missing the proper constructor", JANSI_CLASS);
280        } catch (final Exception ex) {
281            LOGGER.warn("Unable to instantiate {}", JANSI_CLASS);
282        }
283        return outputStream;
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.
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}