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 // We type the parameter as a wildcard to avoid a hard reference to Jansi. 125 final Class<?> clazz = Loader.loadClass(JANSI_CLASS); 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, cannot find {}", JANSI_CLASS); 130 } catch (final NoSuchMethodException nsme) { 131 LOGGER.warn("{} is missing the proper constructor", JANSI_CLASS); 132 } catch (final Exception ex) { 133 LOGGER.warn("Unable to instantiate {}", JANSI_CLASS); 134 } 135 return printStream; 136 } 137 138 /** 139 * An implementation of OutputStream that redirects to the current System.err. 140 */ 141 private static class SystemErrStream extends OutputStream { 142 public SystemErrStream() { 143 } 144 145 @Override 146 public void close() { 147 // do not close sys err! 148 } 149 150 @Override 151 public void flush() { 152 System.err.flush(); 153 } 154 155 @Override 156 public void write(final byte[] b) throws IOException { 157 System.err.write(b); 158 } 159 160 @Override 161 public void write(final byte[] b, final int off, final int len) 162 throws IOException { 163 System.err.write(b, off, len); 164 } 165 166 @Override 167 public void write(final int b) { 168 System.err.write(b); 169 } 170 } 171 172 /** 173 * An implementation of OutputStream that redirects to the current System.out. 174 */ 175 private static class SystemOutStream extends OutputStream { 176 public SystemOutStream() { 177 } 178 179 @Override 180 public void close() { 181 // do not close sys out! 182 } 183 184 @Override 185 public void flush() { 186 System.out.flush(); 187 } 188 189 @Override 190 public void write(final byte[] b) throws IOException { 191 System.out.write(b); 192 } 193 194 @Override 195 public void write(final byte[] b, final int off, final int len) 196 throws IOException { 197 System.out.write(b, off, len); 198 } 199 200 @Override 201 public void write(final int b) throws IOException { 202 System.out.write(b); 203 } 204 } 205 206 /** 207 * Data to pass to factory method. 208 */ 209 private static class FactoryData { 210 private final OutputStream os; 211 private final String type; 212 private final Layout<? extends Serializable> layout; 213 214 /** 215 * Constructor. 216 * @param os The OutputStream. 217 * @param type The name of the target. 218 * @param layout A Serializable layout 219 */ 220 public FactoryData(final OutputStream os, final String type, final Layout<? extends Serializable> layout) { 221 this.os = os; 222 this.type = type; 223 this.layout = layout; 224 } 225 } 226 227 /** 228 * Factory to create the Appender. 229 */ 230 private static class ConsoleManagerFactory implements ManagerFactory<OutputStreamManager, FactoryData> { 231 232 /** 233 * Create an OutputStreamManager. 234 * @param name The name of the entity to manage. 235 * @param data The data required to create the entity. 236 * @return The OutputStreamManager 237 */ 238 @Override 239 public OutputStreamManager createManager(final String name, final FactoryData data) { 240 return new OutputStreamManager(data.os, data.type, data.layout); 241 } 242 } 243 244 }