Skip to content

Commit 82df229

Browse files
committed
initial change
1 parent e343ab9 commit 82df229

3 files changed

Lines changed: 253 additions & 8 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# discardClasses Pattern Matching
2+
3+
The `discardClasses` configuration has been enhanced to support pattern matching using wildcards.
4+
5+
## Features
6+
7+
### Exact Matching (Backward Compatible)
8+
Works exactly as before for exact class names:
9+
```java
10+
config.setDiscardClasses(new String[] {
11+
"com.example.CustomException",
12+
"java.io.IOException"
13+
});
14+
```
15+
16+
### Wildcard Patterns
17+
Now supports glob-style wildcards:
18+
19+
**`*` - Matches any characters (including dots)**
20+
```java
21+
config.setDiscardClasses(new String[] {
22+
"com.example.*" // Matches all classes in com.example package
23+
});
24+
// Matches: com.example.CustomException, com.example.OtherException, etc.
25+
```
26+
27+
**`?` - Matches a single character**
28+
```java
29+
config.setDiscardClasses(new String[] {
30+
"com.example.Exception?"
31+
});
32+
// Matches: com.example.Exception1, com.example.ExceptionX
33+
// Does not match: com.example.Exception, com.example.Exception12
34+
```
35+
36+
### Combined Patterns
37+
Mix exact matches and wildcards:
38+
```java
39+
config.setDiscardClasses(new String[] {
40+
"java.io.*", // All java.io exceptions
41+
"com.*.CustomException", // CustomException in any com.* package
42+
"org.example.SpecificException" // Exact match
43+
});
44+
```
45+
46+
## Appender Compatibility
47+
48+
The BugsnagAppender continues to work exactly as before. When setting discard classes through the appender:
49+
50+
```xml
51+
<appender name="BUGSNAG" class="com.bugsnag.BugsnagAppender">
52+
<discardClasses>com.example.*,java.io.IOException</discardClasses>
53+
</appender>
54+
```
55+
56+
Or programmatically:
57+
```java
58+
appender.setDiscardClass("com.example.*");
59+
appender.setDiscardClass("java.io.IOException");
60+
```
61+
62+
The appender internally converts these to the Configuration's pattern set format.
63+
64+
## Implementation Details
65+
66+
- Patterns without wildcards are treated as exact matches (using `Pattern.quote()` internally)
67+
- Special regex characters are properly escaped
68+
- The `getDiscardClasses()` method returns the original pattern strings (not compiled regex patterns)
69+
- Pattern matching is case-sensitive
70+
- Empty or null patterns are safely ignored

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

Lines changed: 73 additions & 8 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> discardClasses = new HashSet<Pattern>();
42+
private Set<String> discardClassPatterns = new HashSet<String>();
4243
private Set<String> enabledReleaseStages = null;
4344
private String[] projectPackages;
4445
private String releaseStage;
@@ -74,12 +75,16 @@ boolean shouldNotifyForReleaseStage() {
7475
}
7576

7677
boolean shouldIgnoreClass(String className) {
77-
if (discardClasses == null) {
78+
if (discardClasses == null || discardClasses.isEmpty()) {
7879
return false;
7980
}
8081

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

8590
void addCallback(Callback callback) {
@@ -253,11 +258,71 @@ public void setRedactedKeys(String[] redactedKeys) {
253258
}
254259

255260
public String[] getDiscardClasses() {
256-
return discardClasses;
261+
return discardClassPatterns.toArray(new String[0]);
257262
}
258263

259264
public void setDiscardClasses(String[] discardClasses) {
260-
this.discardClasses = discardClasses;
265+
this.discardClasses.clear();
266+
this.discardClassPatterns.clear();
267+
if (discardClasses != null) {
268+
for (String pattern : discardClasses) {
269+
if (pattern != null && !pattern.isEmpty()) {
270+
// Store original pattern string
271+
this.discardClassPatterns.add(pattern);
272+
// Convert glob-style wildcards to regex
273+
String regex = convertToRegex(pattern);
274+
this.discardClasses.add(Pattern.compile(regex));
275+
}
276+
}
277+
}
278+
}
279+
280+
/**
281+
* Converts a glob-style pattern to a regex pattern.
282+
* Supports * (matches any characters) and ? (matches single character).
283+
* If the pattern doesn't contain wildcards, it's treated as an exact match.
284+
*
285+
* @param pattern the glob-style pattern
286+
* @return the regex pattern
287+
*/
288+
private String convertToRegex(String pattern) {
289+
// If the pattern doesn't contain wildcards, match exactly
290+
if (!pattern.contains("*") && !pattern.contains("?")) {
291+
return Pattern.quote(pattern);
292+
}
293+
294+
StringBuilder regex = new StringBuilder();
295+
for (int i = 0; i < pattern.length(); i++) {
296+
char c = pattern.charAt(i);
297+
switch (c) {
298+
case '*':
299+
regex.append(".*");
300+
break;
301+
case '?':
302+
regex.append(".");
303+
break;
304+
case '.':
305+
case '(':
306+
case ')':
307+
case '+':
308+
case '|':
309+
case '^':
310+
case '$':
311+
case '@':
312+
case '%':
313+
case '[':
314+
case ']':
315+
case '{':
316+
case '}':
317+
case '\\':
318+
regex.append('\\').append(c);
319+
break;
320+
default:
321+
regex.append(c);
322+
break;
323+
}
324+
}
325+
return regex.toString();
261326
}
262327

263328
public Set<String> getEnabledReleaseStages() {
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package com.bugsnag;
2+
3+
import static org.junit.Assert.assertFalse;
4+
import static org.junit.Assert.assertTrue;
5+
import static org.junit.Assert.assertEquals;
6+
7+
import org.junit.Before;
8+
import org.junit.Test;
9+
10+
/**
11+
* Test for Configuration.shouldIgnoreClass with pattern matching
12+
*/
13+
public class ConfigurationDiscardClassesTest {
14+
15+
private Configuration config;
16+
17+
@Before
18+
public void setUp() {
19+
config = new Configuration("test-api-key");
20+
}
21+
22+
@Test
23+
public void testExactMatch() {
24+
config.setDiscardClasses(new String[] {"com.example.CustomException"});
25+
26+
assertTrue(config.shouldIgnoreClass("com.example.CustomException"));
27+
assertFalse(config.shouldIgnoreClass("com.example.OtherException"));
28+
}
29+
30+
@Test
31+
public void testWildcardMatch() {
32+
config.setDiscardClasses(new String[] {"com.example.*"});
33+
34+
assertTrue(config.shouldIgnoreClass("com.example.CustomException"));
35+
assertTrue(config.shouldIgnoreClass("com.example.OtherException"));
36+
assertTrue(config.shouldIgnoreClass("com.example."));
37+
assertFalse(config.shouldIgnoreClass("com.other.Exception"));
38+
}
39+
40+
@Test
41+
public void testMultipleWildcards() {
42+
config.setDiscardClasses(new String[] {"com.*.Exception"});
43+
44+
assertTrue(config.shouldIgnoreClass("com.example.Exception"));
45+
assertTrue(config.shouldIgnoreClass("com.other.Exception"));
46+
assertFalse(config.shouldIgnoreClass("com.example.CustomException"));
47+
}
48+
49+
@Test
50+
public void testQuestionMarkWildcard() {
51+
config.setDiscardClasses(new String[] {"com.example.Exception?"});
52+
53+
assertTrue(config.shouldIgnoreClass("com.example.Exception1"));
54+
assertTrue(config.shouldIgnoreClass("com.example.ExceptionX"));
55+
assertFalse(config.shouldIgnoreClass("com.example.Exception"));
56+
assertFalse(config.shouldIgnoreClass("com.example.Exception12"));
57+
}
58+
59+
@Test
60+
public void testMultiplePatterns() {
61+
config.setDiscardClasses(new String[] {
62+
"java.io.*",
63+
"com.example.CustomException",
64+
"org.*.SpecialException"
65+
});
66+
67+
assertTrue(config.shouldIgnoreClass("java.io.IOException"));
68+
assertTrue(config.shouldIgnoreClass("java.io.FileNotFoundException"));
69+
assertTrue(config.shouldIgnoreClass("com.example.CustomException"));
70+
assertTrue(config.shouldIgnoreClass("org.apache.SpecialException"));
71+
assertTrue(config.shouldIgnoreClass("org.springframework.SpecialException"));
72+
assertFalse(config.shouldIgnoreClass("com.example.OtherException"));
73+
}
74+
75+
@Test
76+
public void testGetDiscardClassesReturnsOriginalPatterns() {
77+
String[] patterns = new String[] {"com.example.*", "java.io.IOException"};
78+
config.setDiscardClasses(patterns);
79+
80+
String[] retrieved = config.getDiscardClasses();
81+
assertEquals(2, retrieved.length);
82+
83+
// Check that patterns are returned (not regex)
84+
boolean hasWildcard = false;
85+
boolean hasExact = false;
86+
for (String pattern : retrieved) {
87+
if (pattern.equals("com.example.*")) hasWildcard = true;
88+
if (pattern.equals("java.io.IOException")) hasExact = true;
89+
}
90+
assertTrue(hasWildcard);
91+
assertTrue(hasExact);
92+
}
93+
94+
@Test
95+
public void testEmptyAndNullPatterns() {
96+
config.setDiscardClasses(new String[] {});
97+
assertFalse(config.shouldIgnoreClass("com.example.Exception"));
98+
99+
config.setDiscardClasses(null);
100+
assertFalse(config.shouldIgnoreClass("com.example.Exception"));
101+
}
102+
103+
@Test
104+
public void testSpecialCharactersAreEscaped() {
105+
config.setDiscardClasses(new String[] {"com.example.Exception$Inner"});
106+
107+
assertTrue(config.shouldIgnoreClass("com.example.Exception$Inner"));
108+
assertFalse(config.shouldIgnoreClass("com.example.ExceptionXInner"));
109+
}
110+
}

0 commit comments

Comments
 (0)