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 long serialVersionUID = 1L;
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 static 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, 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 follow If true will follow changes to the underlying output stream.
085     * @param name The name of the Appender (required).
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 Use {@link #createAppender(Layout, Filter, Target, String, String, boolean)}.
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, 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 follow If true will follow changes to the underlying output stream.
118     * @param name The name of the Appender (required).
119     * @param ignoreExceptions If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they
120     *            are propagated to the caller.
121     * @return The ConsoleAppender.
122     */
123    @PluginFactory
124    public static ConsoleAppender createAppender(@PluginElement("Layout") Layout<? extends Serializable> layout,
125            @PluginElement("Filter") final Filter filter,
126            @PluginAttribute(value = "target") Target target,
127            @PluginAttribute("name") final String name,
128            @PluginAttribute(value = "follow", defaultBoolean = false) final boolean follow,
129            @PluginAttribute(value = "ignoreExceptions", defaultBoolean = true) final boolean ignoreExceptions) {
130        if (name == null) {
131            LOGGER.error("No name provided for ConsoleAppender");
132            return null;
133        }
134        if (layout == null) {
135            layout = PatternLayout.createDefaultLayout();
136        }
137        target = target == null ? Target.SYSTEM_OUT : target;
138        return new ConsoleAppender(name, layout, filter, getManager(target, follow, layout), ignoreExceptions, target);
139    }
140
141    public static ConsoleAppender createDefaultAppenderForLayout(final Layout<? extends Serializable> layout) {
142        // this method cannot use the builder class without introducing an infinite loop due to DefaultConfiguration
143        return new ConsoleAppender("DefaultConsole-" + COUNT.incrementAndGet(), layout, null,
144                getDefaultManager(DEFAULT_TARGET, false, layout), true, DEFAULT_TARGET);
145    }
146
147    @PluginBuilderFactory
148    public static Builder newBuilder() {
149        return new Builder();
150    }
151
152    /**
153     * Builds ConsoleAppender instances.
154     */
155    public static class Builder implements org.apache.logging.log4j.core.util.Builder<ConsoleAppender> {
156
157        @PluginElement("Layout")
158        @Required
159        private Layout<? extends Serializable> layout = PatternLayout.createDefaultLayout();
160
161        @PluginElement("Filter")
162        private Filter filter;
163
164        @PluginBuilderAttribute
165        @Required
166        private Target target = DEFAULT_TARGET;
167
168        @PluginBuilderAttribute
169        @Required
170        private String name;
171
172        @PluginBuilderAttribute
173        private boolean follow = false;
174
175        @PluginBuilderAttribute
176        private boolean ignoreExceptions = true;
177
178        public Builder setLayout(final Layout<? extends Serializable> aLayout) {
179            this.layout = aLayout;
180            return this;
181        }
182
183        public Builder setFilter(final Filter aFilter) {
184            this.filter = aFilter;
185            return this;
186        }
187
188        public Builder setTarget(final Target aTarget) {
189            this.target = aTarget;
190            return this;
191        }
192
193        public Builder setName(final String aName) {
194            this.name = aName;
195            return this;
196        }
197
198        public Builder setFollow(final boolean shouldFollow) {
199            this.follow = shouldFollow;
200            return this;
201        }
202
203        public Builder setIgnoreExceptions(final boolean shouldIgnoreExceptions) {
204            this.ignoreExceptions = shouldIgnoreExceptions;
205            return this;
206        }
207
208        @Override
209        public ConsoleAppender build() {
210            return new ConsoleAppender(name, layout, filter, getManager(target, follow, layout), ignoreExceptions, target);
211        }
212    }
213
214    private static OutputStreamManager getDefaultManager(final Target target, final boolean follow,
215            final Layout<? extends Serializable> layout) {
216        final OutputStream os = getOutputStream(follow, target);
217
218        // LOG4J2-1176 DefaultConfiguration should not share OutputStreamManager instances to avoid memory leaks.
219        final String managerName = target.name() + '.' + follow + "-" + COUNT.get();
220        return OutputStreamManager.getManager(managerName, new FactoryData(os, managerName, layout), factory);
221    }
222
223    private static OutputStreamManager getManager(final Target target, final boolean follow,
224            final Layout<? extends Serializable> layout) {
225        final OutputStream os = getOutputStream(follow, target);
226        final String managerName = target.name() + '.' + follow;
227        return OutputStreamManager.getManager(managerName, new FactoryData(os, managerName, layout), factory);
228    }
229
230    private static OutputStream getOutputStream(final boolean follow, final Target target) {
231        final String enc = Charset.defaultCharset().name();
232        OutputStream outputStream = null;
233        try {
234            // @formatter:off
235            outputStream = target == Target.SYSTEM_OUT ?
236                follow ? new PrintStream(new SystemOutStream(), true, enc) : System.out :
237                follow ? new PrintStream(new SystemErrStream(), true, enc) : System.err;
238            // @formatter:on
239            outputStream = new CloseShieldOutputStream(outputStream);
240        } catch (final UnsupportedEncodingException ex) { // should never happen
241            throw new IllegalStateException("Unsupported default encoding " + enc, ex);
242        }
243        final PropertiesUtil propsUtil = PropertiesUtil.getProperties();
244        if (!propsUtil.isOsWindows() || propsUtil.getBooleanProperty("log4j.skipJansi")) {
245            return outputStream;
246        }
247        try {
248            // We type the parameter as a wildcard to avoid a hard reference to Jansi.
249            final Class<?> clazz = Loader.loadClass(JANSI_CLASS);
250            final Constructor<?> constructor = clazz.getConstructor(OutputStream.class);
251            return new CloseShieldOutputStream((OutputStream) constructor.newInstance(outputStream));
252        } catch (final ClassNotFoundException cnfe) {
253            LOGGER.debug("Jansi is not installed, cannot find {}", JANSI_CLASS);
254        } catch (final NoSuchMethodException nsme) {
255            LOGGER.warn("{} is missing the proper constructor", JANSI_CLASS);
256        } catch (final Exception ex) {
257            LOGGER.warn("Unable to instantiate {}", JANSI_CLASS);
258        }
259        return outputStream;
260    }
261
262    /**
263     * An implementation of OutputStream that redirects to the current System.err.
264     */
265    private static class SystemErrStream extends OutputStream {
266        public SystemErrStream() {
267        }
268
269        @Override
270        public void close() {
271            // do not close sys err!
272        }
273
274        @Override
275        public void flush() {
276            System.err.flush();
277        }
278
279        @Override
280        public void write(final byte[] b) throws IOException {
281            System.err.write(b);
282        }
283
284        @Override
285        public void write(final byte[] b, final int off, final int len) throws IOException {
286            System.err.write(b, off, len);
287        }
288
289        @Override
290        public void write(final int b) {
291            System.err.write(b);
292        }
293    }
294
295    /**
296     * An implementation of OutputStream that redirects to the current System.out.
297     */
298    private static class SystemOutStream extends OutputStream {
299        public SystemOutStream() {
300        }
301
302        @Override
303        public void close() {
304            // do not close sys out!
305        }
306
307        @Override
308        public void flush() {
309            System.out.flush();
310        }
311
312        @Override
313        public void write(final byte[] b) throws IOException {
314            System.out.write(b);
315        }
316
317        @Override
318        public void write(final byte[] b, final int off, final int len) throws IOException {
319            System.out.write(b, off, len);
320        }
321
322        @Override
323        public void write(final int b) throws IOException {
324            System.out.write(b);
325        }
326    }
327
328    /**
329     * Data to pass to factory method.
330     */
331    private static class FactoryData {
332        private final OutputStream os;
333        private final String name;
334        private final Layout<? extends Serializable> layout;
335
336        /**
337         * Constructor.
338         * 
339         * @param os The OutputStream.
340         * @param type The name of the target.
341         * @param layout A Serializable layout
342         */
343        public FactoryData(final OutputStream os, final String type, final Layout<? extends Serializable> layout) {
344            this.os = os;
345            this.name = type;
346            this.layout = layout;
347        }
348    }
349
350    /**
351     * Factory to create the Appender.
352     */
353    private static class ConsoleManagerFactory implements ManagerFactory<OutputStreamManager, FactoryData> {
354
355        /**
356         * Create an OutputStreamManager.
357         * 
358         * @param name The name of the entity to manage.
359         * @param data The data required to create the entity.
360         * @return The OutputStreamManager
361         */
362        @Override
363        public OutputStreamManager createManager(final String name, final FactoryData data) {
364            return new OutputStreamManager(data.os, data.name, data.layout, true);
365        }
366    }
367
368    public Target getTarget() {
369        return target;
370    }
371
372}