View Javadoc
1   package com.github.valfirst.slf4jtest;
2   
3   import static java.util.Optional.ofNullable;
4   
5   import java.util.ArrayList;
6   import java.util.Arrays;
7   import java.util.Collection;
8   import java.util.Collections;
9   import java.util.EnumSet;
10  import java.util.List;
11  import java.util.Map;
12  import java.util.Optional;
13  import java.util.Set;
14  import java.util.concurrent.CopyOnWriteArrayList;
15  import org.slf4j.Logger;
16  import org.slf4j.Marker;
17  import org.slf4j.event.KeyValuePair;
18  import org.slf4j.event.Level;
19  import org.slf4j.helpers.FormattingTuple;
20  import org.slf4j.helpers.MessageFormatter;
21  import org.slf4j.spi.LoggingEventAware;
22  import org.slf4j.spi.LoggingEventBuilder;
23  import uk.org.lidalia.lang.ThreadLocal;
24  
25  /**
26   * Implementation of {@link Logger} which stores {@link LoggingEvent}s in memory and provides
27   * methods to access and remove them in order to facilitate writing tests that assert particular
28   * logging calls were made.
29   *
30   * <p>{@link LoggingEvent}s are stored in both an {@link ThreadLocal} and a normal {@link List}. The
31   * {@link #getLoggingEvents()} and {@link #clear()} methods reference the {@link ThreadLocal}
32   * events. The {@link #getAllLoggingEvents()} and {@link #clearAll()} methods reference all events
33   * logged on this Logger. This is in order to facilitate parallelising tests - tests that use the
34   * thread local methods can be parallelised.
35   *
36   * <p>By default all Levels are enabled. It is important to note that the conventional hierarchical
37   * notion of Levels, where info being enabled implies warn and error being enabled, is not a
38   * requirement of the SLF4J API, so the {@link #setEnabledLevels(Collection)}, {@link
39   * #setEnabledLevels(Level...)}, {@link #setEnabledLevelsForAllThreads(Collection)}, {@link
40   * #setEnabledLevelsForAllThreads(Level...)} and the various isXxxxxEnabled() methods make no
41   * assumptions about this hierarchy.
42   */
43  @SuppressWarnings({"PMD.ExcessivePublicCount", "PMD.TooManyMethods"})
44  public class TestLogger implements Logger, LoggingEventAware {
45  
46      private final String name;
47      private final TestLoggerFactory testLoggerFactory;
48      private final ThreadLocal<List<LoggingEvent>> loggingEvents = new ThreadLocal<>(ArrayList::new);
49  
50      private final List<LoggingEvent> allLoggingEvents = new CopyOnWriteArrayList<>();
51  
52      private static final Set<Level> allLevels =
53              Collections.unmodifiableSet(EnumSet.allOf(Level.class));
54      private volatile ThreadLocal<Set<Level>> enabledLevels = new ThreadLocal<>(allLevels);
55  
56      TestLogger(final String name, final TestLoggerFactory testLoggerFactory) {
57          this.name = name;
58          this.testLoggerFactory = testLoggerFactory;
59      }
60  
61      public String getName() {
62          return name;
63      }
64  
65      /** Make a {@link TestLoggingEventBuilder}. */
66      @Override
67      public LoggingEventBuilder makeLoggingEventBuilder(Level level) {
68          return new TestLoggingEventBuilder(this, level);
69      }
70  
71      /**
72       * Removes all {@link LoggingEvent}s logged by this thread and resets the enabled levels of the
73       * logger to all levels for this thread.
74       */
75      public void clear() {
76          loggingEvents.get().clear();
77          enabledLevels.set(allLevels);
78      }
79  
80      /**
81       * Removes ALL {@link LoggingEvent}s logged on this logger, regardless of thread, and resets the
82       * enabled levels of the logger to all levels for ALL threads.
83       */
84      public void clearAll() {
85          allLoggingEvents.clear();
86          loggingEvents.reset();
87          enabledLevels.reset();
88          enabledLevels = new ThreadLocal<>(allLevels);
89      }
90  
91      /**
92       * Get all {@link LoggingEvent}s logged on this logger by this thread.
93       *
94       * @return the {@link LoggingEvent}s as an unmodifiable {@link List}.
95       */
96      public List<LoggingEvent> getLoggingEvents() {
97          return Collections.unmodifiableList(new ArrayList<>(loggingEvents.get()));
98      }
99  
100     /**
101      * Get all {@link LoggingEvent}s logged on this logger by ANY thread.
102      *
103      * @return the {@link LoggingEvent}s as an unmodifiable {@link List}.
104      */
105     public List<LoggingEvent> getAllLoggingEvents() {
106         return Collections.unmodifiableList(new ArrayList<>(allLoggingEvents));
107     }
108 
109     /**
110      * @return whether this logger is trace enabled in this thread
111      */
112     @Override
113     public boolean isTraceEnabled() {
114         return enabledLevels.get().contains(Level.TRACE);
115     }
116 
117     @Override
118     public void trace(final String message) {
119         log(Level.TRACE, message);
120     }
121 
122     @Override
123     public void trace(final String format, final Object arg) {
124         log(Level.TRACE, format, arg);
125     }
126 
127     @Override
128     public void trace(final String format, final Object arg1, final Object arg2) {
129         log(Level.TRACE, format, arg1, arg2);
130     }
131 
132     @Override
133     public void trace(final String format, final Object... args) {
134         log(Level.TRACE, format, args);
135     }
136 
137     @Override
138     public void trace(final String msg, final Throwable throwable) {
139         log(Level.TRACE, msg, throwable);
140     }
141 
142     @Override
143     public boolean isTraceEnabled(final Marker marker) {
144         return enabledLevels.get().contains(Level.TRACE);
145     }
146 
147     @Override
148     public void trace(final Marker marker, final String msg) {
149         log(Level.TRACE, marker, msg);
150     }
151 
152     @Override
153     public void trace(final Marker marker, final String format, final Object arg) {
154         log(Level.TRACE, marker, format, arg);
155     }
156 
157     @Override
158     public void trace(
159             final Marker marker, final String format, final Object arg1, final Object arg2) {
160         log(Level.TRACE, marker, format, arg1, arg2);
161     }
162 
163     @Override
164     public void trace(final Marker marker, final String format, final Object... args) {
165         log(Level.TRACE, marker, format, args);
166     }
167 
168     @Override
169     public void trace(final Marker marker, final String msg, final Throwable throwable) {
170         log(Level.TRACE, marker, msg, throwable);
171     }
172 
173     /**
174      * @return whether this logger is debug enabled in this thread
175      */
176     @Override
177     public boolean isDebugEnabled() {
178         return enabledLevels.get().contains(Level.DEBUG);
179     }
180 
181     @Override
182     public void debug(final String message) {
183         log(Level.DEBUG, message);
184     }
185 
186     @Override
187     public void debug(final String format, final Object arg) {
188         log(Level.DEBUG, format, arg);
189     }
190 
191     @Override
192     public void debug(final String format, final Object arg1, final Object arg2) {
193         log(Level.DEBUG, format, arg1, arg2);
194     }
195 
196     @Override
197     public void debug(final String format, final Object... args) {
198         log(Level.DEBUG, format, args);
199     }
200 
201     @Override
202     public void debug(final String msg, final Throwable throwable) {
203         log(Level.DEBUG, msg, throwable);
204     }
205 
206     @Override
207     public boolean isDebugEnabled(final Marker marker) {
208         return enabledLevels.get().contains(Level.DEBUG);
209     }
210 
211     @Override
212     public void debug(final Marker marker, final String msg) {
213         log(Level.DEBUG, marker, msg);
214     }
215 
216     @Override
217     public void debug(final Marker marker, final String format, final Object arg) {
218         log(Level.DEBUG, marker, format, arg);
219     }
220 
221     @Override
222     public void debug(
223             final Marker marker, final String format, final Object arg1, final Object arg2) {
224         log(Level.DEBUG, marker, format, arg1, arg2);
225     }
226 
227     @Override
228     public void debug(final Marker marker, final String format, final Object... args) {
229         log(Level.DEBUG, marker, format, args);
230     }
231 
232     @Override
233     public void debug(final Marker marker, final String msg, final Throwable throwable) {
234         log(Level.DEBUG, marker, msg, throwable);
235     }
236 
237     /**
238      * @return whether this logger is info enabled in this thread
239      */
240     @Override
241     public boolean isInfoEnabled() {
242         return enabledLevels.get().contains(Level.INFO);
243     }
244 
245     @Override
246     public void info(final String message) {
247         log(Level.INFO, message);
248     }
249 
250     @Override
251     public void info(final String format, final Object arg) {
252         log(Level.INFO, format, arg);
253     }
254 
255     @Override
256     public void info(final String format, final Object arg1, final Object arg2) {
257         log(Level.INFO, format, arg1, arg2);
258     }
259 
260     @Override
261     public void info(final String format, final Object... args) {
262         log(Level.INFO, format, args);
263     }
264 
265     @Override
266     public void info(final String msg, final Throwable throwable) {
267         log(Level.INFO, msg, throwable);
268     }
269 
270     @Override
271     public boolean isInfoEnabled(final Marker marker) {
272         return enabledLevels.get().contains(Level.INFO);
273     }
274 
275     @Override
276     public void info(final Marker marker, final String msg) {
277         log(Level.INFO, marker, msg);
278     }
279 
280     @Override
281     public void info(final Marker marker, final String format, final Object arg) {
282         log(Level.INFO, marker, format, arg);
283     }
284 
285     @Override
286     public void info(final Marker marker, final String format, final Object arg1, final Object arg2) {
287         log(Level.INFO, marker, format, arg1, arg2);
288     }
289 
290     @Override
291     public void info(final Marker marker, final String format, final Object... args) {
292         log(Level.INFO, marker, format, args);
293     }
294 
295     @Override
296     public void info(final Marker marker, final String msg, final Throwable throwable) {
297         log(Level.INFO, marker, msg, throwable);
298     }
299 
300     /**
301      * @return whether this logger is warn enabled in this thread
302      */
303     @Override
304     public boolean isWarnEnabled() {
305         return enabledLevels.get().contains(Level.WARN);
306     }
307 
308     @Override
309     public void warn(final String message) {
310         log(Level.WARN, message);
311     }
312 
313     @Override
314     public void warn(final String format, final Object arg) {
315         log(Level.WARN, format, arg);
316     }
317 
318     @Override
319     public void warn(final String format, final Object arg1, final Object arg2) {
320         log(Level.WARN, format, arg1, arg2);
321     }
322 
323     @Override
324     public void warn(final String format, final Object... args) {
325         log(Level.WARN, format, args);
326     }
327 
328     @Override
329     public void warn(final String msg, final Throwable throwable) {
330         log(Level.WARN, msg, throwable);
331     }
332 
333     @Override
334     public boolean isWarnEnabled(final Marker marker) {
335         return enabledLevels.get().contains(Level.WARN);
336     }
337 
338     @Override
339     public void warn(final Marker marker, final String msg) {
340         log(Level.WARN, marker, msg);
341     }
342 
343     @Override
344     public void warn(final Marker marker, final String format, final Object arg) {
345         log(Level.WARN, marker, format, arg);
346     }
347 
348     @Override
349     public void warn(final Marker marker, final String format, final Object arg1, final Object arg2) {
350         log(Level.WARN, marker, format, arg1, arg2);
351     }
352 
353     @Override
354     public void warn(final Marker marker, final String format, final Object... args) {
355         log(Level.WARN, marker, format, args);
356     }
357 
358     @Override
359     public void warn(final Marker marker, final String msg, final Throwable throwable) {
360         log(Level.WARN, marker, msg, throwable);
361     }
362 
363     /**
364      * @return whether this logger is error enabled in this thread
365      */
366     @Override
367     public boolean isErrorEnabled() {
368         return enabledLevels.get().contains(Level.ERROR);
369     }
370 
371     @Override
372     public void error(final String message) {
373         log(Level.ERROR, message);
374     }
375 
376     @Override
377     public void error(final String format, final Object arg) {
378         log(Level.ERROR, format, arg);
379     }
380 
381     @Override
382     public void error(final String format, final Object arg1, final Object arg2) {
383         log(Level.ERROR, format, arg1, arg2);
384     }
385 
386     @Override
387     public void error(final String format, final Object... args) {
388         log(Level.ERROR, format, args);
389     }
390 
391     @Override
392     public void error(final String msg, final Throwable throwable) {
393         log(Level.ERROR, msg, throwable);
394     }
395 
396     @Override
397     public boolean isErrorEnabled(final Marker marker) {
398         return enabledLevels.get().contains(Level.ERROR);
399     }
400 
401     @Override
402     public void error(final Marker marker, final String msg) {
403         log(Level.ERROR, marker, msg);
404     }
405 
406     @Override
407     public void error(final Marker marker, final String format, final Object arg) {
408         log(Level.ERROR, marker, format, arg);
409     }
410 
411     @Override
412     public void error(
413             final Marker marker, final String format, final Object arg1, final Object arg2) {
414         log(Level.ERROR, marker, format, arg1, arg2);
415     }
416 
417     @Override
418     public void error(final Marker marker, final String format, final Object... args) {
419         log(Level.ERROR, marker, format, args);
420     }
421 
422     @Override
423     public void error(final Marker marker, final String msg, final Throwable throwable) {
424         log(Level.ERROR, marker, msg, throwable);
425     }
426 
427     @Override
428     public void log(org.slf4j.event.LoggingEvent event) {
429         // The fluent logging API calls this method only if the level is enabled.
430         if (enabledByGlobalCaptureLevel(event.getLevel())) {
431             addLoggingEvent(LoggingEvent.fromSlf4jEvent(event, mdc()));
432         }
433     }
434 
435     private void log(final Level level, final String format, final Object... args) {
436         log(level, format, Optional.empty(), args);
437     }
438 
439     private void log(final Level level, final String msg, final Throwable throwable) {
440         addLoggingEvent(level, Optional.empty(), ofNullable(throwable), msg);
441     }
442 
443     private void log(
444             final Level level, final Marker marker, final String format, final Object... args) {
445         log(level, format, ofNullable(marker), args);
446     }
447 
448     private void log(
449             final Level level, final Marker marker, final String msg, final Throwable throwable) {
450         addLoggingEvent(level, ofNullable(marker), ofNullable(throwable), msg);
451     }
452 
453     private void log(
454             final Level level, final String format, final Optional<Marker> marker, final Object[] args) {
455         final FormattingTuple formattedArgs = MessageFormatter.arrayFormat(format, args);
456         addLoggingEvent(
457                 level,
458                 marker,
459                 ofNullable(formattedArgs.getThrowable()),
460                 format,
461                 formattedArgs.getArgArray());
462     }
463 
464     private void addLoggingEvent(
465             final Level level,
466             final Optional<Marker> marker,
467             final Optional<Throwable> throwable,
468             final String format,
469             final Object... args) {
470         addLoggingEvent(
471                 level,
472                 marker.map(Collections::singletonList).orElseGet(Collections::emptyList),
473                 Collections.emptyList(),
474                 throwable,
475                 format,
476                 args);
477     }
478 
479     private void addLoggingEvent(
480             final Level level,
481             final List<Marker> markers,
482             final List<KeyValuePair> keyValuePairs,
483             final Optional<Throwable> throwable,
484             final String format,
485             final Object... args) {
486         if (enabledLevels.get().contains(level) && enabledByGlobalCaptureLevel(level)) {
487             final LoggingEvent event =
488                     new LoggingEvent(
489                             Optional.of(this), level, mdc(), markers, keyValuePairs, throwable, format, args);
490             addLoggingEvent(event);
491         }
492     }
493 
494     private void addLoggingEvent(final LoggingEvent event) {
495         allLoggingEvents.add(event);
496         loggingEvents.get().add(event);
497         testLoggerFactory.addLoggingEvent(event);
498         optionallyPrint(event);
499     }
500 
501     private boolean enabledByGlobalCaptureLevel(Level level) {
502         Level captureLevel = testLoggerFactory.getCaptureLevel();
503         return captureLevel != null && captureLevel.compareTo(level) >= 0;
504     }
505 
506     private Map<String, String> mdc() {
507         return TestMDCAdapter.getInstance().getContextMap();
508     }
509 
510     private void optionallyPrint(final LoggingEvent event) {
511         Level printLevel = testLoggerFactory.getPrintLevel();
512         if (printLevel != null && printLevel.compareTo(event.getLevel()) >= 0) {
513             event.print();
514         }
515     }
516 
517     /**
518      * Get the levels enabled for this logger on this thread.
519      *
520      * @return the set of levels as an unmodifiable {@link Set}.
521      */
522     public Set<Level> getEnabledLevels() {
523         return enabledLevels.get();
524     }
525 
526     /**
527      * The conventional hierarchical notion of Levels, where info being enabled implies warn and error
528      * being enabled, is not a requirement of the SLF4J API, so all levels you wish to enable must be
529      * passed explicitly to this method.
530      *
531      * @param enabledLevels levels which will be considered enabled for this logger IN THIS THREAD;
532      *     does not affect enabled levels for this logger in other threads
533      */
534     public void setEnabledLevels(final Collection<Level> enabledLevels) {
535         this.enabledLevels.set(setOfLevels(enabledLevels));
536     }
537 
538     /**
539      * The conventional hierarchical notion of Levels, where info being enabled implies warn and error
540      * being enabled, is not a requirement of the SLF4J API, so all levels you wish to enable must be
541      * passed explicitly to this method.
542      *
543      * @param enabledLevels levels which will be considered enabled for this logger IN THIS THREAD;
544      *     does not affect enabled levels for this logger in other threads
545      */
546     public void setEnabledLevels(final Level... enabledLevels) {
547         setEnabledLevels(Arrays.asList(enabledLevels));
548     }
549 
550     /**
551      * The conventional hierarchical notion of Levels, where info being enabled implies warn and error
552      * being enabled, is not a requirement of the SLF4J API, so all levels you wish to enable must be
553      * passed explicitly to this method.
554      *
555      * @param enabledLevelsForAllThreads levels which will be considered enabled for this logger IN
556      *     ALL THREADS
557      */
558     public void setEnabledLevelsForAllThreads(final Collection<Level> enabledLevelsForAllThreads) {
559         this.enabledLevels = new ThreadLocal<>(setOfLevels(enabledLevelsForAllThreads));
560     }
561 
562     /**
563      * The conventional hierarchical notion of Levels, where info being enabled implies warn and error
564      * being enabled, is not a requirement of the SLF4J API, so all levels you wish to enable must be
565      * passed explicitly to this method.
566      *
567      * @param enabledLevelsForAllThreads levels which will be considered enabled for this logger IN
568      *     ALL THREADS
569      */
570     public void setEnabledLevelsForAllThreads(final Level... enabledLevelsForAllThreads) {
571         setEnabledLevelsForAllThreads(Arrays.asList(enabledLevelsForAllThreads));
572     }
573 
574     private Set<Level> setOfLevels(final Collection<Level> levels) {
575         return levels.isEmpty()
576                 ? Collections.emptySet()
577                 : Collections.unmodifiableSet(EnumSet.copyOf(levels));
578     }
579 }