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 * ConsoleAppender appends log events to <code>System.out</code> or 043 * <code>System.err</code> using a layout specified by the user. The 044 * default target is <code>System.out</code>. 045 * TODO accessing System.out or .err as a byte stream instead of a writer 046 * bypasses the JVM's knowledge of the proper encoding. (RG) Encoding 047 * is handled within the Layout. Typically, a Layout will generate a String 048 * and then call getBytes which may use a configured encoding or the system 049 * 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 059 /** 060 * Enumeration of console destinations. 061 */ 062 public enum Target { 063 /** Standard output. */ 064 SYSTEM_OUT, 065 /** Standard error output. */ 066 SYSTEM_ERR 067 } 068 069 private ConsoleAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter, 070 final OutputStreamManager manager, 071 final boolean ignoreExceptions) { 072 super(name, layout, filter, ignoreExceptions, true, manager); 073 } 074 075 /** 076 * Create a Console Appender. 077 * @param layout The layout to use (required). 078 * @param filter The Filter or null. 079 * @param targetStr The target ("SYSTEM_OUT" or "SYSTEM_ERR"). The default is "SYSTEM_OUT". 080 * @param follow If true will follow changes to the underlying output stream. 081 * @param name The name of the Appender (required). 082 * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise 083 * they are propagated to the caller. 084 * @return The ConsoleAppender. 085 */ 086 @PluginFactory 087 public static ConsoleAppender createAppender( 088 @PluginElement("Layout") Layout<? extends Serializable> layout, 089 @PluginElement("Filter") final Filter filter, 090 @PluginAttribute(value = "target", defaultString = "SYSTEM_OUT") final String targetStr, 091 @PluginAttribute("name") final String name, 092 @PluginAttribute(value = "follow", defaultBoolean = false) final String follow, 093 @PluginAttribute(value = "ignoreExceptions", defaultBoolean = true) final String ignore) { 094 if (name == null) { 095 LOGGER.error("No name provided for ConsoleAppender"); 096 return null; 097 } 098 if (layout == null) { 099 layout = PatternLayout.createDefaultLayout(); 100 } 101 final boolean isFollow = Boolean.parseBoolean(follow); 102 final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); 103 final Target target = targetStr == null ? DEFAULT_TARGET : Target.valueOf(targetStr); 104 return new ConsoleAppender(name, layout, filter, getManager(isFollow, target, layout), ignoreExceptions); 105 } 106 107 public static ConsoleAppender createDefaultAppenderForLayout(final Layout<? extends Serializable> layout) { 108 // this method cannot use the builder class without introducing an infinite loop due to DefaultConfiguration 109 return new ConsoleAppender("Console", layout, null, getManager(false, DEFAULT_TARGET, layout), true); 110 } 111 112 @PluginBuilderFactory 113 public static Builder newBuilder() { 114 return new Builder(); 115 } 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> layout) { 141 this.layout = layout; 142 return this; 143 } 144 145 public Builder setFilter(final Filter filter) { 146 this.filter = filter; 147 return this; 148 } 149 150 public Builder setTarget(final Target target) { 151 this.target = target; 152 return this; 153 } 154 155 public Builder setName(final String name) { 156 this.name = name; 157 return this; 158 } 159 160 public Builder setFollow(final boolean follow) { 161 this.follow = follow; 162 return this; 163 } 164 165 public Builder setIgnoreExceptions(final boolean ignoreExceptions) { 166 this.ignoreExceptions = ignoreExceptions; 167 return this; 168 } 169 170 @Override 171 public ConsoleAppender build() { 172 return new ConsoleAppender(name, layout, filter, getManager(follow, target, layout), ignoreExceptions); 173 } 174 } 175 176 private static OutputStreamManager getManager(final boolean follow, final Target target, final Layout<? extends Serializable> layout) { 177 final String type = target.name(); 178 final OutputStream os = getOutputStream(follow, target); 179 return OutputStreamManager.getManager(target.name() + '.' + follow, new FactoryData(os, type, layout), factory); 180 } 181 182 private static OutputStream getOutputStream(final boolean follow, final Target target) { 183 final String enc = Charset.defaultCharset().name(); 184 OutputStream outputStream = null; 185 try { 186 // @formatter:off 187 outputStream = target == Target.SYSTEM_OUT ? 188 follow ? new PrintStream(new SystemOutStream(), true, enc) : System.out : 189 follow ? new PrintStream(new SystemErrStream(), true, enc) : System.err; 190 // @formatter:on 191 outputStream = new CloseShieldOutputStream(outputStream); 192 } catch (final UnsupportedEncodingException ex) { // should never happen 193 throw new IllegalStateException("Unsupported default encoding " + enc, ex); 194 } 195 final PropertiesUtil propsUtil = PropertiesUtil.getProperties(); 196 if (!propsUtil.getStringProperty("os.name").startsWith("Windows") 197 || propsUtil.getBooleanProperty("log4j.skipJansi")) { 198 return outputStream; 199 } 200 try { 201 // We type the parameter as a wildcard to avoid a hard reference to Jansi. 202 final Class<?> clazz = Loader.loadClass(JANSI_CLASS); 203 final Constructor<?> constructor = clazz.getConstructor(OutputStream.class); 204 return new CloseShieldOutputStream((OutputStream) constructor.newInstance(outputStream)); 205 } catch (final ClassNotFoundException cnfe) { 206 LOGGER.debug("Jansi is not installed, cannot find {}", JANSI_CLASS); 207 } catch (final NoSuchMethodException nsme) { 208 LOGGER.warn("{} is missing the proper constructor", JANSI_CLASS); 209 } catch (final Exception ex) { 210 LOGGER.warn("Unable to instantiate {}", JANSI_CLASS); 211 } 212 return outputStream; 213 } 214 215 /** 216 * An implementation of OutputStream that redirects to the current System.err. 217 */ 218 private static class SystemErrStream extends OutputStream { 219 public SystemErrStream() { 220 } 221 222 @Override 223 public void close() { 224 // do not close sys err! 225 } 226 227 @Override 228 public void flush() { 229 System.err.flush(); 230 } 231 232 @Override 233 public void write(final byte[] b) throws IOException { 234 System.err.write(b); 235 } 236 237 @Override 238 public void write(final byte[] b, final int off, final int len) 239 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) 273 throws IOException { 274 System.out.write(b, off, len); 275 } 276 277 @Override 278 public void write(final int b) throws IOException { 279 System.out.write(b); 280 } 281 } 282 283 /** 284 * A delegating OutputStream that does not close its delegate. 285 */ 286 private static class CloseShieldOutputStream extends OutputStream { 287 288 private final OutputStream delegate; 289 290 public CloseShieldOutputStream(final OutputStream delegate) { 291 this.delegate = delegate; 292 } 293 294 @Override 295 public void close() { 296 // do not close delegate 297 } 298 299 @Override 300 public void flush() throws IOException { 301 delegate.flush(); 302 } 303 304 @Override 305 public void write(final byte[] b) throws IOException { 306 delegate.write(b); 307 } 308 309 @Override 310 public void write(final byte[] b, final int off, final int len) 311 throws IOException { 312 delegate.write(b, off, len); 313 } 314 315 @Override 316 public void write(final int b) throws IOException { 317 delegate.write(b); 318 } 319 } 320 321 /** 322 * Data to pass to factory method. 323 */ 324 private static class FactoryData { 325 private final OutputStream os; 326 private final String type; 327 private final Layout<? extends Serializable> layout; 328 329 /** 330 * Constructor. 331 * @param os The OutputStream. 332 * @param type The name of the target. 333 * @param layout A Serializable layout 334 */ 335 public FactoryData(final OutputStream os, final String type, final Layout<? extends Serializable> layout) { 336 this.os = os; 337 this.type = type; 338 this.layout = layout; 339 } 340 } 341 342 /** 343 * Factory to create the Appender. 344 */ 345 private static class ConsoleManagerFactory implements ManagerFactory<OutputStreamManager, FactoryData> { 346 347 /** 348 * Create an OutputStreamManager. 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); 356 } 357 } 358 359}