package org.apache.logging.log4j.core.appender;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.core.util.Booleans;
import org.apache.logging.log4j.core.util.CloseShieldOutputStream;
import org.apache.logging.log4j.core.util.Loader;
import org.apache.logging.log4j.core.util.Throwables;
import org.apache.logging.log4j.util.Chars;
import org.apache.logging.log4j.util.PropertiesUtil;
import java.io.*;
import java.lang.reflect.Constructor;
import java.nio.charset.Charset;
import java.util.concurrent.atomic.AtomicInteger;
@Plugin(name = ConsoleAppender.PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
public final class ConsoleAppender extends AbstractOutputStreamAppender<OutputStreamManager> {
public static final String PLUGIN_NAME = "Console";
private static final String JANSI_CLASS = "org.fusesource.jansi.WindowsAnsiOutputStream";
private static ConsoleManagerFactory factory = new ConsoleManagerFactory();
private static final Target DEFAULT_TARGET = Target.SYSTEM_OUT;
private static final AtomicInteger COUNT = new AtomicInteger();
private final Target target;
public enum Target {
SYSTEM_OUT {
@Override
public Charset getDefaultCharset() {
return getCharset("sun.stdout.encoding", Charset.defaultCharset());
}
},
SYSTEM_ERR {
@Override
public Charset getDefaultCharset() {
return getCharset("sun.stderr.encoding", Charset.defaultCharset());
}
};
public abstract Charset getDefaultCharset();
protected Charset getCharset(final String property, final Charset defaultCharset) {
return new PropertiesUtil(PropertiesUtil.getSystemProperties()).getCharsetProperty(property, defaultCharset);
}
}
private ConsoleAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
final OutputStreamManager manager, final boolean ignoreExceptions, final Target target,
final Property[] properties) {
super(name, layout, filter, ignoreExceptions, true, properties, manager);
this.target = target;
}
@Deprecated
public static ConsoleAppender createAppender(Layout<? extends Serializable> layout,
final Filter filter,
final String targetStr,
final String name,
final String follow,
final String ignore) {
if (name == null) {
LOGGER.error("No name provided for ConsoleAppender");
return null;
}
if (layout == null) {
layout = PatternLayout.createDefaultLayout();
}
final boolean isFollow = Boolean.parseBoolean(follow);
final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
final Target target = targetStr == null ? DEFAULT_TARGET : Target.valueOf(targetStr);
return new ConsoleAppender(name, layout, filter, getManager(target, isFollow, false, layout), ignoreExceptions, target, null);
}
@Deprecated
public static ConsoleAppender createAppender(
Layout<? extends Serializable> layout,
final Filter filter,
Target target,
final String name,
final boolean follow,
final boolean direct,
final boolean ignoreExceptions) {
if (name == null) {
LOGGER.error("No name provided for ConsoleAppender");
return null;
}
if (layout == null) {
layout = PatternLayout.createDefaultLayout();
}
target = target == null ? Target.SYSTEM_OUT : target;
if (follow && direct) {
LOGGER.error("Cannot use both follow and direct on ConsoleAppender");
return null;
}
return new ConsoleAppender(name, layout, filter, getManager(target, follow, direct, layout), ignoreExceptions, target, null);
}
public static ConsoleAppender createDefaultAppenderForLayout(final Layout<? extends Serializable> layout) {
return new ConsoleAppender("DefaultConsole-" + COUNT.incrementAndGet(), layout, null,
getDefaultManager(DEFAULT_TARGET, false, false, layout), true, DEFAULT_TARGET, null);
}
@PluginBuilderFactory
public static <B extends Builder<B>> B newBuilder() {
return new Builder<B>().asBuilder();
}
public static class Builder<B extends Builder<B>> extends AbstractOutputStreamAppender.Builder<B>
implements org.apache.logging.log4j.core.util.Builder<ConsoleAppender> {
@PluginBuilderAttribute
@Required
private Target target = DEFAULT_TARGET;
@PluginBuilderAttribute
private boolean follow;
@PluginBuilderAttribute
private boolean direct;
public B setTarget(final Target aTarget) {
this.target = aTarget;
return asBuilder();
}
public B setFollow(final boolean shouldFollow) {
this.follow = shouldFollow;
return asBuilder();
}
public B setDirect(final boolean shouldDirect) {
this.direct = shouldDirect;
return asBuilder();
}
@Override
public ConsoleAppender build() {
if (follow && direct) {
throw new IllegalArgumentException("Cannot use both follow and direct on ConsoleAppender '" + getName() + "'");
}
final Layout<? extends Serializable> layout = getOrCreateLayout(target.getDefaultCharset());
return new ConsoleAppender(getName(), layout, getFilter(), getManager(target, follow, direct, layout),
isIgnoreExceptions(), target, getPropertyArray());
}
}
private static OutputStreamManager getDefaultManager(final Target target, final boolean follow, final boolean direct,
final Layout<? extends Serializable> layout) {
final OutputStream os = getOutputStream(follow, direct, target);
final String managerName = target.name() + '.' + follow + '.' + direct + "-" + COUNT.get();
return OutputStreamManager.getManager(managerName, new FactoryData(os, managerName, layout), factory);
}
private static OutputStreamManager getManager(final Target target, final boolean follow, final boolean direct,
final Layout<? extends Serializable> layout) {
final OutputStream os = getOutputStream(follow, direct, target);
final String managerName = target.name() + '.' + follow + '.' + direct;
return OutputStreamManager.getManager(managerName, new FactoryData(os, managerName, layout), factory);
}
private static OutputStream getOutputStream(final boolean follow, final boolean direct, final Target target) {
final String enc = Charset.defaultCharset().name();
OutputStream outputStream;
try {
outputStream = target == Target.SYSTEM_OUT ?
direct ? new FileOutputStream(FileDescriptor.out) :
(follow ? new PrintStream(new SystemOutStream(), true, enc) : System.out) :
direct ? new FileOutputStream(FileDescriptor.err) :
(follow ? new PrintStream(new SystemErrStream(), true, enc) : System.err);
outputStream = new CloseShieldOutputStream(outputStream);
} catch (final UnsupportedEncodingException ex) {
throw new IllegalStateException("Unsupported default encoding " + enc, ex);
}
final PropertiesUtil propsUtil = PropertiesUtil.getProperties();
if (!propsUtil.isOsWindows() || propsUtil.getBooleanProperty("log4j.skipJansi", true) || direct) {
return outputStream;
}
try {
final Class<?> clazz = Loader.loadClass(JANSI_CLASS);
final Constructor<?> constructor = clazz.getConstructor(OutputStream.class);
return new CloseShieldOutputStream((OutputStream) constructor.newInstance(outputStream));
} catch (final ClassNotFoundException cnfe) {
LOGGER.debug("Jansi is not installed, cannot find {}", JANSI_CLASS);
} catch (final NoSuchMethodException nsme) {
LOGGER.warn("{} is missing the proper constructor", JANSI_CLASS);
} catch (final Exception ex) {
LOGGER.warn("Unable to instantiate {} due to {}", JANSI_CLASS, clean(Throwables.getRootCause(ex).toString()).trim());
}
return outputStream;
}
private static String clean(final String string) {
return string.replace(Chars.NUL, Chars.SPACE);
}
private static class SystemErrStream extends OutputStream {
public SystemErrStream() {
}
@Override
public void close() {
}
@Override
public void flush() {
System.err.flush();
}
@Override
public void write(final byte[] b) throws IOException {
System.err.write(b);
}
@Override
public void write(final byte[] b, final int off, final int len) throws IOException {
System.err.write(b, off, len);
}
@Override
public void write(final int b) {
System.err.write(b);
}
}
private static class SystemOutStream extends OutputStream {
public SystemOutStream() {
}
@Override
public void close() {
}
@Override
public void flush() {
System.out.flush();
}
@Override
public void write(final byte[] b) throws IOException {
System.out.write(b);
}
@Override
public void write(final byte[] b, final int off, final int len) throws IOException {
System.out.write(b, off, len);
}
@Override
public void write(final int b) throws IOException {
System.out.write(b);
}
}
private static class FactoryData {
private final OutputStream os;
private final String name;
private final Layout<? extends Serializable> layout;
public FactoryData(final OutputStream os, final String type, final Layout<? extends Serializable> layout) {
this.os = os;
this.name = type;
this.layout = layout;
}
}
private static class ConsoleManagerFactory implements ManagerFactory<OutputStreamManager, FactoryData> {
@Override
public OutputStreamManager createManager(final String name, final FactoryData data) {
return new OutputStreamManager(data.os, data.name, data.layout, true);
}
}
public Target getTarget() {
return target;
}
}