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.PluginElement; 032 import org.apache.logging.log4j.core.config.plugins.PluginFactory; 033 import org.apache.logging.log4j.core.layout.PatternLayout; 034 import org.apache.logging.log4j.core.util.Booleans; 035 import org.apache.logging.log4j.core.util.Loader; 036 import org.apache.logging.log4j.util.PropertiesUtil; 037 038 /** 039 * ConsoleAppender appends log events to <code>System.out</code> or 040 * <code>System.err</code> using a layout specified by the user. The 041 * default target is <code>System.out</code>. 042 * @doubt accessing System.out or .err as a byte stream instead of a writer 043 * bypasses the JVM's knowledge of the proper encoding. (RG) Encoding 044 * is handled within the Layout. Typically, a Layout will generate a String 045 * and then call getBytes which may use a configured encoding or the system 046 * default. OTOH, a Writer cannot print byte streams. 047 */ 048 @Plugin(name = "Console", category = "Core", elementType = "appender", printObject = true) 049 public final class ConsoleAppender extends AbstractOutputStreamAppender<OutputStreamManager> { 050 051 private static final String JANSI_CLASS = "org.fusesource.jansi.WindowsAnsiOutputStream"; 052 private static ConsoleManagerFactory factory = new ConsoleManagerFactory(); 053 054 /** 055 * Enumeration of console destinations. 056 */ 057 public enum Target { 058 /** Standard output. */ 059 SYSTEM_OUT, 060 /** Standard error output. */ 061 SYSTEM_ERR 062 } 063 064 private ConsoleAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter, 065 final OutputStreamManager manager, 066 final boolean ignoreExceptions) { 067 super(name, layout, filter, ignoreExceptions, true, manager); 068 } 069 070 /** 071 * Create a Console Appender. 072 * @param layout The layout to use (required). 073 * @param filter The Filter or null. 074 * @param targetStr The target ("SYSTEM_OUT" or "SYSTEM_ERR"). The default is "SYSTEM_OUT". 075 * @param follow If true will follow changes to the underlying output stream. 076 * @param name The name of the Appender (required). 077 * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise 078 * they are propagated to the caller. 079 * @return The ConsoleAppender. 080 */ 081 @PluginFactory 082 public static ConsoleAppender createAppender( 083 @PluginElement("Layout") Layout<? extends Serializable> layout, 084 @PluginElement("Filter") final Filter filter, 085 @PluginAttribute(value = "target", defaultString = "SYSTEM_OUT") final String targetStr, 086 @PluginAttribute("name") final String name, 087 @PluginAttribute(value = "follow", defaultBoolean = false) final String follow, 088 @PluginAttribute(value = "ignoreExceptions", defaultBoolean = true) final String ignore) { 089 if (name == null) { 090 LOGGER.error("No name provided for ConsoleAppender"); 091 return null; 092 } 093 if (layout == null) { 094 layout = PatternLayout.createDefaultLayout(); 095 } 096 final boolean isFollow = Boolean.parseBoolean(follow); 097 final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); 098 final Target target = targetStr == null ? Target.SYSTEM_OUT : Target.valueOf(targetStr); 099 return new ConsoleAppender(name, layout, filter, getManager(isFollow, target, layout), ignoreExceptions); 100 } 101 102 private static OutputStreamManager getManager(final boolean follow, final Target target, final Layout<? extends Serializable> layout) { 103 final String type = target.name(); 104 final OutputStream os = getOutputStream(follow, target); 105 return OutputStreamManager.getManager(target.name() + '.' + follow, new FactoryData(os, type, layout), factory); 106 } 107 108 private static OutputStream getOutputStream(final boolean follow, final Target target) { 109 final String enc = Charset.defaultCharset().name(); 110 PrintStream printStream = null; 111 try { 112 printStream = target == Target.SYSTEM_OUT ? 113 follow ? new PrintStream(new SystemOutStream(), true, enc) : System.out : 114 follow ? new PrintStream(new SystemErrStream(), true, enc) : System.err; 115 } catch (final UnsupportedEncodingException ex) { // should never happen 116 throw new IllegalStateException("Unsupported default encoding " + enc, ex); 117 } 118 final PropertiesUtil propsUtil = PropertiesUtil.getProperties(); 119 if (!propsUtil.getStringProperty("os.name").startsWith("Windows") || 120 propsUtil.getBooleanProperty("log4j.skipJansi")) { 121 return printStream; 122 } 123 try { 124 final ClassLoader loader = Loader.getClassLoader(); 125 // We type the parameter as a wildcard to avoid a hard reference to Jansi. 126 final Class<?> clazz = loader.loadClass(JANSI_CLASS); 127 final Constructor<?> constructor = clazz.getConstructor(OutputStream.class); 128 return (OutputStream) constructor.newInstance(printStream); 129 } catch (final ClassNotFoundException cnfe) { 130 LOGGER.debug("Jansi is not installed, cannot find {}", JANSI_CLASS); 131 } catch (final NoSuchMethodException nsme) { 132 LOGGER.warn("{} is missing the proper constructor", JANSI_CLASS); 133 } catch (final Exception ex) { 134 LOGGER.warn("Unable to instantiate {}", JANSI_CLASS); 135 } 136 return printStream; 137 } 138 139 /** 140 * An implementation of OutputStream that redirects to the current System.err. 141 */ 142 private static class SystemErrStream extends OutputStream { 143 public SystemErrStream() { 144 } 145 146 @Override 147 public void close() { 148 // do not close sys err! 149 } 150 151 @Override 152 public void flush() { 153 System.err.flush(); 154 } 155 156 @Override 157 public void write(final byte[] b) throws IOException { 158 System.err.write(b); 159 } 160 161 @Override 162 public void write(final byte[] b, final int off, final int len) 163 throws IOException { 164 System.err.write(b, off, len); 165 } 166 167 @Override 168 public void write(final int b) { 169 System.err.write(b); 170 } 171 } 172 173 /** 174 * An implementation of OutputStream that redirects to the current System.out. 175 */ 176 private static class SystemOutStream extends OutputStream { 177 public SystemOutStream() { 178 } 179 180 @Override 181 public void close() { 182 // do not close sys out! 183 } 184 185 @Override 186 public void flush() { 187 System.out.flush(); 188 } 189 190 @Override 191 public void write(final byte[] b) throws IOException { 192 System.out.write(b); 193 } 194 195 @Override 196 public void write(final byte[] b, final int off, final int len) 197 throws IOException { 198 System.out.write(b, off, len); 199 } 200 201 @Override 202 public void write(final int b) throws IOException { 203 System.out.write(b); 204 } 205 } 206 207 /** 208 * Data to pass to factory method. 209 */ 210 private static class FactoryData { 211 private final OutputStream os; 212 private final String type; 213 private final Layout<? extends Serializable> layout; 214 215 /** 216 * Constructor. 217 * @param os The OutputStream. 218 * @param type The name of the target. 219 * @param layout A Serializable layout 220 */ 221 public FactoryData(final OutputStream os, final String type, final Layout<? extends Serializable> layout) { 222 this.os = os; 223 this.type = type; 224 this.layout = layout; 225 } 226 } 227 228 /** 229 * Factory to create the Appender. 230 */ 231 private static class ConsoleManagerFactory implements ManagerFactory<OutputStreamManager, FactoryData> { 232 233 /** 234 * Create an OutputStreamManager. 235 * @param name The name of the entity to manage. 236 * @param data The data required to create the entity. 237 * @return The OutputStreamManager 238 */ 239 @Override 240 public OutputStreamManager createManager(final String name, final FactoryData data) { 241 return new OutputStreamManager(data.os, data.type, data.layout); 242 } 243 } 244 245 }