1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 package org.spf4j.log;
33
34 import com.google.common.cache.CacheBuilder;
35 import com.google.common.cache.CacheLoader;
36 import com.google.common.cache.LoadingCache;
37 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
38 import java.io.IOException;
39 import java.io.UncheckedIOException;
40 import java.lang.ref.Reference;
41 import java.lang.ref.SoftReference;
42 import java.lang.reflect.Field;
43 import java.security.AccessController;
44 import java.security.PrivilegedAction;
45 import java.util.Locale;
46 import java.util.MissingResourceException;
47 import java.util.ResourceBundle;
48 import java.util.logging.Handler;
49 import java.util.logging.Level;
50 import java.util.logging.LogManager;
51 import java.util.logging.LogRecord;
52 import javax.annotation.Nonnull;
53 import javax.annotation.Nullable;
54
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
57 import org.slf4j.spi.LocationAwareLogger;
58 import org.spf4j.base.Arrays;
59 import org.spf4j.base.Pair;
60 import org.spf4j.base.avro.Method;
61 import org.spf4j.text.MessageFormat;
62
63
64
65
66
67
68
69
70
71
72
73
74 public final class SLF4JBridgeHandler extends Handler {
75
76 private static final String FQCN = java.util.logging.Logger.class.getName();
77 private static final String UNKNOWN_LOGGER_NAME = "unknown.jul.logger";
78
79 private static final int TRACE_LEVEL_THRESHOLD = Level.FINEST.intValue();
80 private static final int DEBUG_LEVEL_THRESHOLD = Level.FINE.intValue();
81 private static final int INFO_LEVEL_THRESHOLD = Level.INFO.intValue();
82 private static final int WARN_LEVEL_THRESHOLD = Level.WARNING.intValue();
83
84 private static final ThreadLocal<Reference<StringBuilder>> SB = new
85 ThreadLocal<Reference<StringBuilder>>() {
86 @Override
87 protected Reference<StringBuilder> initialValue() {
88 return new SoftReference<>(new StringBuilder(64));
89 }
90
91 };
92
93 private static final MessageFormat INVALID_FORMAT = new MessageFormat("SPF4J Invalid Message Format");
94
95 private static class Lazy {
96 private static final LoadingCache<Locale, LoadingCache<String, MessageFormat>> LOCALIZED_FORMAT_CACHE
97 = CacheBuilder.newBuilder()
98 .build(new CacheLoader<Locale, LoadingCache<String, MessageFormat>>() {
99 @Override
100 public LoadingCache<String, MessageFormat> load(final Locale locale) throws Exception {
101 return CacheBuilder.newBuilder()
102 .maximumSize(Integer.getInteger("spf4j.julBridge.MaxFormatCacheSize",
103 1024)).build(new CacheLoader<String, MessageFormat>() {
104 @Override
105 public MessageFormat load(final String key) {
106 try {
107 return new MessageFormat(key, locale);
108 } catch (IllegalArgumentException ex) {
109
110 LoggerFactory.getLogger(Lazy.class).trace("Unable to forrmat {}", key, ex);
111 return INVALID_FORMAT;
112 }
113 }
114 });
115 }
116 }
117 );
118 }
119
120 private static final LoadingCache<String, MessageFormat> FORMAT_CACHE
121 = CacheBuilder.newBuilder()
122 .maximumSize(Integer.getInteger("spf4j.julBridge.MaxFormatCacheSize",
123 1024)).build(new CacheLoader<String, MessageFormat>() {
124 @Override
125 public MessageFormat load(final String key) {
126 try {
127 return new MessageFormat(key);
128 } catch (IllegalArgumentException ex) {
129
130 LoggerFactory.getLogger(SLF4JBridgeHandler.class).trace("Unable to forrmat {}", key, ex);
131 return INVALID_FORMAT;
132 }
133 }
134 });
135
136 private static final boolean ALWAYS_TRY_INFER = Boolean.getBoolean("spf4j.jul2slf4jBridge.alwaysTryInferSource");
137
138
139
140
141
142 @Nullable
143 private static final Field NEED_INFER;
144
145 static {
146 NEED_INFER = AccessController.doPrivileged(new PrivilegedAction<Field>() {
147 @Override
148 public Field run() {
149 try {
150 Field declaredField = LogRecord.class.getDeclaredField("needToInferCaller");
151 declaredField.setAccessible(true);
152 return declaredField;
153 } catch (NoSuchFieldException | SecurityException ex) {
154 LoggerFactory.getLogger(SLF4JBridgeHandler.class)
155 .debug("jul to slf4j bridge will not differentiate between computed caller info and provided", ex);
156 return null;
157 }
158 }
159 });
160 }
161
162
163
164
165
166
167
168
169
170 public static void install() {
171 getRootLogger().addHandler(new SLF4JBridgeHandler());
172 }
173
174 private static java.util.logging.Logger getRootLogger() {
175 return LogManager.getLogManager().getLogger("");
176 }
177
178
179
180
181
182
183
184 public static void uninstall() {
185 java.util.logging.Logger rootLogger = getRootLogger();
186 Handler[] handlers = rootLogger.getHandlers();
187 for (int i = 0; i < handlers.length; i++) {
188 if (handlers[i] instanceof SLF4JBridgeHandler) {
189 rootLogger.removeHandler(handlers[i]);
190 }
191 }
192 }
193
194
195
196
197
198
199
200 public static boolean isInstalled() {
201 java.util.logging.Logger rootLogger = getRootLogger();
202 Handler[] handlers = rootLogger.getHandlers();
203 for (int i = 0; i < handlers.length; i++) {
204 if (handlers[i] instanceof SLF4JBridgeHandler) {
205 return true;
206 }
207 }
208 return false;
209 }
210
211
212
213
214 public static void removeHandlersForRootLogger() {
215 java.util.logging.Logger rootLogger = getRootLogger();
216 Handler[] handlers = rootLogger.getHandlers();
217 for (int i = 0; i < handlers.length; i++) {
218 rootLogger.removeHandler(handlers[i]);
219 }
220 }
221
222 @Override
223 public void close() {
224
225 }
226
227 @Override
228 public void flush() {
229
230 }
231
232
233
234
235 private static Logger getSLF4JLogger(final LogRecord record) {
236 String name = record.getLoggerName();
237 if (name == null) {
238 name = UNKNOWN_LOGGER_NAME;
239 }
240 return LoggerFactory.getLogger(name);
241 }
242
243 private static void callLocationAwareLogger(final LocationAwareLogger lal, final LogRecord record) {
244 int julLevelValue = record.getLevel().intValue();
245 int slf4jLevel;
246 if (julLevelValue <= TRACE_LEVEL_THRESHOLD) {
247 slf4jLevel = LocationAwareLogger.TRACE_INT;
248 } else if (julLevelValue <= DEBUG_LEVEL_THRESHOLD) {
249 slf4jLevel = LocationAwareLogger.DEBUG_INT;
250 } else if (julLevelValue <= INFO_LEVEL_THRESHOLD) {
251 slf4jLevel = LocationAwareLogger.INFO_INT;
252 } else if (julLevelValue <= WARN_LEVEL_THRESHOLD) {
253 slf4jLevel = LocationAwareLogger.WARN_INT;
254 } else {
255 slf4jLevel = LocationAwareLogger.ERROR_INT;
256 }
257 Pair<String, Object[]> messageArgs = getMessageI18N(record);
258 Method m = getSourceMethodInfo(record);
259 if (m != null) {
260 lal.log(null, m.toString(), slf4jLevel, messageArgs.getFirst(), messageArgs.getSecond(), record.getThrown());
261 } else {
262 lal.log(null, FQCN, slf4jLevel, messageArgs.getFirst(), messageArgs.getSecond(), record.getThrown());
263 }
264 }
265
266 @SuppressFBWarnings("UCC_UNRELATED_COLLECTION_CONTENTS")
267 private static void callPlainSLF4JLogger(final Logger slf4jLogger, final LogRecord record) {
268 Pair<String, Object[]> message = getMessageI18N(record);
269 Object[] args = message.getSecond();
270 if (args == null || args.length == 0) {
271 logEfficient(message.getFirst(), record, slf4jLogger);
272 } else {
273 int julLevelValue = record.getLevel().intValue();
274 Throwable thrown = record.getThrown();
275 Method m = getSourceMethodInfo(record);
276 Object[] pargs;
277 if (m == null) {
278 if (thrown == null) {
279 pargs = args;
280 } else {
281 pargs = java.util.Arrays.copyOf(args, args.length + 1);
282 pargs[args.length] = thrown;
283 }
284 } else {
285 if (thrown == null) {
286 pargs = java.util.Arrays.copyOf(args, args.length + 1);
287 pargs[args.length] = m;
288 } else {
289 pargs = java.util.Arrays.copyOf(args, args.length + 2);
290 pargs[args.length] = m;
291 pargs[args.length + 1] = thrown;
292 }
293 }
294 if (julLevelValue <= TRACE_LEVEL_THRESHOLD) {
295 slf4jLogger.trace(message.getFirst(), pargs);
296 } else if (julLevelValue <= DEBUG_LEVEL_THRESHOLD) {
297 slf4jLogger.debug(message.getFirst(), pargs);
298 } else if (julLevelValue <= INFO_LEVEL_THRESHOLD) {
299 slf4jLogger.info(message.getFirst(), pargs);
300 } else if (julLevelValue <= WARN_LEVEL_THRESHOLD) {
301 slf4jLogger.warn(message.getFirst(), pargs);
302 } else {
303 slf4jLogger.error(message.getFirst(), pargs);
304 }
305
306 }
307 }
308
309 public static void logEfficient(final String i18nMessage,
310 final LogRecord record, final Logger slf4jLogger) {
311 int julLevelValue = record.getLevel().intValue();
312 Throwable thrown = record.getThrown();
313 Method m = getSourceMethodInfo(record);
314 if (thrown != null) {
315 if (julLevelValue <= TRACE_LEVEL_THRESHOLD) {
316 if (m != null) {
317 slf4jLogger.trace(i18nMessage, m, thrown);
318 } else {
319 slf4jLogger.trace(i18nMessage, thrown);
320 }
321 } else if (julLevelValue <= DEBUG_LEVEL_THRESHOLD) {
322 if (m != null) {
323 slf4jLogger.debug(i18nMessage, m, thrown);
324 } else {
325 slf4jLogger.debug(i18nMessage, thrown);
326 }
327 } else if (julLevelValue <= INFO_LEVEL_THRESHOLD) {
328 if (m != null) {
329 slf4jLogger.info(i18nMessage, m, thrown);
330 } else {
331 slf4jLogger.info(i18nMessage, thrown);
332 }
333 } else if (julLevelValue <= WARN_LEVEL_THRESHOLD) {
334 if (m != null) {
335 slf4jLogger.warn(i18nMessage, m, thrown);
336 } else {
337 slf4jLogger.warn(i18nMessage, thrown);
338 }
339 } else {
340 if (m != null) {
341 slf4jLogger.error(i18nMessage, m, thrown);
342 } else {
343 slf4jLogger.error(i18nMessage, thrown);
344 }
345 }
346 } else {
347 if (julLevelValue <= TRACE_LEVEL_THRESHOLD) {
348 if (m != null) {
349 slf4jLogger.trace(i18nMessage, m);
350 } else {
351 slf4jLogger.trace(i18nMessage);
352 }
353 } else if (julLevelValue <= DEBUG_LEVEL_THRESHOLD) {
354 if (m != null) {
355 slf4jLogger.debug(i18nMessage, m);
356 } else {
357 slf4jLogger.debug(i18nMessage);
358 }
359 } else if (julLevelValue <= INFO_LEVEL_THRESHOLD) {
360 if (m != null) {
361 slf4jLogger.info(i18nMessage, m);
362 } else {
363 slf4jLogger.info(i18nMessage);
364 }
365 } else if (julLevelValue <= WARN_LEVEL_THRESHOLD) {
366 if (m != null) {
367 slf4jLogger.warn(i18nMessage, m);
368 } else {
369 slf4jLogger.warn(i18nMessage);
370 }
371 } else {
372 if (m != null) {
373 slf4jLogger.error(i18nMessage, m);
374 } else {
375 slf4jLogger.error(i18nMessage);
376 }
377 }
378 }
379 }
380
381 @Nullable
382 public static Method getSourceMethodInfo(final LogRecord record) {
383 Method m;
384 try {
385 if (ALWAYS_TRY_INFER || (NEED_INFER != null && !NEED_INFER.getBoolean(record))) {
386 m = new Method(record.getSourceClassName(), record.getSourceMethodName());
387 } else {
388 m = null;
389 }
390 } catch (IllegalArgumentException | IllegalAccessException ex) {
391 throw new RuntimeException(ex);
392 }
393 return m;
394 }
395
396
397
398
399
400
401
402 @Nonnull
403 private static Pair<String, Object[]> getMessageI18N(final LogRecord record) {
404 String message = record.getMessage();
405 if (message == null) {
406 return Pair.of("", record.getParameters());
407 }
408
409 ResourceBundle bundle = record.getResourceBundle();
410 if (bundle != null) {
411 try {
412 message = bundle.getString(message);
413 } catch (MissingResourceException e) {
414 }
415 }
416 Object[] params = record.getParameters();
417 if (params != null && params.length > 0) {
418 StringBuilder msg = SB.get().get();
419 if (msg == null) {
420 msg = new StringBuilder(64);
421 SB.set(new SoftReference<>(msg));
422 } else {
423 msg.setLength(0);
424 }
425 try {
426 boolean[] used;
427 try {
428 MessageFormat msgFormat;
429 if (bundle == null) {
430 msgFormat = FORMAT_CACHE.getUnchecked(message);
431 } else {
432 msgFormat = Lazy.LOCALIZED_FORMAT_CACHE.getUnchecked(bundle.getLocale()).getUnchecked(message);
433 }
434 if (msgFormat != INVALID_FORMAT) {
435 used = msgFormat.format(params, msg);
436 } else {
437
438
439 return Pair.of(record.getMessage(), record.getParameters());
440 }
441 } catch (IOException ex) {
442 throw new UncheckedIOException(ex);
443 }
444 int nrLeft = 0;
445 for (boolean u : used) {
446 if (!u) {
447 nrLeft++;
448 }
449 }
450 if (nrLeft == 0) {
451 return Pair.of(msg.toString(), Arrays.EMPTY_OBJ_ARRAY);
452 } else {
453 Object[] left = new Object[nrLeft];
454 for (int i = 0, j = 0; i < used.length; i++) {
455 if (!used[i]) {
456 left[j++] = params[i];
457 }
458 }
459 return Pair.of(msg.toString(), left);
460 }
461 } catch (IllegalArgumentException e) {
462 LoggerFactory.getLogger(SLF4JBridgeHandler.class).warn("Unable to format {} with {}", message, params, e);
463 return Pair.of(message, Arrays.EMPTY_OBJ_ARRAY);
464 }
465 } else {
466 return Pair.of(message, Arrays.EMPTY_OBJ_ARRAY);
467 }
468 }
469
470 @Override
471 public void publish(final LogRecord record) {
472 try {
473 Logger slf4jLogger = getSLF4JLogger(record);
474 if (slf4jLogger instanceof LocationAwareLogger) {
475 callLocationAwareLogger((LocationAwareLogger) slf4jLogger, record);
476 } else {
477 callPlainSLF4JLogger(slf4jLogger, record);
478 }
479 } catch (RuntimeException ex) {
480 LoggerFactory.getLogger(SLF4JBridgeHandler.class).warn("Unable to publish {}", record, ex);
481 }
482 }
483
484 }