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