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 }