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