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
148 if (this.started) {
149 return;
150 }
151
152
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
160 if (System.console() == null) {
161 return;
162 }
163
164
165 final PrintStream out = System.out;
166 final StatusAcceptorStream acceptor = new StatusAcceptorStream(out);
167 OutputStream generator = new StatusGeneratorStream(acceptor);
168
169
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
178 }
179 }
180
181 try {
182
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
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 }