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