1 package com.atlassian.plugin.test;
2
3 import com.google.common.base.Function;
4 import com.google.common.collect.Lists;
5 import org.apache.log4j.Appender;
6 import org.apache.log4j.AppenderSkeleton;
7 import org.apache.log4j.Level;
8 import org.apache.log4j.LogManager;
9 import org.apache.log4j.Logger;
10 import org.apache.log4j.spi.LoggingEvent;
11 import org.hamcrest.Description;
12 import org.hamcrest.Matcher;
13 import org.hamcrest.TypeSafeMatcher;
14 import org.junit.rules.ExternalResource;
15
16 import java.util.ArrayList;
17 import java.util.List;
18 import java.util.Optional;
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
26
27
28
29
30
31
32
33 public class CapturedLogging extends ExternalResource {
34 private final Class logSource;
35 private Appender appender;
36 private List<LoggingEvent> loggingEvents;
37 private Logger logger;
38 private Level savedLoggerLevel;
39 private boolean savedLoggerAdditivity;
40
41 public CapturedLogging(final Class logSource) {
42 this.logSource = logSource;
43 }
44
45 public List<LoggingEvent> getLoggingEvents() {
46 return loggingEvents;
47 }
48
49 @Override
50 protected void before() throws Throwable {
51 super.before();
52 loggingEvents = new ArrayList<>();
53 appender = new AppenderSkeleton() {
54 @Override
55 protected void append(final LoggingEvent event) {
56 loggingEvents.add(event);
57 }
58
59 @Override
60 public void close() {
61 }
62
63 @Override
64 public boolean requiresLayout() {
65 return false;
66 }
67 };
68 logger = LogManager.getLogger(logSource);
69
70 savedLoggerLevel = logger.getLevel();
71 savedLoggerAdditivity = logger.getAdditivity();
72 logger.setLevel(Level.ALL);
73 logger.setAdditivity(false);
74 logger.addAppender(appender);
75 }
76
77 @Override
78 protected void after() {
79 logger.setLevel(savedLoggerLevel);
80 logger.setAdditivity(savedLoggerAdditivity);
81 logger.removeAppender(appender);
82 super.after();
83 }
84
85 public static Matcher<CapturedLogging> didLog(final Matcher<LoggingEvent> loggingEventMatcher) {
86 return new TypeSafeMatcher<CapturedLogging>() {
87 @Override
88 protected boolean matchesSafely(final CapturedLogging capturedLogging) {
89 return hasItem(loggingEventMatcher).matches(capturedLogging.getLoggingEvents());
90 }
91
92 @Override
93 public void describeTo(final Description description) {
94 description.appendText("some LoggingEvent that ");
95 description.appendDescriptionOf(loggingEventMatcher);
96 }
97 };
98 }
99
100 public static Matcher<CapturedLogging> didLogError(final Matcher<String> messageMatcher) {
101 return didLog(levelAndMessageMatch(Level.ERROR, messageMatcher));
102 }
103
104 public static Matcher<CapturedLogging> didLogError(final String... substrings) {
105 return didLogError(containsAllStrings(substrings));
106 }
107
108 public static Matcher<CapturedLogging> didLogWarn(final Matcher<String> messageMatcher) {
109 return didLog(levelAndMessageMatch(Level.WARN, messageMatcher));
110 }
111
112 public static Matcher<CapturedLogging> didLogWarn(final String... substrings) {
113 return didLogWarn(containsAllStrings(substrings));
114 }
115
116 public static Matcher<CapturedLogging> didLogInfo(final Matcher<String> messageMatcher) {
117 return didLog(levelAndMessageMatch(Level.INFO, messageMatcher));
118 }
119
120 public static Matcher<CapturedLogging> didLogInfo(final String... substrings) {
121 return didLogInfo(containsAllStrings(substrings));
122 }
123
124 public static Matcher<CapturedLogging> didLogDebug(final Matcher<String> messageMatcher) {
125 return didLog(levelAndMessageMatch(Level.DEBUG, messageMatcher));
126 }
127
128 public static Matcher<CapturedLogging> didLogDebug(final String... substrings) {
129 return didLogDebug(containsAllStrings(substrings));
130 }
131
132 public static Matcher<LoggingEvent> levelIs(final Level level) {
133 return new TypeSafeMatcher<LoggingEvent>() {
134 @Override
135 protected boolean matchesSafely(final LoggingEvent loggingEvent) {
136 return level.equals(loggingEvent.getLevel());
137 }
138
139 @Override
140 public void describeTo(final Description description) {
141 description.appendText("has level ");
142 description.appendValue(level);
143 }
144 };
145 }
146
147 public static Matcher<LoggingEvent> messageMatches(final Matcher<String> stringMatcher) {
148 return new TypeSafeMatcher<LoggingEvent>() {
149 @Override
150 protected boolean matchesSafely(final LoggingEvent loggingEvent) {
151 return stringMatcher.matches(loggingEvent.getMessage());
152 }
153
154 @Override
155 public void describeTo(final Description description) {
156 description.appendText("has message ");
157 description.appendDescriptionOf(stringMatcher);
158 }
159 };
160 }
161
162 public static Matcher<LoggingEvent> throwableMatches(final Matcher<Throwable> throwableMatcher) {
163 return new TypeSafeMatcher<LoggingEvent>() {
164 @Override
165 protected boolean matchesSafely(final LoggingEvent loggingEvent) {
166 return Optional.ofNullable(loggingEvent.getThrowableInformation())
167 .map(ti -> throwableMatcher.matches(ti.getThrowable()))
168 .orElse(false);
169 }
170
171 @Override
172 public void describeTo(final Description description) {
173 description.appendText("has throwable which ");
174 description.appendDescriptionOf(throwableMatcher);
175 }
176 };
177 }
178
179 public String toString() {
180 final List<String> loggingEventsAsString = Lists.transform(loggingEvents, new Function<LoggingEvent, String>() {
181 @Override
182 public String apply(final LoggingEvent loggingEvent) {
183 return loggingEvent.getLevel() + ":" + loggingEvent.getMessage();
184 }
185 });
186 return "CapturedLogging( " + loggingEventsAsString + ")";
187 }
188
189 public static Matcher<LoggingEvent> levelAndMessageMatch(final Level level, final Matcher<String> messageMatcher) {
190 return allOf(levelIs(level), messageMatches(messageMatcher));
191 }
192 }