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