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