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