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