1   package eu.fbk.knowledgestore.internal;
2   
3   import java.io.FilterOutputStream;
4   import java.io.IOException;
5   import java.io.OutputStream;
6   import java.io.PrintStream;
7   import java.lang.reflect.Constructor;
8   import java.util.Map;
9   
10  import javax.annotation.Nullable;
11  
12  import com.google.common.base.Throwables;
13  import com.google.common.collect.Maps;
14  
15  import org.slf4j.Logger;
16  import org.slf4j.LoggerFactory;
17  import org.slf4j.MDC;
18  
19  import ch.qos.logback.classic.Level;
20  import ch.qos.logback.classic.pattern.ClassicConverter;
21  import ch.qos.logback.classic.spi.ILoggingEvent;
22  import ch.qos.logback.core.UnsynchronizedAppenderBase;
23  import ch.qos.logback.core.encoder.Encoder;
24  import ch.qos.logback.core.pattern.color.ANSIConstants;
25  import ch.qos.logback.core.pattern.color.ForegroundCompositeConverterBase;
26  import ch.qos.logback.core.spi.DeferredProcessingAware;
27  import ch.qos.logback.core.status.ErrorStatus;
28  import ch.qos.logback.core.util.EnvUtil;
29  
30  public final class Logging {
31  
32      private static final Logger LOGGER = LoggerFactory.getLogger(Logging.class);
33  
34      public static final String MDC_CONTEXT = "context";
35  
36      private Logging() {
37      }
38  
39      @Nullable
40      public static Map<String, String> getMDC() {
41          try {
42              return MDC.getCopyOfContextMap();
43          } catch (final Throwable ex) {
44              LOGGER.warn("Could not retrieve MDC map", ex);
45              return null;
46          }
47      }
48  
49      @Nullable
50      public static void setMDC(@Nullable final Map<String, String> mdc) {
51          try {
52              MDC.setContextMap(mdc == null ? Maps.<String, String>newHashMap() : mdc);
53          } catch (final Throwable ex) {
54              LOGGER.warn("Could not update MDC map", ex);
55          }
56      }
57  
58      public static final class NormalConverter extends
59              ForegroundCompositeConverterBase<ILoggingEvent> {
60  
61          @Override
62          protected String getForegroundColorCode(final ILoggingEvent event) {
63              final Level level = event.getLevel();
64              switch (level.toInt()) {
65              case Level.ERROR_INT:
66                  return ANSIConstants.RED_FG;
67              case Level.WARN_INT:
68                  return ANSIConstants.MAGENTA_FG;
69              default:
70                  return ANSIConstants.DEFAULT_FG;
71              }
72          }
73  
74      }
75  
76      public static final class BoldConverter extends
77              ForegroundCompositeConverterBase<ILoggingEvent> {
78  
79          @Override
80          protected String getForegroundColorCode(final ILoggingEvent event) {
81              final Level level = event.getLevel();
82              switch (level.toInt()) {
83              case Level.ERROR_INT:
84                  return ANSIConstants.BOLD + ANSIConstants.RED_FG;
85              case Level.WARN_INT:
86                  return ANSIConstants.BOLD + ANSIConstants.MAGENTA_FG;
87              default:
88                  return ANSIConstants.BOLD + ANSIConstants.DEFAULT_FG;
89              }
90          }
91  
92      }
93  
94      public static final class ContextConverter extends ClassicConverter {
95  
96          @Override
97          public String convert(final ILoggingEvent event) {
98              final String context = MDC.get(MDC_CONTEXT);
99              final String logger = event.getLevel().toInt() >= Level.WARN_INT ? event
100                     .getLoggerName() : null;
101             if (context == null) {
102                 return logger == null ? "" : "[" + logger + "] ";
103             } else {
104                 return logger == null ? "[" + context + "] " : "[" + context + "][" + logger
105                         + "] ";
106             }
107         }
108 
109     }
110 
111     public static final class StatusAppender<E> extends UnsynchronizedAppenderBase<E> {
112 
113         private static final int MAX_STATUS_LENGTH = 80;
114 
115         private boolean withJansi;
116 
117         private Encoder<E> encoder;
118 
119         public synchronized boolean isWithJansi() {
120             return this.withJansi;
121         }
122 
123         public synchronized void setWithJansi(final boolean withJansi) {
124             if (isStarted()) {
125                 addStatus(new ErrorStatus("Cannot configure appender named \"" + this.name
126                         + "\" after it has been started.", this));
127             }
128             this.withJansi = withJansi;
129         }
130 
131         public synchronized Encoder<E> getEncoder() {
132             return this.encoder;
133         }
134 
135         public synchronized void setEncoder(final Encoder<E> encoder) {
136             if (isStarted()) {
137                 addStatus(new ErrorStatus("Cannot configure appender named \"" + this.name
138                         + "\" after it has been started.", this));
139             }
140             this.encoder = encoder;
141         }
142 
143         @SuppressWarnings("resource")
144         @Override
145         public synchronized void start() {
146 
147             // Abort if already started
148             if (this.started) {
149                 return;
150             }
151 
152             // Abort with error if there is no encoder attached to the appender
153             if (this.encoder == null) {
154                 addStatus(new ErrorStatus("No encoder set for the appender named \"" + this.name
155                         + "\".", this));
156                 return;
157             }
158 
159             // Abort if there is no console attached to the process
160             if (System.console() == null) {
161                 return;
162             }
163 
164             // Setup streams required for generating and displaying status information
165             final PrintStream out = System.out;
166             final StatusAcceptorStream acceptor = new StatusAcceptorStream(out);
167             OutputStream generator = new StatusGeneratorStream(acceptor);
168 
169             // Install Jansi if on Windows and enabled
170             if (EnvUtil.isWindows() && this.withJansi) {
171                 try {
172                     final Class<?> clazz = Class
173                             .forName("org.fusesource.jansi.WindowsAnsiOutputStream");
174                     final Constructor<?> constructor = clazz.getConstructor(OutputStream.class);
175                     generator = (OutputStream) constructor.newInstance(generator);
176                 } catch (final Throwable ex) {
177                     // ignore
178                 }
179             }
180 
181             try {
182                 // Setup encoder. On success, replace System.out and start the appender
183                 this.encoder.init(generator);
184                 System.setOut(new PrintStream(acceptor));
185                 super.start();
186             } catch (final IOException ex) {
187                 addStatus(new ErrorStatus("Failed to initialize encoder for appender named \""
188                         + this.name + "\".", this, ex));
189             }
190         }
191 
192         @Override
193         public synchronized void stop() {
194             if (!isStarted()) {
195                 return;
196             }
197             try {
198                 this.encoder.close();
199                 // no need to restore System.out (due to buffering, better not to do that)
200 
201             } catch (final IOException ex) {
202                 addStatus(new ErrorStatus("Failed to write footer for appender named \""
203                         + this.name + "\".", this, ex));
204             } finally {
205                 super.stop();
206             }
207         }
208 
209         @Override
210         protected synchronized void append(final E event) {
211             if (!isStarted()) {
212                 return;
213             }
214             try {
215                 if (event instanceof DeferredProcessingAware) {
216                     ((DeferredProcessingAware) event).prepareForDeferredProcessing();
217                 }
218                 this.encoder.doEncode(event);
219             } catch (final IOException ex) {
220                 stop();
221                 addStatus(new ErrorStatus("IO failure in appender named \"" + this.name + "\".",
222                         this, ex));
223             }
224         }
225 
226         private static final class StatusAcceptorStream extends FilterOutputStream {
227 
228             private byte[] status;
229 
230             private boolean statusEnabled;
231 
232             public StatusAcceptorStream(final OutputStream stream) {
233                 super(stream);
234                 this.status = null;
235                 this.statusEnabled = true;
236             }
237 
238             @Override
239             public void write(final int b) throws IOException {
240                 enableStatus(false);
241                 this.out.write(b);
242                 enableStatus(b == '\n');
243             }
244 
245             @Override
246             public void write(final byte[] b) throws IOException {
247                 enableStatus(false);
248                 super.write(b);
249                 enableStatus(b[b.length - 1] == '\n');
250             }
251 
252             @Override
253             public void write(final byte[] b, final int off, final int len) throws IOException {
254                 enableStatus(false);
255                 super.write(b, off, len);
256                 enableStatus(len > 0 && b[off + len - 1] == '\n');
257             }
258 
259             void setStatus(final byte[] status) {
260                 final boolean oldEnabled = this.statusEnabled;
261                 enableStatus(false);
262                 this.status = status;
263                 enableStatus(oldEnabled);
264             }
265 
266             private void enableStatus(final boolean enabled) {
267                 try {
268                     if (enabled == this.statusEnabled) {
269                         return;
270                     }
271                     this.statusEnabled = enabled;
272                     if (this.status == null) {
273                         return;
274                     } else if (enabled) {
275                         final int length = Math.min(this.status.length, MAX_STATUS_LENGTH);
276                         this.out.write(this.status, 0, length);
277                         this.out.flush();
278                     } else {
279                         final int length = Math.min(this.status.length, MAX_STATUS_LENGTH);
280                         for (int i = 0; i < length; ++i) {
281                             this.out.write('\b');
282                         }
283                         for (int i = 0; i < length; ++i) {
284                             this.out.write(' ');
285                         }
286                         for (int i = 0; i < length; ++i) {
287                             this.out.write('\b');
288                         }
289                     }
290                 } catch (final Throwable ex) {
291                     Throwables.propagate(ex);
292                 }
293             }
294         }
295 
296         private static final class StatusGeneratorStream extends OutputStream {
297 
298             private final StatusAcceptorStream stream;
299 
300             private final byte[] buffer;
301 
302             private int offset;
303 
304             public StatusGeneratorStream(final StatusAcceptorStream stream) {
305                 this.stream = stream;
306                 this.buffer = new byte[MAX_STATUS_LENGTH];
307                 this.offset = 0;
308             }
309 
310             @Override
311             public void write(final int b) throws IOException {
312                 int emitCount = -1;
313                 if (b == '\n') {
314                     if (this.offset < MAX_STATUS_LENGTH) {
315                         emitCount = this.offset;
316                     }
317                     this.offset = 0;
318                 } else if (this.offset < MAX_STATUS_LENGTH) {
319                     this.buffer[this.offset++] = (byte) b;
320                     if (this.offset == MAX_STATUS_LENGTH) {
321                         emitCount = this.offset;
322                     }
323                 }
324                 if (emitCount >= 0) {
325                     final byte[] status = new byte[emitCount];
326                     System.arraycopy(this.buffer, 0, status, 0, emitCount);
327                     this.stream.setStatus(status);
328                 }
329             }
330 
331             @Override
332             public void write(final byte[] b) throws IOException {
333                 for (int i = 0; i < b.length; ++i) {
334                     write(b[i]);
335                 }
336             }
337 
338             @Override
339             public void write(final byte[] b, final int off, final int len) throws IOException {
340                 final int to = off + len;
341                 for (int i = off; i < to; ++i) {
342                     write(b[i]);
343                 }
344             }
345 
346         }
347 
348     }
349 
350 }