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 * Appends log events to <code>System.out</code> or <code>System.err</code> using a layout specified by the user. The
043 * default target is <code>System.out</code>.
044 * <p>
045 * TODO accessing System.out or .err as a byte stream instead of a writer bypasses the JVM's knowledge of the proper
046 * encoding. (RG) Encoding is handled within the Layout. Typically, a Layout will generate a String and then call
047 * getBytes which may use a configured encoding or the system default. OTOH, a Writer cannot print byte streams.
048 */
049@Plugin(name = "Console", category = "Core", elementType = "appender", printObject = true)
050public final class ConsoleAppender extends AbstractOutputStreamAppender<OutputStreamManager> {
051
052    private static final long serialVersionUID = 1L;
053    private static final String JANSI_CLASS = "org.fusesource.jansi.WindowsAnsiOutputStream";
054    private static ConsoleManagerFactory factory = new ConsoleManagerFactory();
055    private static final Target DEFAULT_TARGET = Target.SYSTEM_OUT;
056
057    /**
058     * Enumeration of console destinations.
059     */
060    public static 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, final boolean ignoreExceptions) {
069        super(name, layout, filter, ignoreExceptions, true, manager);
070    }
071
072    /**
073     * Creates a Console Appender.
074     * 
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 they
081     *            are propagated to the caller.
082     * @return The ConsoleAppender.
083     */
084    @PluginFactory
085    public static ConsoleAppender createAppender(@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 ? DEFAULT_TARGET : Target.valueOf(targetStr);
101        return new ConsoleAppender(name, layout, filter, getManager(target, isFollow, 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(DEFAULT_TARGET, false, layout), true);
107    }
108
109    @PluginBuilderFactory
110    public static Builder newBuilder() {
111        return new Builder();
112    }
113
114    /**
115     * Builds ConsoleAppender instances.
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> aLayout) {
141            this.layout = aLayout;
142            return this;
143        }
144
145        public Builder setFilter(final Filter aFilter) {
146            this.filter = aFilter;
147            return this;
148        }
149
150        public Builder setTarget(final Target aTarget) {
151            this.target = aTarget;
152            return this;
153        }
154
155        public Builder setName(final String aName) {
156            this.name = aName;
157            return this;
158        }
159
160        public Builder setFollow(final boolean shouldFollow) {
161            this.follow = shouldFollow;
162            return this;
163        }
164
165        public Builder setIgnoreExceptions(final boolean shouldIgnoreExceptions) {
166            this.ignoreExceptions = shouldIgnoreExceptions;
167            return this;
168        }
169
170        @Override
171        public ConsoleAppender build() {
172            return new ConsoleAppender(name, layout, filter, getManager(target, follow, layout), ignoreExceptions);
173        }
174    }
175
176    private static OutputStreamManager getManager(final Target target, final boolean follow,
177            final Layout<? extends Serializable> layout) {
178        final OutputStream os = getOutputStream(follow, target);
179        final String managerName = target.name() + '.' + follow;
180        return OutputStreamManager.getManager(managerName, new FactoryData(os, managerName, layout), factory);
181    }
182
183    private static OutputStream getOutputStream(final boolean follow, final Target target) {
184        final String enc = Charset.defaultCharset().name();
185        OutputStream outputStream = null;
186        try {
187            // @formatter:off
188            outputStream = target == Target.SYSTEM_OUT ?
189                follow ? new PrintStream(new SystemOutStream(), true, enc) : System.out :
190                follow ? new PrintStream(new SystemErrStream(), true, enc) : System.err;
191            // @formatter:on
192            outputStream = new CloseShieldOutputStream(outputStream);
193        } catch (final UnsupportedEncodingException ex) { // should never happen
194            throw new IllegalStateException("Unsupported default encoding " + enc, ex);
195        }
196        final PropertiesUtil propsUtil = PropertiesUtil.getProperties();
197        if (!propsUtil.getStringProperty("os.name").startsWith("Windows")
198                || propsUtil.getBooleanProperty("log4j.skipJansi")) {
199            return outputStream;
200        }
201        try {
202            // We type the parameter as a wildcard to avoid a hard reference to Jansi.
203            final Class<?> clazz = Loader.loadClass(JANSI_CLASS);
204            final Constructor<?> constructor = clazz.getConstructor(OutputStream.class);
205            return new CloseShieldOutputStream((OutputStream) constructor.newInstance(outputStream));
206        } catch (final ClassNotFoundException cnfe) {
207            LOGGER.debug("Jansi is not installed, cannot find {}", JANSI_CLASS);
208        } catch (final NoSuchMethodException nsme) {
209            LOGGER.warn("{} is missing the proper constructor", JANSI_CLASS);
210        } catch (final Exception ex) {
211            LOGGER.warn("Unable to instantiate {}", JANSI_CLASS);
212        }
213        return outputStream;
214    }
215
216    /**
217     * An implementation of OutputStream that redirects to the current System.err.
218     */
219    private static class SystemErrStream extends OutputStream {
220        public SystemErrStream() {
221        }
222
223        @Override
224        public void close() {
225            // do not close sys err!
226        }
227
228        @Override
229        public void flush() {
230            System.err.flush();
231        }
232
233        @Override
234        public void write(final byte[] b) throws IOException {
235            System.err.write(b);
236        }
237
238        @Override
239        public void write(final byte[] b, final int off, final int len) 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) throws IOException {
273            System.out.write(b, off, len);
274        }
275
276        @Override
277        public void write(final int b) throws IOException {
278            System.out.write(b);
279        }
280    }
281
282    /**
283     * A delegating OutputStream that does not close its delegate.
284     */
285    private static class CloseShieldOutputStream extends OutputStream {
286
287        private final OutputStream delegate;
288
289        public CloseShieldOutputStream(final OutputStream delegate) {
290            this.delegate = delegate;
291        }
292
293        @Override
294        public void close() {
295            // do not close delegate
296        }
297
298        @Override
299        public void flush() throws IOException {
300            delegate.flush();
301        }
302
303        @Override
304        public void write(final byte[] b) throws IOException {
305            delegate.write(b);
306        }
307
308        @Override
309        public void write(final byte[] b, final int off, final int len) throws IOException {
310            delegate.write(b, off, len);
311        }
312
313        @Override
314        public void write(final int b) throws IOException {
315            delegate.write(b);
316        }
317    }
318
319    /**
320     * Data to pass to factory method.
321     */
322    private static class FactoryData {
323        private final OutputStream os;
324        private final String type;
325        private final Layout<? extends Serializable> layout;
326
327        /**
328         * Constructor.
329         * 
330         * @param os The OutputStream.
331         * @param type The name of the target.
332         * @param layout A Serializable layout
333         */
334        public FactoryData(final OutputStream os, final String type, final Layout<? extends Serializable> layout) {
335            this.os = os;
336            this.type = type;
337            this.layout = layout;
338        }
339    }
340
341    /**
342     * Factory to create the Appender.
343     */
344    private static class ConsoleManagerFactory implements ManagerFactory<OutputStreamManager, FactoryData> {
345
346        /**
347         * Create an OutputStreamManager.
348         * 
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, true);
356        }
357    }
358
359}