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