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        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    }