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