Skip to content

Commit 0d46121

Browse files
authored
Merge pull request #248 from bugsnag/discardClassesAsPatterns
Discard classes as patterns
2 parents a884744 + d40d29c commit 0d46121

12 files changed

Lines changed: 333 additions & 25 deletions

bugsnag/src/main/java/com/bugsnag/Bugsnag.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.concurrent.ThreadFactory;
2424
import java.util.concurrent.ThreadPoolExecutor;
2525
import java.util.concurrent.TimeUnit;
26+
import java.util.regex.Pattern;
2627

2728
public class Bugsnag implements Closeable {
2829
private static final Logger LOGGER = LoggerFactory.getLogger(Bugsnag.class);
@@ -244,10 +245,11 @@ public void setRedactedKeys(String... redactedKeys) {
244245

245246
/**
246247
* Set which exception classes should be ignored (not sent) by Bugsnag.
248+
* Uses Java regex patterns for matching exception class names.
247249
*
248-
* @param discardClasses a list of exception classes to ignore
250+
* @param discardClasses compiled regex patterns to match exception class names
249251
*/
250-
public void setDiscardClasses(String... discardClasses) {
252+
public void setDiscardClasses(Pattern... discardClasses) {
251253
config.setDiscardClasses(discardClasses);
252254
}
253255

bugsnag/src/main/java/com/bugsnag/BugsnagAppender.java

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.net.Proxy;
2121
import java.util.ArrayList;
2222
import java.util.Arrays;
23+
import java.util.Collection;
2324
import java.util.Collections;
2425
import java.util.HashSet;
2526
import java.util.List;
@@ -265,7 +266,7 @@ private Bugsnag createBugsnag() {
265266
bugsnag.setRedactedKeys(redactedKeys.toArray(new String[0]));
266267
}
267268

268-
bugsnag.setDiscardClasses(discardClasses.toArray(new String[0]));
269+
bugsnag.setDiscardClasses(compileDiscardPatterns(discardClasses));
269270

270271
if (!enabledReleaseStages.isEmpty()) {
271272
bugsnag.setEnabledReleaseStages(enabledReleaseStages.toArray(new String[0]));
@@ -301,6 +302,21 @@ public boolean onError(Report report) {
301302
return bugsnag;
302303
}
303304

305+
/**
306+
* Compiles a collection of pattern strings into an array of Pattern objects.
307+
*
308+
* @param patternStrings the collection of pattern strings to compile
309+
* @return an array of compiled Pattern objects
310+
*/
311+
private Pattern[] compileDiscardPatterns(Collection<String> patternStrings) {
312+
Pattern[] patterns = new Pattern[patternStrings.size()];
313+
int idx = 0;
314+
for (String pattern : patternStrings) {
315+
patterns[idx++] = Pattern.compile(pattern);
316+
}
317+
return patterns;
318+
}
319+
304320
/**
305321
* Add a callback to execute code before/after every notification to Bugsnag.
306322
*
@@ -414,24 +430,24 @@ public void setIgnoredClass(String ignoredClass) {
414430
}
415431

416432
/**
417-
* @see Bugsnag#setDiscardClasses(String...)
433+
* @see Bugsnag#setDiscardClasses(Pattern...)
418434
*/
419435
public void setDiscardClass(String discardClass) {
420436
this.discardClasses.add(discardClass);
421437

422438
if (bugsnag != null) {
423-
bugsnag.setDiscardClasses(this.discardClasses.toArray(new String[0]));
439+
bugsnag.setDiscardClasses(compileDiscardPatterns(this.discardClasses));
424440
}
425441
}
426442

427443
/**
428-
* @see Bugsnag#setDiscardClasses(String...)
444+
* @see Bugsnag#setDiscardClasses(Pattern...)
429445
*/
430446
public void setDiscardClasses(String discardClasses) {
431447
this.discardClasses.addAll(split(discardClasses));
432448

433449
if (bugsnag != null) {
434-
bugsnag.setDiscardClasses(this.discardClasses.toArray(new String[0]));
450+
bugsnag.setDiscardClasses(compileDiscardPatterns(this.discardClasses));
435451
}
436452
}
437453

bugsnag/src/main/java/com/bugsnag/Configuration.java

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@
1313
import org.slf4j.Logger;
1414
import org.slf4j.LoggerFactory;
1515

16-
import java.util.Arrays;
1716
import java.util.Collection;
1817
import java.util.Date;
1918
import java.util.HashMap;
20-
import java.util.List;
19+
import java.util.HashSet;
2120
import java.util.Map;
2221
import java.util.Set;
2322
import java.util.concurrent.ConcurrentLinkedQueue;
2423
import java.util.concurrent.atomic.AtomicBoolean;
24+
import java.util.regex.Pattern;
2525

2626
@SuppressWarnings("visibilitymodifier")
2727
public class Configuration {
@@ -38,7 +38,8 @@ public class Configuration {
3838
private EndpointConfiguration endpoints;
3939
private Delivery sessionDelivery;
4040
private String[] redactedKeys = new String[] {"password", "secret", "Authorization", "Cookie"};
41-
private String[] discardClasses;
41+
private Set<Pattern> discardClassRegexPatterns = new HashSet<Pattern>();
42+
private Set<String> discardClassStringPatterns = new HashSet<String>();
4243
private Set<String> enabledReleaseStages = null;
4344
private String[] projectPackages;
4445
private String releaseStage;
@@ -75,12 +76,16 @@ boolean shouldNotifyForReleaseStage() {
7576
}
7677

7778
boolean shouldIgnoreClass(String className) {
78-
if (discardClasses == null) {
79+
if (discardClassRegexPatterns == null || discardClassRegexPatterns.isEmpty()) {
7980
return false;
8081
}
8182

82-
List<String> classes = Arrays.asList(discardClasses);
83-
return classes.contains(className);
83+
for (Pattern pattern : discardClassRegexPatterns) {
84+
if (pattern.matcher(className).matches()) {
85+
return true;
86+
}
87+
}
88+
return false;
8489
}
8590

8691
void addCallback(Callback callback) {
@@ -253,12 +258,50 @@ public void setRedactedKeys(String[] redactedKeys) {
253258
this.redactedKeys = redactedKeys;
254259
}
255260

256-
public String[] getDiscardClasses() {
257-
return discardClasses;
261+
public Pattern[] getDiscardClasses() {
262+
return discardClassRegexPatterns.toArray(new Pattern[0]);
258263
}
259264

260-
public void setDiscardClasses(String[] discardClasses) {
261-
this.discardClasses = discardClasses;
265+
/**
266+
* Set which exception classes should be ignored (not sent) by Bugsnag.
267+
* Uses Java regex patterns for matching exception class names.
268+
*
269+
* @param discardClasses a list of compiled regex patterns to match exception class names
270+
*/
271+
public void setDiscardClasses(Pattern[] discardClasses) {
272+
this.discardClassRegexPatterns.clear();
273+
this.discardClassStringPatterns.clear();
274+
if (discardClasses != null) {
275+
for (Pattern pattern : discardClasses) {
276+
if (pattern != null) {
277+
// Store pattern
278+
this.discardClassRegexPatterns.add(pattern);
279+
// Store string representation for serialization
280+
this.discardClassStringPatterns.add(pattern.pattern());
281+
}
282+
}
283+
}
284+
}
285+
286+
/**
287+
* Set which exception classes should be ignored (not sent) by Bugsnag.
288+
* Compiles the provided strings as Java regex patterns.
289+
*
290+
* @param discardClasses a list of regex pattern strings to match exception class names
291+
*/
292+
public void setDiscardClassesFromStrings(String[] discardClasses) {
293+
this.discardClassRegexPatterns.clear();
294+
this.discardClassStringPatterns.clear();
295+
if (discardClasses != null) {
296+
for (String patternStr : discardClasses) {
297+
if (patternStr != null && !patternStr.isEmpty()) {
298+
// Store original pattern string
299+
this.discardClassStringPatterns.add(patternStr);
300+
// Compile as regex pattern
301+
this.discardClassRegexPatterns.add(Pattern.compile(patternStr));
302+
}
303+
}
304+
}
262305
}
263306

264307
public Set<String> getEnabledReleaseStages() {

bugsnag/src/test/java/com/bugsnag/AppenderTest.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.Arrays;
2121
import java.util.List;
2222
import java.util.Map;
23+
import java.util.regex.Pattern;
2324

2425
/**
2526
* Test for the Bugsnag Appender
@@ -151,9 +152,19 @@ public void testBugsnagConfig() {
151152
assertTrue(redactedKeys.contains("credit_card_number"));
152153

153154
assertEquals(2, config.getDiscardClasses().length);
154-
ArrayList<String> discardClasses = new ArrayList<String>(Arrays.asList(config.getDiscardClasses()));
155-
assertTrue(discardClasses.contains("com.example.Custom"));
156-
assertTrue(discardClasses.contains("java.io.IOException"));
155+
Pattern[] discardPatterns = config.getDiscardClasses();
156+
boolean hasCustom = false;
157+
boolean hasIoException = false;
158+
for (Pattern pattern : discardPatterns) {
159+
if (pattern.pattern().equals("com.example.Custom")) {
160+
hasCustom = true;
161+
}
162+
if (pattern.pattern().equals("java.io.IOException")) {
163+
hasIoException = true;
164+
}
165+
}
166+
assertTrue(hasCustom);
167+
assertTrue(hasIoException);
157168

158169
assertEquals(2, config.getEnabledReleaseStages().size());
159170
assertTrue(config.getEnabledReleaseStages().contains("development"));

bugsnag/src/test/java/com/bugsnag/BugsnagTest.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.HashMap;
2424
import java.util.Map;
2525
import java.util.Set;
26+
import java.util.regex.Pattern;
2627

2728
public class BugsnagTest {
2829

@@ -61,13 +62,16 @@ public void testIgnoreClasses() {
6162
assertTrue(bugsnag.notify(new RuntimeException()));
6263
assertTrue(bugsnag.notify(new TestException()));
6364

64-
// Ignore just RuntimeException
65-
bugsnag.setDiscardClasses(RuntimeException.class.getName());
65+
// Ignore just RuntimeException (compile pattern for exact match)
66+
bugsnag.setDiscardClasses(Pattern.compile(Pattern.quote(RuntimeException.class.getName())));
6667
assertFalse(bugsnag.notify(new RuntimeException()));
6768
assertTrue(bugsnag.notify(new TestException()));
6869

69-
// Ignore both
70-
bugsnag.setDiscardClasses(RuntimeException.class.getName(), TestException.class.getName());
70+
// Ignore both (compile patterns for exact matches)
71+
bugsnag.setDiscardClasses(
72+
Pattern.compile(Pattern.quote(RuntimeException.class.getName())),
73+
Pattern.compile(Pattern.quote(TestException.class.getName()))
74+
);
7175
assertFalse(bugsnag.notify(new RuntimeException()));
7276
assertFalse(bugsnag.notify(new TestException()));
7377
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package com.bugsnag;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertFalse;
5+
import static org.junit.Assert.assertTrue;
6+
7+
import org.junit.Before;
8+
import org.junit.Test;
9+
10+
import java.util.regex.Pattern;
11+
12+
/**
13+
* Test for Configuration.shouldIgnoreClass with regex pattern matching
14+
*/
15+
public class ConfigurationDiscardClassesTest {
16+
17+
private Configuration config;
18+
19+
@Before
20+
public void setUp() {
21+
config = new Configuration("test-api-key");
22+
}
23+
24+
@Test
25+
public void testExactMatch() {
26+
config.setDiscardClasses(new Pattern[] {Pattern.compile("com.example.CustomException")});
27+
28+
assertTrue(config.shouldIgnoreClass("com.example.CustomException"));
29+
assertFalse(config.shouldIgnoreClass("com.example.OtherException"));
30+
}
31+
32+
@Test
33+
public void testWildcardMatch() {
34+
config.setDiscardClasses(new Pattern[] {Pattern.compile("com\\.example\\..*")});
35+
36+
assertTrue(config.shouldIgnoreClass("com.example.CustomException"));
37+
assertTrue(config.shouldIgnoreClass("com.example.OtherException"));
38+
assertTrue(config.shouldIgnoreClass("com.example."));
39+
assertFalse(config.shouldIgnoreClass("com.other.Exception"));
40+
}
41+
42+
@Test
43+
public void testMultipleWildcards() {
44+
config.setDiscardClasses(new Pattern[] {Pattern.compile("com\\..*\\.Exception")});
45+
46+
assertTrue(config.shouldIgnoreClass("com.example.Exception"));
47+
assertTrue(config.shouldIgnoreClass("com.other.Exception"));
48+
assertFalse(config.shouldIgnoreClass("com.example.CustomException"));
49+
}
50+
51+
@Test
52+
public void testQuestionMarkWildcard() {
53+
config.setDiscardClasses(new Pattern[] {Pattern.compile("com\\.example\\.Exception.")});
54+
55+
assertTrue(config.shouldIgnoreClass("com.example.Exception1"));
56+
assertTrue(config.shouldIgnoreClass("com.example.ExceptionX"));
57+
assertFalse(config.shouldIgnoreClass("com.example.Exception"));
58+
assertFalse(config.shouldIgnoreClass("com.example.Exception12"));
59+
}
60+
61+
@Test
62+
public void testMultiplePatterns() {
63+
config.setDiscardClasses(new Pattern[] {
64+
Pattern.compile("java\\.io\\..*"),
65+
Pattern.compile("com\\.example\\.CustomException"),
66+
Pattern.compile("org\\..*\\.SpecialException")
67+
});
68+
69+
assertTrue(config.shouldIgnoreClass("java.io.IOException"));
70+
assertTrue(config.shouldIgnoreClass("java.io.FileNotFoundException"));
71+
assertTrue(config.shouldIgnoreClass("com.example.CustomException"));
72+
assertTrue(config.shouldIgnoreClass("org.apache.SpecialException"));
73+
assertTrue(config.shouldIgnoreClass("org.springframework.SpecialException"));
74+
assertFalse(config.shouldIgnoreClass("com.example.OtherException"));
75+
}
76+
77+
@Test
78+
public void testGetDiscardClassesReturnsOriginalPatterns() {
79+
Pattern[] patterns = new Pattern[] {
80+
Pattern.compile("com\\.example\\..*"),
81+
Pattern.compile("java\\.io\\.IOException")
82+
};
83+
config.setDiscardClasses(patterns);
84+
85+
Pattern[] retrieved = config.getDiscardClasses();
86+
assertEquals(2, retrieved.length);
87+
88+
// Check that patterns are returned
89+
boolean hasWildcard = false;
90+
boolean hasExact = false;
91+
for (Pattern pattern : retrieved) {
92+
if (pattern.pattern().equals("com\\.example\\..*")) {
93+
hasWildcard = true;
94+
}
95+
if (pattern.pattern().equals("java\\.io\\.IOException")) {
96+
hasExact = true;
97+
}
98+
}
99+
assertTrue(hasWildcard);
100+
assertTrue(hasExact);
101+
}
102+
103+
@Test
104+
public void testEmptyAndNullPatterns() {
105+
config.setDiscardClasses(new Pattern[] {});
106+
assertFalse(config.shouldIgnoreClass("com.example.Exception"));
107+
108+
config.setDiscardClasses(null);
109+
assertFalse(config.shouldIgnoreClass("com.example.Exception"));
110+
}
111+
112+
@Test
113+
public void testSpecialCharactersAreEscaped() {
114+
// In regex, $ is a special character (end of line), so it needs to be escaped
115+
config.setDiscardClasses(new Pattern[] {Pattern.compile("com\\.example\\.Exception\\$Inner")});
116+
117+
assertTrue(config.shouldIgnoreClass("com.example.Exception$Inner"));
118+
assertFalse(config.shouldIgnoreClass("com.example.ExceptionXInner"));
119+
}
120+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<configuration>
3+
<include resource="org/springframework/boot/logging/logback/base.xml" />
4+
5+
<appender name="BUGSNAG" class="com.bugsnag.BugsnagAppender">
6+
<apiKey>a35a2a72bd230ac0aa0f52715bbdc6aa</apiKey>
7+
<releaseStage>production</releaseStage>
8+
<appVersion>1.0.0</appVersion>
9+
10+
<discardClass>java\.lang\..*</discardClass>
11+
12+
<endpoint>http://localhost:9339/notify</endpoint>
13+
</appender>
14+
15+
<root level="INFO">
16+
<appender-ref ref="BUGSNAG"/>
17+
</root>
18+
</configuration>

0 commit comments

Comments
 (0)