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