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.FileDescriptor; 020import java.io.FileOutputStream; 021import java.io.IOException; 022import java.io.OutputStream; 023import java.io.PrintStream; 024import java.io.Serializable; 025import java.io.UnsupportedEncodingException; 026import java.lang.reflect.Constructor; 027import java.nio.charset.Charset; 028import java.util.concurrent.atomic.AtomicInteger; 029 030import org.apache.logging.log4j.core.Appender; 031import org.apache.logging.log4j.core.Core; 032import org.apache.logging.log4j.core.Filter; 033import org.apache.logging.log4j.core.Layout; 034import org.apache.logging.log4j.core.config.plugins.Plugin; 035import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; 036import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; 037import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; 038import org.apache.logging.log4j.core.layout.PatternLayout; 039import org.apache.logging.log4j.core.util.Booleans; 040import org.apache.logging.log4j.core.util.CloseShieldOutputStream; 041import org.apache.logging.log4j.core.util.Throwables; 042import org.apache.logging.log4j.util.Chars; 043import org.apache.logging.log4j.util.LoaderUtil; 044import org.apache.logging.log4j.util.PropertiesUtil; 045 046/** 047 * Appends log events to <code>System.out</code> or <code>System.err</code> using a layout specified by the user. The 048 * default target is <code>System.out</code>. 049 * <p> 050 * TODO Accessing <code>System.out</code> or <code>System.err</code> as a byte stream instead of a writer bypasses the 051 * JVM's knowledge of the proper encoding. (RG) Encoding is handled within the Layout. Typically, a Layout will generate 052 * a String and then call getBytes which may use a configured encoding or the system default. OTOH, a Writer cannot 053 * print byte streams. 054 * </p> 055 */ 056@Plugin(name = ConsoleAppender.PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) 057public final class ConsoleAppender extends AbstractOutputStreamAppender<OutputStreamManager> { 058 059 public static final String PLUGIN_NAME = "Console"; 060 private static final String JANSI_CLASS = "org.fusesource.jansi.WindowsAnsiOutputStream"; 061 private static ConsoleManagerFactory factory = new ConsoleManagerFactory(); 062 private static final Target DEFAULT_TARGET = Target.SYSTEM_OUT; 063 private static final AtomicInteger COUNT = new AtomicInteger(); 064 065 private final Target target; 066 067 /** 068 * Enumeration of console destinations. 069 */ 070 public enum Target { 071 072 /** Standard output. */ 073 SYSTEM_OUT { 074 @Override 075 public Charset getDefaultCharset() { 076 // "sun.stdout.encoding" is only set when running from the console. 077 return getCharset("sun.stdout.encoding", Charset.defaultCharset()); 078 } 079 }, 080 081 /** Standard error output. */ 082 SYSTEM_ERR { 083 @Override 084 public Charset getDefaultCharset() { 085 // "sun.stderr.encoding" is only set when running from the console. 086 return getCharset("sun.stderr.encoding", Charset.defaultCharset()); 087 } 088 }; 089 090 public abstract Charset getDefaultCharset(); 091 092 protected Charset getCharset(final String property, Charset defaultCharset) { 093 return new PropertiesUtil(PropertiesUtil.getSystemProperties()).getCharsetProperty(property, defaultCharset); 094 } 095 096 } 097 098 private ConsoleAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter, 099 final OutputStreamManager manager, final boolean ignoreExceptions, final Target target) { 100 super(name, layout, filter, ignoreExceptions, true, manager); 101 this.target = target; 102 } 103 104 /** 105 * Creates a Console Appender. 106 * 107 * @param layout The layout to use (required). 108 * @param filter The Filter or null. 109 * @param targetStr The target ("SYSTEM_OUT" or "SYSTEM_ERR"). The default is "SYSTEM_OUT". 110 * @param name The name of the Appender (required). 111 * @param follow If true will follow changes to the underlying output stream. 112 * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they 113 * are propagated to the caller. 114 * @return The ConsoleAppender. 115 * @deprecated Deprecated in 2.7; use {@link #newBuilder()}. 116 */ 117 @Deprecated 118 public static ConsoleAppender createAppender(Layout<? extends Serializable> layout, 119 final Filter filter, 120 final String targetStr, 121 final String name, 122 final String follow, 123 final String ignore) { 124 if (name == null) { 125 LOGGER.error("No name provided for ConsoleAppender"); 126 return null; 127 } 128 if (layout == null) { 129 layout = PatternLayout.createDefaultLayout(); 130 } 131 final boolean isFollow = Boolean.parseBoolean(follow); 132 final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); 133 final Target target = targetStr == null ? DEFAULT_TARGET : Target.valueOf(targetStr); 134 return new ConsoleAppender(name, layout, filter, getManager(target, isFollow, false, layout), ignoreExceptions, target); 135 } 136 137 /** 138 * Creates a Console Appender. 139 * 140 * @param layout The layout to use (required). 141 * @param filter The Filter or null. 142 * @param target The target (SYSTEM_OUT or SYSTEM_ERR). The default is SYSTEM_OUT. 143 * @param name The name of the Appender (required). 144 * @param follow If true will follow changes to the underlying output stream. 145 * @param direct If true will write directly to {@link java.io.FileDescriptor} and bypass 146 * {@link System#out}/{@link System#err}. 147 * @param ignoreExceptions If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they 148 * are propagated to the caller. 149 * @return The ConsoleAppender. 150 * @deprecated Deprecated in 2.7; use {@link #newBuilder()}. 151 */ 152 @Deprecated 153 public static ConsoleAppender createAppender( 154 // @formatter:off 155 Layout<? extends Serializable> layout, 156 final Filter filter, 157 Target target, 158 final String name, 159 final boolean follow, 160 final boolean direct, 161 final boolean ignoreExceptions) { 162 // @formatter:on 163 if (name == null) { 164 LOGGER.error("No name provided for ConsoleAppender"); 165 return null; 166 } 167 if (layout == null) { 168 layout = PatternLayout.createDefaultLayout(); 169 } 170 target = target == null ? Target.SYSTEM_OUT : target; 171 if (follow && direct) { 172 LOGGER.error("Cannot use both follow and direct on ConsoleAppender"); 173 return null; 174 } 175 return new ConsoleAppender(name, layout, filter, getManager(target, follow, direct, layout), ignoreExceptions, target); 176 } 177 178 public static ConsoleAppender createDefaultAppenderForLayout(final Layout<? extends Serializable> layout) { 179 // this method cannot use the builder class without introducing an infinite loop due to DefaultConfiguration 180 return new ConsoleAppender("DefaultConsole-" + COUNT.incrementAndGet(), layout, null, 181 getDefaultManager(DEFAULT_TARGET, false, false, layout), true, DEFAULT_TARGET); 182 } 183 184 @PluginBuilderFactory 185 public static <B extends Builder<B>> B newBuilder() { 186 return new Builder<B>().asBuilder(); 187 } 188 189 /** 190 * Builds ConsoleAppender instances. 191 * @param <B> The type to build 192 */ 193 public static class Builder<B extends Builder<B>> extends AbstractOutputStreamAppender.Builder<B> 194 implements org.apache.logging.log4j.core.util.Builder<ConsoleAppender> { 195 196 @PluginBuilderAttribute 197 @Required 198 private Target target = DEFAULT_TARGET; 199 200 @PluginBuilderAttribute 201 private boolean follow; 202 203 @PluginBuilderAttribute 204 private boolean direct; 205 206 public B setTarget(final Target aTarget) { 207 this.target = aTarget; 208 return asBuilder(); 209 } 210 211 public B setFollow(final boolean shouldFollow) { 212 this.follow = shouldFollow; 213 return asBuilder(); 214 } 215 216 public B setDirect(final boolean shouldDirect) { 217 this.direct = shouldDirect; 218 return asBuilder(); 219 } 220 221 @Override 222 public ConsoleAppender build() { 223 if (follow && direct) { 224 throw new IllegalArgumentException("Cannot use both follow and direct on ConsoleAppender '" + getName() + "'"); 225 } 226 final Layout<? extends Serializable> layout = getOrCreateLayout(target.getDefaultCharset()); 227 return new ConsoleAppender(getName(), layout, getFilter(), getManager(target, follow, direct, layout), 228 isIgnoreExceptions(), target); 229 } 230 } 231 232 private static OutputStreamManager getDefaultManager(final Target target, final boolean follow, final boolean direct, 233 final Layout<? extends Serializable> layout) { 234 final OutputStream os = getOutputStream(follow, direct, target); 235 236 // LOG4J2-1176 DefaultConfiguration should not share OutputStreamManager instances to avoid memory leaks. 237 final String managerName = target.name() + '.' + follow + '.' + direct + "-" + COUNT.get(); 238 return OutputStreamManager.getManager(managerName, new FactoryData(os, managerName, layout), factory); 239 } 240 241 private static OutputStreamManager getManager(final Target target, final boolean follow, final boolean direct, 242 final Layout<? extends Serializable> layout) { 243 final OutputStream os = getOutputStream(follow, direct, target); 244 final String managerName = target.name() + '.' + follow + '.' + direct; 245 return OutputStreamManager.getManager(managerName, new FactoryData(os, managerName, layout), factory); 246 } 247 248 private static OutputStream getOutputStream(final boolean follow, final boolean direct, final Target target) { 249 final String enc = Charset.defaultCharset().name(); 250 OutputStream outputStream; 251 try { 252 // @formatter:off 253 outputStream = target == Target.SYSTEM_OUT ? 254 direct ? new FileOutputStream(FileDescriptor.out) : 255 (follow ? new PrintStream(new SystemOutStream(), true, enc) : System.out) : 256 direct ? new FileOutputStream(FileDescriptor.err) : 257 (follow ? new PrintStream(new SystemErrStream(), true, enc) : System.err); 258 // @formatter:on 259 outputStream = new CloseShieldOutputStream(outputStream); 260 } catch (final UnsupportedEncodingException ex) { // should never happen 261 throw new IllegalStateException("Unsupported default encoding " + enc, ex); 262 } 263 final PropertiesUtil propsUtil = PropertiesUtil.getProperties(); 264 if (!propsUtil.isOsWindows() || propsUtil.getBooleanProperty("log4j.skipJansi", true) || direct) { 265 return outputStream; 266 } 267 try { 268 // We type the parameter as a wildcard to avoid a hard reference to Jansi. 269 final Class<?> clazz = LoaderUtil.loadClass(JANSI_CLASS); 270 final Constructor<?> constructor = clazz.getConstructor(OutputStream.class); 271 return new CloseShieldOutputStream((OutputStream) constructor.newInstance(outputStream)); 272 } catch (final ClassNotFoundException cnfe) { 273 LOGGER.debug("Jansi is not installed, cannot find {}", JANSI_CLASS); 274 } catch (final NoSuchMethodException nsme) { 275 LOGGER.warn("{} is missing the proper constructor", JANSI_CLASS); 276 } catch (final Exception ex) { 277 LOGGER.warn("Unable to instantiate {} due to {}", JANSI_CLASS, clean(Throwables.getRootCause(ex).toString()).trim()); 278 } 279 return outputStream; 280 } 281 282 private static String clean(String string) { 283 return string.replace(Chars.NUL, Chars.SPACE); 284 } 285 286 /** 287 * An implementation of OutputStream that redirects to the current System.err. 288 */ 289 private static class SystemErrStream extends OutputStream { 290 public SystemErrStream() { 291 } 292 293 @Override 294 public void close() { 295 // do not close sys err! 296 } 297 298 @Override 299 public void flush() { 300 System.err.flush(); 301 } 302 303 @Override 304 public void write(final byte[] b) throws IOException { 305 System.err.write(b); 306 } 307 308 @Override 309 public void write(final byte[] b, final int off, final int len) throws IOException { 310 System.err.write(b, off, len); 311 } 312 313 @Override 314 public void write(final int b) { 315 System.err.write(b); 316 } 317 } 318 319 /** 320 * An implementation of OutputStream that redirects to the current System.out. 321 */ 322 private static class SystemOutStream extends OutputStream { 323 public SystemOutStream() { 324 } 325 326 @Override 327 public void close() { 328 // do not close sys out! 329 } 330 331 @Override 332 public void flush() { 333 System.out.flush(); 334 } 335 336 @Override 337 public void write(final byte[] b) throws IOException { 338 System.out.write(b); 339 } 340 341 @Override 342 public void write(final byte[] b, final int off, final int len) throws IOException { 343 System.out.write(b, off, len); 344 } 345 346 @Override 347 public void write(final int b) throws IOException { 348 System.out.write(b); 349 } 350 } 351 352 /** 353 * Data to pass to factory method.Unable to instantiate 354 */ 355 private static class FactoryData { 356 private final OutputStream os; 357 private final String name; 358 private final Layout<? extends Serializable> layout; 359 360 /** 361 * Constructor. 362 * 363 * @param os The OutputStream. 364 * @param type The name of the target. 365 * @param layout A Serializable layout 366 */ 367 public FactoryData(final OutputStream os, final String type, final Layout<? extends Serializable> layout) { 368 this.os = os; 369 this.name = type; 370 this.layout = layout; 371 } 372 } 373 374 /** 375 * Factory to create the Appender. 376 */ 377 private static class ConsoleManagerFactory implements ManagerFactory<OutputStreamManager, FactoryData> { 378 379 /** 380 * Create an OutputStreamManager. 381 * 382 * @param name The name of the entity to manage. 383 * @param data The data required to create the entity. 384 * @return The OutputStreamManager 385 */ 386 @Override 387 public OutputStreamManager createManager(final String name, final FactoryData data) { 388 return new OutputStreamManager(data.os, data.name, data.layout, true); 389 } 390 } 391 392 public Target getTarget() { 393 return target; 394 } 395 396}