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