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.validation.constraints.Required; 035 import org.apache.logging.log4j.core.layout.PatternLayout; 036 import org.apache.logging.log4j.core.util.Booleans; 037 import org.apache.logging.log4j.core.util.Loader; 038 import org.apache.logging.log4j.util.PropertiesUtil; 039 040 /** 041 * ConsoleAppender appends log events to <code>System.out</code> or 042 * <code>System.err</code> using a layout specified by the user. The 043 * default target is <code>System.out</code>. 044 * TODO accessing System.out or .err as a byte stream instead of a writer 045 * bypasses the JVM's knowledge of the proper encoding. (RG) Encoding 046 * is handled within the Layout. Typically, a Layout will generate a String 047 * and then call getBytes which may use a configured encoding or the system 048 * default. OTOH, a Writer cannot print byte streams. 049 */ 050 @Plugin(name = "Console", category = "Core", elementType = "appender", printObject = true) 051 public final class ConsoleAppender extends AbstractOutputStreamAppender<OutputStreamManager> { 052 053 private static final long serialVersionUID = 1L; 054 private static final String JANSI_CLASS = "org.fusesource.jansi.WindowsAnsiOutputStream"; 055 private static ConsoleManagerFactory factory = new ConsoleManagerFactory(); 056 057 /** 058 * Enumeration of console destinations. 059 */ 060 public 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, 069 final boolean ignoreExceptions) { 070 super(name, layout, filter, ignoreExceptions, true, manager); 071 } 072 073 /** 074 * Create a Console Appender. 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 081 * they are propagated to the caller. 082 * @return The ConsoleAppender. 083 */ 084 public static ConsoleAppender createAppender( 085 @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 ? Target.SYSTEM_OUT : Target.valueOf(targetStr); 101 return new ConsoleAppender(name, layout, filter, getManager(isFollow, target, 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(false, Target.SYSTEM_OUT, layout), true); 107 } 108 109 @PluginBuilderFactory 110 public static Builder newBuilder() { 111 return new Builder(); 112 } 113 114 public static class Builder implements org.apache.logging.log4j.core.util.Builder<ConsoleAppender> { 115 116 @PluginElement("Layout") 117 @Required 118 private Layout<? extends Serializable> layout = PatternLayout.createDefaultLayout(); 119 120 @PluginElement("Filter") 121 private Filter filter; 122 123 @PluginBuilderAttribute 124 @Required 125 private Target target = Target.SYSTEM_OUT; 126 127 @PluginBuilderAttribute 128 @Required 129 private String name; 130 131 @PluginBuilderAttribute 132 private boolean follow = false; 133 134 @PluginBuilderAttribute 135 private boolean ignoreExceptions = true; 136 137 public Builder setLayout(final Layout<? extends Serializable> layout) { 138 this.layout = layout; 139 return this; 140 } 141 142 public Builder setFilter(final Filter filter) { 143 this.filter = filter; 144 return this; 145 } 146 147 public Builder setTarget(final Target target) { 148 this.target = target; 149 return this; 150 } 151 152 public Builder setName(final String name) { 153 this.name = name; 154 return this; 155 } 156 157 public Builder setFollow(final boolean follow) { 158 this.follow = follow; 159 return this; 160 } 161 162 public Builder setIgnoreExceptions(final boolean ignoreExceptions) { 163 this.ignoreExceptions = ignoreExceptions; 164 return this; 165 } 166 167 @Override 168 public ConsoleAppender build() { 169 return new ConsoleAppender(name, layout, filter, getManager(follow, target, layout), ignoreExceptions); 170 } 171 } 172 173 private static OutputStreamManager getManager(final boolean follow, final Target target, final Layout<? extends Serializable> layout) { 174 final String type = target.name(); 175 final OutputStream os = getOutputStream(follow, target); 176 return OutputStreamManager.getManager(target.name() + '.' + follow, new FactoryData(os, type, layout), factory); 177 } 178 179 private static OutputStream getOutputStream(final boolean follow, final Target target) { 180 final String enc = Charset.defaultCharset().name(); 181 PrintStream printStream = null; 182 try { 183 printStream = target == Target.SYSTEM_OUT ? 184 follow ? new PrintStream(new SystemOutStream(), true, enc) : System.out : 185 follow ? new PrintStream(new SystemErrStream(), true, enc) : System.err; 186 } catch (final UnsupportedEncodingException ex) { // should never happen 187 throw new IllegalStateException("Unsupported default encoding " + enc, ex); 188 } 189 final PropertiesUtil propsUtil = PropertiesUtil.getProperties(); 190 if (!propsUtil.getStringProperty("os.name").startsWith("Windows") || 191 propsUtil.getBooleanProperty("log4j.skipJansi")) { 192 return printStream; 193 } 194 try { 195 // We type the parameter as a wildcard to avoid a hard reference to Jansi. 196 final Class<?> clazz = Loader.loadClass(JANSI_CLASS); 197 final Constructor<?> constructor = clazz.getConstructor(OutputStream.class); 198 return (OutputStream) constructor.newInstance(printStream); 199 } catch (final ClassNotFoundException cnfe) { 200 LOGGER.debug("Jansi is not installed, cannot find {}", JANSI_CLASS); 201 } catch (final NoSuchMethodException nsme) { 202 LOGGER.warn("{} is missing the proper constructor", JANSI_CLASS); 203 } catch (final Exception ex) { 204 LOGGER.warn("Unable to instantiate {}", JANSI_CLASS); 205 } 206 return printStream; 207 } 208 209 /** 210 * An implementation of OutputStream that redirects to the current System.err. 211 */ 212 private static class SystemErrStream extends OutputStream { 213 public SystemErrStream() { 214 } 215 216 @Override 217 public void close() { 218 // do not close sys err! 219 } 220 221 @Override 222 public void flush() { 223 System.err.flush(); 224 } 225 226 @Override 227 public void write(final byte[] b) throws IOException { 228 System.err.write(b); 229 } 230 231 @Override 232 public void write(final byte[] b, final int off, final int len) 233 throws IOException { 234 System.err.write(b, off, len); 235 } 236 237 @Override 238 public void write(final int b) { 239 System.err.write(b); 240 } 241 } 242 243 /** 244 * An implementation of OutputStream that redirects to the current System.out. 245 */ 246 private static class SystemOutStream extends OutputStream { 247 public SystemOutStream() { 248 } 249 250 @Override 251 public void close() { 252 // do not close sys out! 253 } 254 255 @Override 256 public void flush() { 257 System.out.flush(); 258 } 259 260 @Override 261 public void write(final byte[] b) throws IOException { 262 System.out.write(b); 263 } 264 265 @Override 266 public void write(final byte[] b, final int off, final int len) 267 throws IOException { 268 System.out.write(b, off, len); 269 } 270 271 @Override 272 public void write(final int b) throws IOException { 273 System.out.write(b); 274 } 275 } 276 277 /** 278 * Data to pass to factory method. 279 */ 280 private static class FactoryData { 281 private final OutputStream os; 282 private final String type; 283 private final Layout<? extends Serializable> layout; 284 285 /** 286 * Constructor. 287 * @param os The OutputStream. 288 * @param type The name of the target. 289 * @param layout A Serializable layout 290 */ 291 public FactoryData(final OutputStream os, final String type, final Layout<? extends Serializable> layout) { 292 this.os = os; 293 this.type = type; 294 this.layout = layout; 295 } 296 } 297 298 /** 299 * Factory to create the Appender. 300 */ 301 private static class ConsoleManagerFactory implements ManagerFactory<OutputStreamManager, FactoryData> { 302 303 /** 304 * Create an OutputStreamManager. 305 * @param name The name of the entity to manage. 306 * @param data The data required to create the entity. 307 * @return The OutputStreamManager 308 */ 309 @Override 310 public OutputStreamManager createManager(final String name, final FactoryData data) { 311 return new OutputStreamManager(data.os, data.type, data.layout); 312 } 313 } 314 315 }