package eu.fbk.knowledgestore.internal; import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.lang.reflect.Constructor; import java.util.Map; import javax.annotation.Nullable; import com.google.common.base.Throwables; import com.google.common.collect.Maps; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.pattern.ClassicConverter; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.UnsynchronizedAppenderBase; import ch.qos.logback.core.encoder.Encoder; import ch.qos.logback.core.pattern.color.ANSIConstants; import ch.qos.logback.core.pattern.color.ForegroundCompositeConverterBase; import ch.qos.logback.core.spi.DeferredProcessingAware; import ch.qos.logback.core.status.ErrorStatus; import ch.qos.logback.core.util.EnvUtil; public final class Logging { private static final Logger LOGGER = LoggerFactory.getLogger(Logging.class); public static final String MDC_CONTEXT = "context"; private Logging() { } @Nullable public static Map<String, String> getMDC() { try { return MDC.getCopyOfContextMap(); } catch (final Throwable ex) { LOGGER.warn("Could not retrieve MDC map", ex); return null; } } @Nullable public static void setMDC(@Nullable final Map<String, String> mdc) { try { MDC.setContextMap(mdc == null ? Maps.<String, String>newHashMap() : mdc); } catch (final Throwable ex) { LOGGER.warn("Could not update MDC map", ex); } } public static final class NormalConverter extends ForegroundCompositeConverterBase<ILoggingEvent> { @Override protected String getForegroundColorCode(final ILoggingEvent event) { final Level level = event.getLevel(); switch (level.toInt()) { case Level.ERROR_INT: return ANSIConstants.RED_FG; case Level.WARN_INT: return ANSIConstants.MAGENTA_FG; default: return ANSIConstants.DEFAULT_FG; } } } public static final class BoldConverter extends ForegroundCompositeConverterBase<ILoggingEvent> { @Override protected String getForegroundColorCode(final ILoggingEvent event) { final Level level = event.getLevel(); switch (level.toInt()) { case Level.ERROR_INT: return ANSIConstants.BOLD + ANSIConstants.RED_FG; case Level.WARN_INT: return ANSIConstants.BOLD + ANSIConstants.MAGENTA_FG; default: return ANSIConstants.BOLD + ANSIConstants.DEFAULT_FG; } } } public static final class ContextConverter extends ClassicConverter { @Override public String convert(final ILoggingEvent event) { final String context = MDC.get(MDC_CONTEXT); final String logger = event.getLevel().toInt() >= Level.WARN_INT ? event .getLoggerName() : null; if (context == null) { return logger == null ? "" : "[" + logger + "] "; } else { return logger == null ? "[" + context + "] " : "[" + context + "][" + logger + "] "; } } } public static final class StatusAppender<E> extends UnsynchronizedAppenderBase<E> { private static final int MAX_STATUS_LENGTH = 80; private boolean withJansi; private Encoder<E> encoder; public synchronized boolean isWithJansi() { return this.withJansi; } public synchronized void setWithJansi(final boolean withJansi) { if (isStarted()) { addStatus(new ErrorStatus("Cannot configure appender named \"" + this.name + "\" after it has been started.", this)); } this.withJansi = withJansi; } public synchronized Encoder<E> getEncoder() { return this.encoder; } public synchronized void setEncoder(final Encoder<E> encoder) { if (isStarted()) { addStatus(new ErrorStatus("Cannot configure appender named \"" + this.name + "\" after it has been started.", this)); } this.encoder = encoder; } @SuppressWarnings("resource") @Override public synchronized void start() { // Abort if already started if (this.started) { return; } // Abort with error if there is no encoder attached to the appender if (this.encoder == null) { addStatus(new ErrorStatus("No encoder set for the appender named \"" + this.name + "\".", this)); return; } // Abort if there is no console attached to the process if (System.console() == null) { return; } // Setup streams required for generating and displaying status information final PrintStream out = System.out; final StatusAcceptorStream acceptor = new StatusAcceptorStream(out); OutputStream generator = new StatusGeneratorStream(acceptor); // Install Jansi if on Windows and enabled if (EnvUtil.isWindows() && this.withJansi) { try { final Class<?> clazz = Class .forName("org.fusesource.jansi.WindowsAnsiOutputStream"); final Constructor<?> constructor = clazz.getConstructor(OutputStream.class); generator = (OutputStream) constructor.newInstance(generator); } catch (final Throwable ex) { // ignore } } try { // Setup encoder. On success, replace System.out and start the appender this.encoder.init(generator); System.setOut(new PrintStream(acceptor)); super.start(); } catch (final IOException ex) { addStatus(new ErrorStatus("Failed to initialize encoder for appender named \"" + this.name + "\".", this, ex)); } } @Override public synchronized void stop() { if (!isStarted()) { return; } try { this.encoder.close(); // no need to restore System.out (due to buffering, better not to do that) } catch (final IOException ex) { addStatus(new ErrorStatus("Failed to write footer for appender named \"" + this.name + "\".", this, ex)); } finally { super.stop(); } } @Override protected synchronized void append(final E event) { if (!isStarted()) { return; } try { if (event instanceof DeferredProcessingAware) { ((DeferredProcessingAware) event).prepareForDeferredProcessing(); } this.encoder.doEncode(event); } catch (final IOException ex) { stop(); addStatus(new ErrorStatus("IO failure in appender named \"" + this.name + "\".", this, ex)); } } private static final class StatusAcceptorStream extends FilterOutputStream { private byte[] status; private boolean statusEnabled; public StatusAcceptorStream(final OutputStream stream) { super(stream); this.status = null; this.statusEnabled = true; } @Override public void write(final int b) throws IOException { enableStatus(false); this.out.write(b); enableStatus(b == '\n'); } @Override public void write(final byte[] b) throws IOException { enableStatus(false); super.write(b); enableStatus(b[b.length - 1] == '\n'); } @Override public void write(final byte[] b, final int off, final int len) throws IOException { enableStatus(false); super.write(b, off, len); enableStatus(len > 0 && b[off + len - 1] == '\n'); } void setStatus(final byte[] status) { final boolean oldEnabled = this.statusEnabled; enableStatus(false); this.status = status; enableStatus(oldEnabled); } private void enableStatus(final boolean enabled) { try { if (enabled == this.statusEnabled) { return; } this.statusEnabled = enabled; if (this.status == null) { return; } else if (enabled) { final int length = Math.min(this.status.length, MAX_STATUS_LENGTH); this.out.write(this.status, 0, length); this.out.flush(); } else { final int length = Math.min(this.status.length, MAX_STATUS_LENGTH); for (int i = 0; i < length; ++i) { this.out.write('\b'); } for (int i = 0; i < length; ++i) { this.out.write(' '); } for (int i = 0; i < length; ++i) { this.out.write('\b'); } } } catch (final Throwable ex) { Throwables.propagate(ex); } } } private static final class StatusGeneratorStream extends OutputStream { private final StatusAcceptorStream stream; private final byte[] buffer; private int offset; public StatusGeneratorStream(final StatusAcceptorStream stream) { this.stream = stream; this.buffer = new byte[MAX_STATUS_LENGTH]; this.offset = 0; } @Override public void write(final int b) throws IOException { int emitCount = -1; if (b == '\n') { if (this.offset < MAX_STATUS_LENGTH) { emitCount = this.offset; } this.offset = 0; } else if (this.offset < MAX_STATUS_LENGTH) { this.buffer[this.offset++] = (byte) b; if (this.offset == MAX_STATUS_LENGTH) { emitCount = this.offset; } } if (emitCount >= 0) { final byte[] status = new byte[emitCount]; System.arraycopy(this.buffer, 0, status, 0, emitCount); this.stream.setStatus(status); } } @Override public void write(final byte[] b) throws IOException { for (int i = 0; i < b.length; ++i) { write(b[i]); } } @Override public void write(final byte[] b, final int off, final int len) throws IOException { final int to = off + len; for (int i = off; i < to; ++i) { write(b[i]); } } } } }