View Javadoc

1   package com.atlassian.plugin.test;
2   
3   import java.util.ArrayList;
4   import java.util.List;
5   
6   import com.google.common.base.Function;
7   import com.google.common.collect.Lists;
8   
9   import org.apache.log4j.Appender;
10  import org.apache.log4j.AppenderSkeleton;
11  import org.apache.log4j.Level;
12  import org.apache.log4j.LogManager;
13  import org.apache.log4j.Logger;
14  import org.apache.log4j.spi.LoggingEvent;
15  import org.hamcrest.Description;
16  import org.hamcrest.Matcher;
17  import org.hamcrest.TypeSafeMatcher;
18  import org.junit.rules.ExternalResource;
19  
20  import static com.atlassian.plugin.test.Matchers.containsAllStrings;
21  import static org.hamcrest.CoreMatchers.allOf;
22  import static org.hamcrest.Matchers.hasItem;
23  
24  /**
25   * A JUnit Rule for capturing and verifying log messages from a given class.
26   *
27   * This implementation is Log4J specific, but the interface is framed in SLF4J terminology. The intent is that if we need to switch
28   * to a different logger we should be able to preserve the interface, since it mirrors the SLF4J interface used by non test code.
29   * The implementation uses a Log4j appender to capture log messages for verification.
30   *
31   * @since 3.2.16
32   */
33  public class CapturedLogging extends ExternalResource
34  {
35      private final Class logSource;
36      private Appender appender;
37      private List<LoggingEvent> loggingEvents;
38      private Logger logger;
39      private Level savedLoggerLevel;
40      private boolean savedLoggerAdditivity;
41  
42      public CapturedLogging(final Class logSource)
43      {
44          this.logSource = logSource;
45      }
46  
47      public List<LoggingEvent> getLoggingEvents()
48      {
49          return loggingEvents;
50      }
51  
52      @Override
53      protected void before() throws Throwable
54      {
55          super.before();
56          loggingEvents = new ArrayList<LoggingEvent>();
57          appender = new AppenderSkeleton()
58          {
59              @Override
60              protected void append(final LoggingEvent event)
61              {
62                  loggingEvents.add(event);
63              }
64  
65              @Override
66              public void close()
67              {
68              }
69  
70              @Override
71              public boolean requiresLayout()
72              {
73                  return false;
74              }
75          };
76          logger = LogManager.getLogger(logSource);
77          // Ensure we get all logs from the object under test, but stop them propagating
78          savedLoggerLevel = logger.getLevel();
79          savedLoggerAdditivity = logger.getAdditivity();
80          logger.setLevel(Level.ALL);
81          logger.setAdditivity(false);
82          logger.addAppender(appender);
83      }
84  
85      @Override
86      protected void after()
87      {
88          logger.setLevel(savedLoggerLevel);
89          logger.setAdditivity(savedLoggerAdditivity);
90          logger.removeAppender(appender);
91          super.after();
92      }
93  
94      public static Matcher<CapturedLogging> didLog(final Matcher<LoggingEvent> loggingEventMatcher)
95      {
96          return new TypeSafeMatcher<CapturedLogging>()
97          {
98              @Override
99              protected boolean matchesSafely(final CapturedLogging capturedLogging)
100             {
101                 return hasItem(loggingEventMatcher).matches(capturedLogging.getLoggingEvents());
102             }
103 
104             @Override
105             public void describeTo(final Description description)
106             {
107                 description.appendText("some LoggingEvent that ");
108                 description.appendDescriptionOf(loggingEventMatcher);
109             }
110         };
111     }
112 
113     public static Matcher<CapturedLogging> didLogWarn(final Matcher<String> messageMatcher)
114     {
115         return didLog(levelAndMessageMatch(Level.WARN, messageMatcher));
116     }
117 
118     public static Matcher<CapturedLogging> didLogWarn(final String... substrings)
119     {
120         return didLogWarn(containsAllStrings(substrings));
121     }
122 
123     public static Matcher<CapturedLogging> didLogDebug(final Matcher<String> messageMatcher)
124     {
125         return didLog(levelAndMessageMatch(Level.DEBUG, messageMatcher));
126     }
127 
128     public static Matcher<CapturedLogging> didLogDebug(final String... substrings)
129     {
130         return didLogDebug(containsAllStrings(substrings));
131     }
132 
133     public static Matcher<LoggingEvent> levelIs(final Level level)
134     {
135         return new TypeSafeMatcher<LoggingEvent>()
136         {
137             @Override
138             protected boolean matchesSafely(final LoggingEvent loggingEvent)
139             {
140                 return level.equals(loggingEvent.getLevel());
141             }
142 
143             @Override
144             public void describeTo(final Description description)
145             {
146                 description.appendText("has level ");
147                 description.appendValue(level);
148             }
149         };
150     }
151 
152     public static Matcher<LoggingEvent> messageMatches(final Matcher<String> stringMatcher)
153     {
154         return new TypeSafeMatcher<LoggingEvent>()
155         {
156             @Override
157             protected boolean matchesSafely(final LoggingEvent loggingEvent)
158             {
159                 return stringMatcher.matches(loggingEvent.getMessage());
160             }
161 
162             @Override
163             public void describeTo(final Description description)
164             {
165                 description.appendText("has message ");
166                 description.appendDescriptionOf(stringMatcher);
167             }
168         };
169     }
170 
171     public String toString()
172     {
173         final List<String> loggingEventsAsString = Lists.transform(loggingEvents, new Function<LoggingEvent, String>()
174         {
175             @Override
176             public String apply(final LoggingEvent loggingEvent)
177             {
178                 return loggingEvent.getLevel() + ":" + loggingEvent.getMessage();
179             }
180         });
181         return "CapturedLogging( " + loggingEventsAsString + ")";
182     }
183 
184     public static Matcher<LoggingEvent> levelAndMessageMatch(final Level level, final Matcher<String> messageMatcher)
185     {
186         return allOf(levelIs(level), messageMatches(messageMatcher));
187     }
188 }