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