From 82df229b82d149aa7d6f2e9d212974ab263c8b10 Mon Sep 17 00:00:00 2001 From: richard elms Date: Wed, 17 Dec 2025 09:09:05 +0100 Subject: [PATCH 1/9] initial change --- DISCARD_CLASSES_PATTERN_MATCHING.md | 70 +++++++++++ .../main/java/com/bugsnag/Configuration.java | 81 +++++++++++-- .../ConfigurationDiscardClassesTest.java | 110 ++++++++++++++++++ 3 files changed, 253 insertions(+), 8 deletions(-) create mode 100644 DISCARD_CLASSES_PATTERN_MATCHING.md create mode 100644 bugsnag/src/test/java/com/bugsnag/ConfigurationDiscardClassesTest.java diff --git a/DISCARD_CLASSES_PATTERN_MATCHING.md b/DISCARD_CLASSES_PATTERN_MATCHING.md new file mode 100644 index 00000000..fd62da75 --- /dev/null +++ b/DISCARD_CLASSES_PATTERN_MATCHING.md @@ -0,0 +1,70 @@ +# discardClasses Pattern Matching + +The `discardClasses` configuration has been enhanced to support pattern matching using wildcards. + +## Features + +### Exact Matching (Backward Compatible) +Works exactly as before for exact class names: +```java +config.setDiscardClasses(new String[] { + "com.example.CustomException", + "java.io.IOException" +}); +``` + +### Wildcard Patterns +Now supports glob-style wildcards: + +**`*` - Matches any characters (including dots)** +```java +config.setDiscardClasses(new String[] { + "com.example.*" // Matches all classes in com.example package +}); +// Matches: com.example.CustomException, com.example.OtherException, etc. +``` + +**`?` - Matches a single character** +```java +config.setDiscardClasses(new String[] { + "com.example.Exception?" +}); +// Matches: com.example.Exception1, com.example.ExceptionX +// Does not match: com.example.Exception, com.example.Exception12 +``` + +### Combined Patterns +Mix exact matches and wildcards: +```java +config.setDiscardClasses(new String[] { + "java.io.*", // All java.io exceptions + "com.*.CustomException", // CustomException in any com.* package + "org.example.SpecificException" // Exact match +}); +``` + +## Appender Compatibility + +The BugsnagAppender continues to work exactly as before. When setting discard classes through the appender: + +```xml + + com.example.*,java.io.IOException + +``` + +Or programmatically: +```java +appender.setDiscardClass("com.example.*"); +appender.setDiscardClass("java.io.IOException"); +``` + +The appender internally converts these to the Configuration's pattern set format. + +## Implementation Details + +- Patterns without wildcards are treated as exact matches (using `Pattern.quote()` internally) +- Special regex characters are properly escaped +- The `getDiscardClasses()` method returns the original pattern strings (not compiled regex patterns) +- Pattern matching is case-sensitive +- Empty or null patterns are safely ignored diff --git a/bugsnag/src/main/java/com/bugsnag/Configuration.java b/bugsnag/src/main/java/com/bugsnag/Configuration.java index 7fd5d536..c71de747 100644 --- a/bugsnag/src/main/java/com/bugsnag/Configuration.java +++ b/bugsnag/src/main/java/com/bugsnag/Configuration.java @@ -13,15 +13,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashMap; -import java.util.List; +import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.regex.Pattern; @SuppressWarnings("visibilitymodifier") public class Configuration { @@ -38,7 +38,8 @@ public class Configuration { private EndpointConfiguration endpoints; private Delivery sessionDelivery; private String[] redactedKeys = new String[] {"password", "secret", "Authorization", "Cookie"}; - private String[] discardClasses; + private Set discardClasses = new HashSet(); + private Set discardClassPatterns = new HashSet(); private Set enabledReleaseStages = null; private String[] projectPackages; private String releaseStage; @@ -74,12 +75,16 @@ boolean shouldNotifyForReleaseStage() { } boolean shouldIgnoreClass(String className) { - if (discardClasses == null) { + if (discardClasses == null || discardClasses.isEmpty()) { return false; } - List classes = Arrays.asList(discardClasses); - return classes.contains(className); + for (Pattern pattern : discardClasses) { + if (pattern.matcher(className).matches()) { + return true; + } + } + return false; } void addCallback(Callback callback) { @@ -253,11 +258,71 @@ public void setRedactedKeys(String[] redactedKeys) { } public String[] getDiscardClasses() { - return discardClasses; + return discardClassPatterns.toArray(new String[0]); } public void setDiscardClasses(String[] discardClasses) { - this.discardClasses = discardClasses; + this.discardClasses.clear(); + this.discardClassPatterns.clear(); + if (discardClasses != null) { + for (String pattern : discardClasses) { + if (pattern != null && !pattern.isEmpty()) { + // Store original pattern string + this.discardClassPatterns.add(pattern); + // Convert glob-style wildcards to regex + String regex = convertToRegex(pattern); + this.discardClasses.add(Pattern.compile(regex)); + } + } + } + } + + /** + * Converts a glob-style pattern to a regex pattern. + * Supports * (matches any characters) and ? (matches single character). + * If the pattern doesn't contain wildcards, it's treated as an exact match. + * + * @param pattern the glob-style pattern + * @return the regex pattern + */ + private String convertToRegex(String pattern) { + // If the pattern doesn't contain wildcards, match exactly + if (!pattern.contains("*") && !pattern.contains("?")) { + return Pattern.quote(pattern); + } + + StringBuilder regex = new StringBuilder(); + for (int i = 0; i < pattern.length(); i++) { + char c = pattern.charAt(i); + switch (c) { + case '*': + regex.append(".*"); + break; + case '?': + regex.append("."); + break; + case '.': + case '(': + case ')': + case '+': + case '|': + case '^': + case '$': + case '@': + case '%': + case '[': + case ']': + case '{': + case '}': + case '\\': + regex.append('\\').append(c); + break; + default: + regex.append(c); + break; + } + } + return regex.toString(); } public Set getEnabledReleaseStages() { diff --git a/bugsnag/src/test/java/com/bugsnag/ConfigurationDiscardClassesTest.java b/bugsnag/src/test/java/com/bugsnag/ConfigurationDiscardClassesTest.java new file mode 100644 index 00000000..1f570998 --- /dev/null +++ b/bugsnag/src/test/java/com/bugsnag/ConfigurationDiscardClassesTest.java @@ -0,0 +1,110 @@ +package com.bugsnag; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; + +import org.junit.Before; +import org.junit.Test; + +/** + * Test for Configuration.shouldIgnoreClass with pattern matching + */ +public class ConfigurationDiscardClassesTest { + + private Configuration config; + + @Before + public void setUp() { + config = new Configuration("test-api-key"); + } + + @Test + public void testExactMatch() { + config.setDiscardClasses(new String[] {"com.example.CustomException"}); + + assertTrue(config.shouldIgnoreClass("com.example.CustomException")); + assertFalse(config.shouldIgnoreClass("com.example.OtherException")); + } + + @Test + public void testWildcardMatch() { + config.setDiscardClasses(new String[] {"com.example.*"}); + + assertTrue(config.shouldIgnoreClass("com.example.CustomException")); + assertTrue(config.shouldIgnoreClass("com.example.OtherException")); + assertTrue(config.shouldIgnoreClass("com.example.")); + assertFalse(config.shouldIgnoreClass("com.other.Exception")); + } + + @Test + public void testMultipleWildcards() { + config.setDiscardClasses(new String[] {"com.*.Exception"}); + + assertTrue(config.shouldIgnoreClass("com.example.Exception")); + assertTrue(config.shouldIgnoreClass("com.other.Exception")); + assertFalse(config.shouldIgnoreClass("com.example.CustomException")); + } + + @Test + public void testQuestionMarkWildcard() { + config.setDiscardClasses(new String[] {"com.example.Exception?"}); + + assertTrue(config.shouldIgnoreClass("com.example.Exception1")); + assertTrue(config.shouldIgnoreClass("com.example.ExceptionX")); + assertFalse(config.shouldIgnoreClass("com.example.Exception")); + assertFalse(config.shouldIgnoreClass("com.example.Exception12")); + } + + @Test + public void testMultiplePatterns() { + config.setDiscardClasses(new String[] { + "java.io.*", + "com.example.CustomException", + "org.*.SpecialException" + }); + + assertTrue(config.shouldIgnoreClass("java.io.IOException")); + assertTrue(config.shouldIgnoreClass("java.io.FileNotFoundException")); + assertTrue(config.shouldIgnoreClass("com.example.CustomException")); + assertTrue(config.shouldIgnoreClass("org.apache.SpecialException")); + assertTrue(config.shouldIgnoreClass("org.springframework.SpecialException")); + assertFalse(config.shouldIgnoreClass("com.example.OtherException")); + } + + @Test + public void testGetDiscardClassesReturnsOriginalPatterns() { + String[] patterns = new String[] {"com.example.*", "java.io.IOException"}; + config.setDiscardClasses(patterns); + + String[] retrieved = config.getDiscardClasses(); + assertEquals(2, retrieved.length); + + // Check that patterns are returned (not regex) + boolean hasWildcard = false; + boolean hasExact = false; + for (String pattern : retrieved) { + if (pattern.equals("com.example.*")) hasWildcard = true; + if (pattern.equals("java.io.IOException")) hasExact = true; + } + assertTrue(hasWildcard); + assertTrue(hasExact); + } + + @Test + public void testEmptyAndNullPatterns() { + config.setDiscardClasses(new String[] {}); + assertFalse(config.shouldIgnoreClass("com.example.Exception")); + + config.setDiscardClasses(null); + assertFalse(config.shouldIgnoreClass("com.example.Exception")); + } + + @Test + public void testSpecialCharactersAreEscaped() { + config.setDiscardClasses(new String[] {"com.example.Exception$Inner"}); + + assertTrue(config.shouldIgnoreClass("com.example.Exception$Inner")); + assertFalse(config.shouldIgnoreClass("com.example.ExceptionXInner")); + } +} From aed4bf7c60590486f9aac84629fb0ff9d12f7a7f Mon Sep 17 00:00:00 2001 From: richard elms Date: Wed, 17 Dec 2025 14:07:37 +0100 Subject: [PATCH 2/9] tests --- .../main/java/com/bugsnag/Configuration.java | 14 +++++--- .../ConfigurationDiscardClassesTest.java | 28 +++++++++------- .../logback/ignored_class_wildcard_config.xml | 18 ++++++++++ .../IgnoredExceptionWildcardScenario.java | 32 ++++++++++++++++++ .../MultipleWildcardPatternsScenario.java | 33 +++++++++++++++++++ features/ignored_reports_wildcard.feature | 17 ++++++++++ features/multiple_wildcard_patterns.feature | 7 ++++ 7 files changed, 133 insertions(+), 16 deletions(-) create mode 100644 features/fixtures/logback/ignored_class_wildcard_config.xml create mode 100644 features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/IgnoredExceptionWildcardScenario.java create mode 100644 features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/MultipleWildcardPatternsScenario.java create mode 100644 features/ignored_reports_wildcard.feature create mode 100644 features/multiple_wildcard_patterns.feature diff --git a/bugsnag/src/main/java/com/bugsnag/Configuration.java b/bugsnag/src/main/java/com/bugsnag/Configuration.java index c71de747..e1642e24 100644 --- a/bugsnag/src/main/java/com/bugsnag/Configuration.java +++ b/bugsnag/src/main/java/com/bugsnag/Configuration.java @@ -261,6 +261,12 @@ public String[] getDiscardClasses() { return discardClassPatterns.toArray(new String[0]); } + /** + * Set which exception classes should be ignored (not sent) by Bugsnag. + * Supports glob-style wildcards: * (matches any characters) and ? (matches single character). + * + * @param discardClasses a list of exception class patterns to ignore + */ public void setDiscardClasses(String[] discardClasses) { this.discardClasses.clear(); this.discardClassPatterns.clear(); @@ -293,8 +299,8 @@ private String convertToRegex(String pattern) { StringBuilder regex = new StringBuilder(); for (int i = 0; i < pattern.length(); i++) { - char c = pattern.charAt(i); - switch (c) { + char ch = pattern.charAt(i); + switch (ch) { case '*': regex.append(".*"); break; @@ -315,10 +321,10 @@ private String convertToRegex(String pattern) { case '{': case '}': case '\\': - regex.append('\\').append(c); + regex.append('\\').append(ch); break; default: - regex.append(c); + regex.append(ch); break; } } diff --git a/bugsnag/src/test/java/com/bugsnag/ConfigurationDiscardClassesTest.java b/bugsnag/src/test/java/com/bugsnag/ConfigurationDiscardClassesTest.java index 1f570998..59897582 100644 --- a/bugsnag/src/test/java/com/bugsnag/ConfigurationDiscardClassesTest.java +++ b/bugsnag/src/test/java/com/bugsnag/ConfigurationDiscardClassesTest.java @@ -1,8 +1,8 @@ package com.bugsnag; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertEquals; import org.junit.Before; import org.junit.Test; @@ -22,7 +22,7 @@ public void setUp() { @Test public void testExactMatch() { config.setDiscardClasses(new String[] {"com.example.CustomException"}); - + assertTrue(config.shouldIgnoreClass("com.example.CustomException")); assertFalse(config.shouldIgnoreClass("com.example.OtherException")); } @@ -30,7 +30,7 @@ public void testExactMatch() { @Test public void testWildcardMatch() { config.setDiscardClasses(new String[] {"com.example.*"}); - + assertTrue(config.shouldIgnoreClass("com.example.CustomException")); assertTrue(config.shouldIgnoreClass("com.example.OtherException")); assertTrue(config.shouldIgnoreClass("com.example.")); @@ -40,7 +40,7 @@ public void testWildcardMatch() { @Test public void testMultipleWildcards() { config.setDiscardClasses(new String[] {"com.*.Exception"}); - + assertTrue(config.shouldIgnoreClass("com.example.Exception")); assertTrue(config.shouldIgnoreClass("com.other.Exception")); assertFalse(config.shouldIgnoreClass("com.example.CustomException")); @@ -49,7 +49,7 @@ public void testMultipleWildcards() { @Test public void testQuestionMarkWildcard() { config.setDiscardClasses(new String[] {"com.example.Exception?"}); - + assertTrue(config.shouldIgnoreClass("com.example.Exception1")); assertTrue(config.shouldIgnoreClass("com.example.ExceptionX")); assertFalse(config.shouldIgnoreClass("com.example.Exception")); @@ -63,7 +63,7 @@ public void testMultiplePatterns() { "com.example.CustomException", "org.*.SpecialException" }); - + assertTrue(config.shouldIgnoreClass("java.io.IOException")); assertTrue(config.shouldIgnoreClass("java.io.FileNotFoundException")); assertTrue(config.shouldIgnoreClass("com.example.CustomException")); @@ -76,16 +76,20 @@ public void testMultiplePatterns() { public void testGetDiscardClassesReturnsOriginalPatterns() { String[] patterns = new String[] {"com.example.*", "java.io.IOException"}; config.setDiscardClasses(patterns); - + String[] retrieved = config.getDiscardClasses(); assertEquals(2, retrieved.length); - + // Check that patterns are returned (not regex) boolean hasWildcard = false; boolean hasExact = false; for (String pattern : retrieved) { - if (pattern.equals("com.example.*")) hasWildcard = true; - if (pattern.equals("java.io.IOException")) hasExact = true; + if (pattern.equals("com.example.*")) { + hasWildcard = true; + } + if (pattern.equals("java.io.IOException")) { + hasExact = true; + } } assertTrue(hasWildcard); assertTrue(hasExact); @@ -95,7 +99,7 @@ public void testGetDiscardClassesReturnsOriginalPatterns() { public void testEmptyAndNullPatterns() { config.setDiscardClasses(new String[] {}); assertFalse(config.shouldIgnoreClass("com.example.Exception")); - + config.setDiscardClasses(null); assertFalse(config.shouldIgnoreClass("com.example.Exception")); } @@ -103,7 +107,7 @@ public void testEmptyAndNullPatterns() { @Test public void testSpecialCharactersAreEscaped() { config.setDiscardClasses(new String[] {"com.example.Exception$Inner"}); - + assertTrue(config.shouldIgnoreClass("com.example.Exception$Inner")); assertFalse(config.shouldIgnoreClass("com.example.ExceptionXInner")); } diff --git a/features/fixtures/logback/ignored_class_wildcard_config.xml b/features/fixtures/logback/ignored_class_wildcard_config.xml new file mode 100644 index 00000000..ac51d079 --- /dev/null +++ b/features/fixtures/logback/ignored_class_wildcard_config.xml @@ -0,0 +1,18 @@ + + + + + + a35a2a72bd230ac0aa0f52715bbdc6aa + production + 1.0.0 + + java.lang.* + + http://localhost:9339/notify + + + + + + diff --git a/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/IgnoredExceptionWildcardScenario.java b/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/IgnoredExceptionWildcardScenario.java new file mode 100644 index 00000000..a5eb258f --- /dev/null +++ b/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/IgnoredExceptionWildcardScenario.java @@ -0,0 +1,32 @@ +package com.bugsnag.mazerunner.scenarios; + +import com.bugsnag.Bugsnag; + +/** + * Attempts to send ignored handled exceptions using wildcard patterns to Bugsnag, + * which should not result in any operation. + */ +public class IgnoredExceptionWildcardScenario extends Scenario { + + public IgnoredExceptionWildcardScenario(Bugsnag bugsnag) { + super(bugsnag); + } + + @Override + public void run() { + // Use wildcard pattern to ignore all RuntimeException and its subclasses + bugsnag.setDiscardClasses("java.lang.*"); + + // These should all be ignored due to the wildcard pattern + bugsnag.notify(new RuntimeException("Should never appear")); + bugsnag.notify(new IllegalArgumentException("Should never appear")); + bugsnag.notify(new IllegalStateException("Should never appear")); + + // This should also be sent but will be ignored due to pattern + try { + throw new NullPointerException("Should never appear"); + } catch (Exception e) { + bugsnag.notify(e); + } + } +} diff --git a/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/MultipleWildcardPatternsScenario.java b/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/MultipleWildcardPatternsScenario.java new file mode 100644 index 00000000..ed2d553e --- /dev/null +++ b/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/MultipleWildcardPatternsScenario.java @@ -0,0 +1,33 @@ +package com.bugsnag.mazerunner.scenarios; + +import com.bugsnag.Bugsnag; + +/** + * Tests multiple wildcard patterns working together. + * Uses both * and ? wildcards along with exact matches. + */ +public class MultipleWildcardPatternsScenario extends Scenario { + + public MultipleWildcardPatternsScenario(Bugsnag bugsnag) { + super(bugsnag); + } + + @Override + public void run() { + // Set multiple patterns: wildcards and exact matches + bugsnag.setDiscardClasses( + "java.io.*", // All java.io exceptions + "java.lang.IllegalStateException", // Exact match + "java.lang.Illegal*" // All IllegalXException classes + ); + + // These should all be ignored + bugsnag.notify(new java.io.IOException("Should be ignored - java.io.*")); + bugsnag.notify(new java.io.FileNotFoundException("Should be ignored - java.io.*")); + bugsnag.notify(new IllegalStateException("Should be ignored - exact match")); + bugsnag.notify(new IllegalArgumentException("Should be ignored - java.lang.Illegal*")); + + // This should be sent (not matching any pattern) + bugsnag.notify(new RuntimeException("Should be sent")); + } +} diff --git a/features/ignored_reports_wildcard.feature b/features/ignored_reports_wildcard.feature new file mode 100644 index 00000000..d35b87b3 --- /dev/null +++ b/features/ignored_reports_wildcard.feature @@ -0,0 +1,17 @@ +Feature: Reports are ignored with wildcard patterns + +Scenario: Exception classname ignored with wildcard in plain Java app + When I run "IgnoredExceptionWildcardScenario" with the defaults + Then I should receive no errors + +Scenario: Exception classname ignored with wildcard in spring boot app + When I run spring boot "IgnoredExceptionWildcardScenario" with the defaults + Then I should receive no errors + +Scenario: Exception classname ignored with wildcard in plain spring app + When I run plain Spring "IgnoredExceptionWildcardScenario" with the defaults + Then I should receive no errors + +Scenario: Test logback appender with wildcard pattern for ignored error class + When I run "LogbackScenario" with logback config "ignored_class_wildcard_config.xml" + Then I should receive no errors diff --git a/features/multiple_wildcard_patterns.feature b/features/multiple_wildcard_patterns.feature new file mode 100644 index 00000000..4c045324 --- /dev/null +++ b/features/multiple_wildcard_patterns.feature @@ -0,0 +1,7 @@ +Feature: Multiple wildcard patterns for ignoring reports + +Scenario: Multiple wildcard patterns in plain Java app + When I run "MultipleWildcardPatternsScenario" with the defaults + Then I should receive 1 error + And the exception "errorClass" equals "java.lang.RuntimeException" + And the exception "message" equals "Should be sent" From 27a926fb24bc25bd34f4b90e67dc1209d0586fcd Mon Sep 17 00:00:00 2001 From: richard elms Date: Wed, 17 Dec 2025 15:02:16 +0100 Subject: [PATCH 3/9] syntax fix --- features/multiple_wildcard_patterns.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/multiple_wildcard_patterns.feature b/features/multiple_wildcard_patterns.feature index 4c045324..49f9b400 100644 --- a/features/multiple_wildcard_patterns.feature +++ b/features/multiple_wildcard_patterns.feature @@ -2,6 +2,6 @@ Feature: Multiple wildcard patterns for ignoring reports Scenario: Multiple wildcard patterns in plain Java app When I run "MultipleWildcardPatternsScenario" with the defaults - Then I should receive 1 error + And I wait to receive an error And the exception "errorClass" equals "java.lang.RuntimeException" And the exception "message" equals "Should be sent" From 67a925b0d9c7586978d0d5c1477c3a80b591fcb1 Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Thu, 18 Dec 2025 09:55:08 +0100 Subject: [PATCH 4/9] Update features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/IgnoredExceptionWildcardScenario.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../mazerunner/scenarios/IgnoredExceptionWildcardScenario.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/IgnoredExceptionWildcardScenario.java b/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/IgnoredExceptionWildcardScenario.java index a5eb258f..6a60941b 100644 --- a/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/IgnoredExceptionWildcardScenario.java +++ b/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/IgnoredExceptionWildcardScenario.java @@ -22,7 +22,7 @@ public void run() { bugsnag.notify(new IllegalArgumentException("Should never appear")); bugsnag.notify(new IllegalStateException("Should never appear")); - // This should also be sent but will be ignored due to pattern + // This is also ignored due to the wildcard pattern try { throw new NullPointerException("Should never appear"); } catch (Exception e) { From 374cd91aca0a19f542146bc7ebce833afeec6061 Mon Sep 17 00:00:00 2001 From: richard elms Date: Thu, 18 Dec 2025 10:06:49 +0100 Subject: [PATCH 5/9] review changes --- .../main/java/com/bugsnag/Configuration.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/bugsnag/src/main/java/com/bugsnag/Configuration.java b/bugsnag/src/main/java/com/bugsnag/Configuration.java index e1642e24..ad091cc3 100644 --- a/bugsnag/src/main/java/com/bugsnag/Configuration.java +++ b/bugsnag/src/main/java/com/bugsnag/Configuration.java @@ -38,8 +38,8 @@ public class Configuration { private EndpointConfiguration endpoints; private Delivery sessionDelivery; private String[] redactedKeys = new String[] {"password", "secret", "Authorization", "Cookie"}; - private Set discardClasses = new HashSet(); - private Set discardClassPatterns = new HashSet(); + private Set discardClassRegexPatterns = new HashSet(); + private Set discardClassStringPatterns = new HashSet(); private Set enabledReleaseStages = null; private String[] projectPackages; private String releaseStage; @@ -75,11 +75,11 @@ boolean shouldNotifyForReleaseStage() { } boolean shouldIgnoreClass(String className) { - if (discardClasses == null || discardClasses.isEmpty()) { + if (discardClassRegexPatterns == null || discardClassRegexPatterns.isEmpty()) { return false; } - for (Pattern pattern : discardClasses) { + for (Pattern pattern : discardClassRegexPatterns) { if (pattern.matcher(className).matches()) { return true; } @@ -258,26 +258,27 @@ public void setRedactedKeys(String[] redactedKeys) { } public String[] getDiscardClasses() { - return discardClassPatterns.toArray(new String[0]); + return discardClassStringPatterns.toArray(new String[0]); } /** * Set which exception classes should be ignored (not sent) by Bugsnag. - * Supports glob-style wildcards: * (matches any characters) and ? (matches single character). + * Supports glob-style wildcards: * (matches any characters, including package + * separators '.') and ? (matches a single character). * * @param discardClasses a list of exception class patterns to ignore */ public void setDiscardClasses(String[] discardClasses) { - this.discardClasses.clear(); - this.discardClassPatterns.clear(); + this.discardClassRegexPatterns.clear(); + this.discardClassStringPatterns.clear(); if (discardClasses != null) { for (String pattern : discardClasses) { if (pattern != null && !pattern.isEmpty()) { // Store original pattern string - this.discardClassPatterns.add(pattern); + this.discardClassStringPatterns.add(pattern); // Convert glob-style wildcards to regex String regex = convertToRegex(pattern); - this.discardClasses.add(Pattern.compile(regex)); + this.discardClassRegexPatterns.add(Pattern.compile(regex)); } } } From 859b52c3a9e0c4eb3ebbda9bf18e0bbeb9073f73 Mon Sep 17 00:00:00 2001 From: richard elms Date: Thu, 18 Dec 2025 10:19:53 +0100 Subject: [PATCH 6/9] remove md file --- DISCARD_CLASSES_PATTERN_MATCHING.md | 70 ----------------------------- 1 file changed, 70 deletions(-) delete mode 100644 DISCARD_CLASSES_PATTERN_MATCHING.md diff --git a/DISCARD_CLASSES_PATTERN_MATCHING.md b/DISCARD_CLASSES_PATTERN_MATCHING.md deleted file mode 100644 index fd62da75..00000000 --- a/DISCARD_CLASSES_PATTERN_MATCHING.md +++ /dev/null @@ -1,70 +0,0 @@ -# discardClasses Pattern Matching - -The `discardClasses` configuration has been enhanced to support pattern matching using wildcards. - -## Features - -### Exact Matching (Backward Compatible) -Works exactly as before for exact class names: -```java -config.setDiscardClasses(new String[] { - "com.example.CustomException", - "java.io.IOException" -}); -``` - -### Wildcard Patterns -Now supports glob-style wildcards: - -**`*` - Matches any characters (including dots)** -```java -config.setDiscardClasses(new String[] { - "com.example.*" // Matches all classes in com.example package -}); -// Matches: com.example.CustomException, com.example.OtherException, etc. -``` - -**`?` - Matches a single character** -```java -config.setDiscardClasses(new String[] { - "com.example.Exception?" -}); -// Matches: com.example.Exception1, com.example.ExceptionX -// Does not match: com.example.Exception, com.example.Exception12 -``` - -### Combined Patterns -Mix exact matches and wildcards: -```java -config.setDiscardClasses(new String[] { - "java.io.*", // All java.io exceptions - "com.*.CustomException", // CustomException in any com.* package - "org.example.SpecificException" // Exact match -}); -``` - -## Appender Compatibility - -The BugsnagAppender continues to work exactly as before. When setting discard classes through the appender: - -```xml - - com.example.*,java.io.IOException - -``` - -Or programmatically: -```java -appender.setDiscardClass("com.example.*"); -appender.setDiscardClass("java.io.IOException"); -``` - -The appender internally converts these to the Configuration's pattern set format. - -## Implementation Details - -- Patterns without wildcards are treated as exact matches (using `Pattern.quote()` internally) -- Special regex characters are properly escaped -- The `getDiscardClasses()` method returns the original pattern strings (not compiled regex patterns) -- Pattern matching is case-sensitive -- Empty or null patterns are safely ignored From d0a8b22f279a0a76c4146eb7ca312fdd95398430 Mon Sep 17 00:00:00 2001 From: richard elms Date: Thu, 18 Dec 2025 12:14:28 +0100 Subject: [PATCH 7/9] convert away from blob --- .../main/java/com/bugsnag/Configuration.java | 60 ++----------------- .../test/java/com/bugsnag/BugsnagTest.java | 12 ++-- .../ConfigurationDiscardClassesTest.java | 23 +++---- .../logback/ignored_class_wildcard_config.xml | 2 +- .../IgnoredExceptionWildcardScenario.java | 12 ++-- .../MultipleWildcardPatternsScenario.java | 19 +++--- 6 files changed, 42 insertions(+), 86 deletions(-) diff --git a/bugsnag/src/main/java/com/bugsnag/Configuration.java b/bugsnag/src/main/java/com/bugsnag/Configuration.java index ad091cc3..19a085f8 100644 --- a/bugsnag/src/main/java/com/bugsnag/Configuration.java +++ b/bugsnag/src/main/java/com/bugsnag/Configuration.java @@ -263,10 +263,11 @@ public String[] getDiscardClasses() { /** * Set which exception classes should be ignored (not sent) by Bugsnag. - * Supports glob-style wildcards: * (matches any characters, including package - * separators '.') and ? (matches a single character). + * Supports Java regex patterns for matching exception class names. + * For exact matches, use the fully qualified class name without regex metacharacters. + * For pattern matching, use standard Java regex syntax (e.g., "java\\.io\\..*" to match all java.io exceptions). * - * @param discardClasses a list of exception class patterns to ignore + * @param discardClasses a list of exception class name patterns (regex) to ignore */ public void setDiscardClasses(String[] discardClasses) { this.discardClassRegexPatterns.clear(); @@ -276,62 +277,13 @@ public void setDiscardClasses(String[] discardClasses) { if (pattern != null && !pattern.isEmpty()) { // Store original pattern string this.discardClassStringPatterns.add(pattern); - // Convert glob-style wildcards to regex - String regex = convertToRegex(pattern); - this.discardClassRegexPatterns.add(Pattern.compile(regex)); + // Compile as regex pattern + this.discardClassRegexPatterns.add(Pattern.compile(pattern)); } } } } - /** - * Converts a glob-style pattern to a regex pattern. - * Supports * (matches any characters) and ? (matches single character). - * If the pattern doesn't contain wildcards, it's treated as an exact match. - * - * @param pattern the glob-style pattern - * @return the regex pattern - */ - private String convertToRegex(String pattern) { - // If the pattern doesn't contain wildcards, match exactly - if (!pattern.contains("*") && !pattern.contains("?")) { - return Pattern.quote(pattern); - } - - StringBuilder regex = new StringBuilder(); - for (int i = 0; i < pattern.length(); i++) { - char ch = pattern.charAt(i); - switch (ch) { - case '*': - regex.append(".*"); - break; - case '?': - regex.append("."); - break; - case '.': - case '(': - case ')': - case '+': - case '|': - case '^': - case '$': - case '@': - case '%': - case '[': - case ']': - case '{': - case '}': - case '\\': - regex.append('\\').append(ch); - break; - default: - regex.append(ch); - break; - } - } - return regex.toString(); - } - public Set getEnabledReleaseStages() { return enabledReleaseStages; } diff --git a/bugsnag/src/test/java/com/bugsnag/BugsnagTest.java b/bugsnag/src/test/java/com/bugsnag/BugsnagTest.java index 23d7db97..91d0cf89 100644 --- a/bugsnag/src/test/java/com/bugsnag/BugsnagTest.java +++ b/bugsnag/src/test/java/com/bugsnag/BugsnagTest.java @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.regex.Pattern; public class BugsnagTest { @@ -61,13 +62,16 @@ public void testIgnoreClasses() { assertTrue(bugsnag.notify(new RuntimeException())); assertTrue(bugsnag.notify(new TestException())); - // Ignore just RuntimeException - bugsnag.setDiscardClasses(RuntimeException.class.getName()); + // Ignore just RuntimeException (escape dots for regex) + bugsnag.setDiscardClasses(Pattern.quote(RuntimeException.class.getName())); assertFalse(bugsnag.notify(new RuntimeException())); assertTrue(bugsnag.notify(new TestException())); - // Ignore both - bugsnag.setDiscardClasses(RuntimeException.class.getName(), TestException.class.getName()); + // Ignore both (escape special regex characters) + bugsnag.setDiscardClasses( + Pattern.quote(RuntimeException.class.getName()), + Pattern.quote(TestException.class.getName()) + ); assertFalse(bugsnag.notify(new RuntimeException())); assertFalse(bugsnag.notify(new TestException())); } diff --git a/bugsnag/src/test/java/com/bugsnag/ConfigurationDiscardClassesTest.java b/bugsnag/src/test/java/com/bugsnag/ConfigurationDiscardClassesTest.java index 59897582..76b47183 100644 --- a/bugsnag/src/test/java/com/bugsnag/ConfigurationDiscardClassesTest.java +++ b/bugsnag/src/test/java/com/bugsnag/ConfigurationDiscardClassesTest.java @@ -8,7 +8,7 @@ import org.junit.Test; /** - * Test for Configuration.shouldIgnoreClass with pattern matching + * Test for Configuration.shouldIgnoreClass with regex pattern matching */ public class ConfigurationDiscardClassesTest { @@ -29,7 +29,7 @@ public void testExactMatch() { @Test public void testWildcardMatch() { - config.setDiscardClasses(new String[] {"com.example.*"}); + config.setDiscardClasses(new String[] {"com\\.example\\..*"}); assertTrue(config.shouldIgnoreClass("com.example.CustomException")); assertTrue(config.shouldIgnoreClass("com.example.OtherException")); @@ -39,7 +39,7 @@ public void testWildcardMatch() { @Test public void testMultipleWildcards() { - config.setDiscardClasses(new String[] {"com.*.Exception"}); + config.setDiscardClasses(new String[] {"com\\..*\\.Exception"}); assertTrue(config.shouldIgnoreClass("com.example.Exception")); assertTrue(config.shouldIgnoreClass("com.other.Exception")); @@ -48,7 +48,7 @@ public void testMultipleWildcards() { @Test public void testQuestionMarkWildcard() { - config.setDiscardClasses(new String[] {"com.example.Exception?"}); + config.setDiscardClasses(new String[] {"com\\.example\\.Exception."}); assertTrue(config.shouldIgnoreClass("com.example.Exception1")); assertTrue(config.shouldIgnoreClass("com.example.ExceptionX")); @@ -59,9 +59,9 @@ public void testQuestionMarkWildcard() { @Test public void testMultiplePatterns() { config.setDiscardClasses(new String[] { - "java.io.*", - "com.example.CustomException", - "org.*.SpecialException" + "java\\.io\\..*", + "com\\.example\\.CustomException", + "org\\..*\\.SpecialException" }); assertTrue(config.shouldIgnoreClass("java.io.IOException")); @@ -74,7 +74,7 @@ public void testMultiplePatterns() { @Test public void testGetDiscardClassesReturnsOriginalPatterns() { - String[] patterns = new String[] {"com.example.*", "java.io.IOException"}; + String[] patterns = new String[] {"com\\.example\\..*", "java\\.io\\.IOException"}; config.setDiscardClasses(patterns); String[] retrieved = config.getDiscardClasses(); @@ -84,10 +84,10 @@ public void testGetDiscardClassesReturnsOriginalPatterns() { boolean hasWildcard = false; boolean hasExact = false; for (String pattern : retrieved) { - if (pattern.equals("com.example.*")) { + if (pattern.equals("com\\.example\\..*")) { hasWildcard = true; } - if (pattern.equals("java.io.IOException")) { + if (pattern.equals("java\\.io\\.IOException")) { hasExact = true; } } @@ -106,7 +106,8 @@ public void testEmptyAndNullPatterns() { @Test public void testSpecialCharactersAreEscaped() { - config.setDiscardClasses(new String[] {"com.example.Exception$Inner"}); + // In regex, $ is a special character (end of line), so it needs to be escaped + config.setDiscardClasses(new String[] {"com\\.example\\.Exception\\$Inner"}); assertTrue(config.shouldIgnoreClass("com.example.Exception$Inner")); assertFalse(config.shouldIgnoreClass("com.example.ExceptionXInner")); diff --git a/features/fixtures/logback/ignored_class_wildcard_config.xml b/features/fixtures/logback/ignored_class_wildcard_config.xml index ac51d079..12cbaa70 100644 --- a/features/fixtures/logback/ignored_class_wildcard_config.xml +++ b/features/fixtures/logback/ignored_class_wildcard_config.xml @@ -7,7 +7,7 @@ production 1.0.0 - java.lang.* + java\.lang\..* http://localhost:9339/notify diff --git a/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/IgnoredExceptionWildcardScenario.java b/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/IgnoredExceptionWildcardScenario.java index 6a60941b..b4a10935 100644 --- a/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/IgnoredExceptionWildcardScenario.java +++ b/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/IgnoredExceptionWildcardScenario.java @@ -3,7 +3,7 @@ import com.bugsnag.Bugsnag; /** - * Attempts to send ignored handled exceptions using wildcard patterns to Bugsnag, + * Attempts to send ignored handled exceptions using regex patterns to Bugsnag, * which should not result in any operation. */ public class IgnoredExceptionWildcardScenario extends Scenario { @@ -14,15 +14,15 @@ public IgnoredExceptionWildcardScenario(Bugsnag bugsnag) { @Override public void run() { - // Use wildcard pattern to ignore all RuntimeException and its subclasses - bugsnag.setDiscardClasses("java.lang.*"); + // Use regex pattern to ignore all java.lang exceptions + bugsnag.setDiscardClasses("java\\.lang\\..*"); - // These should all be ignored due to the wildcard pattern + // These should all be ignored due to the regex pattern bugsnag.notify(new RuntimeException("Should never appear")); bugsnag.notify(new IllegalArgumentException("Should never appear")); bugsnag.notify(new IllegalStateException("Should never appear")); - - // This is also ignored due to the wildcard pattern + + // This is also ignored due to the regex pattern try { throw new NullPointerException("Should never appear"); } catch (Exception e) { diff --git a/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/MultipleWildcardPatternsScenario.java b/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/MultipleWildcardPatternsScenario.java index ed2d553e..4a98701c 100644 --- a/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/MultipleWildcardPatternsScenario.java +++ b/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/MultipleWildcardPatternsScenario.java @@ -3,8 +3,7 @@ import com.bugsnag.Bugsnag; /** - * Tests multiple wildcard patterns working together. - * Uses both * and ? wildcards along with exact matches. + * Tests multiple regex patterns working together. */ public class MultipleWildcardPatternsScenario extends Scenario { @@ -14,19 +13,19 @@ public MultipleWildcardPatternsScenario(Bugsnag bugsnag) { @Override public void run() { - // Set multiple patterns: wildcards and exact matches + // Set multiple regex patterns: matching specific packages and classes bugsnag.setDiscardClasses( - "java.io.*", // All java.io exceptions - "java.lang.IllegalStateException", // Exact match - "java.lang.Illegal*" // All IllegalXException classes + "java\\.io\\..*", // All java.io exceptions + "java\\.lang\\.IllegalStateException", // Exact match + "java\\.lang\\.Illegal.*" // All IllegalXException classes ); // These should all be ignored - bugsnag.notify(new java.io.IOException("Should be ignored - java.io.*")); - bugsnag.notify(new java.io.FileNotFoundException("Should be ignored - java.io.*")); + bugsnag.notify(new java.io.IOException("Should be ignored - java\\.io\\..*")); + bugsnag.notify(new java.io.FileNotFoundException("Should be ignored - java\\.io\\..*")); bugsnag.notify(new IllegalStateException("Should be ignored - exact match")); - bugsnag.notify(new IllegalArgumentException("Should be ignored - java.lang.Illegal*")); - + bugsnag.notify(new IllegalArgumentException("Should be ignored - java\\.lang\\.Illegal.*")); + // This should be sent (not matching any pattern) bugsnag.notify(new RuntimeException("Should be sent")); } From fd79d6ddfe089fbf63db91495be73ddb912992e8 Mon Sep 17 00:00:00 2001 From: richard elms Date: Thu, 18 Dec 2025 12:22:32 +0100 Subject: [PATCH 8/9] full pattern support --- .../src/main/java/com/bugsnag/Bugsnag.java | 6 ++- .../java/com/bugsnag/BugsnagAppender.java | 25 ++++++++--- .../main/java/com/bugsnag/Configuration.java | 41 ++++++++++++++----- .../test/java/com/bugsnag/AppenderTest.java | 17 ++++++-- .../test/java/com/bugsnag/BugsnagTest.java | 10 ++--- .../ConfigurationDiscardClassesTest.java | 37 +++++++++-------- .../scenarios/IgnoredExceptionScenario.java | 4 +- .../IgnoredExceptionWildcardScenario.java | 4 +- .../MultipleWildcardPatternsScenario.java | 8 ++-- 9 files changed, 105 insertions(+), 47 deletions(-) diff --git a/bugsnag/src/main/java/com/bugsnag/Bugsnag.java b/bugsnag/src/main/java/com/bugsnag/Bugsnag.java index 8914ac15..94af76bd 100644 --- a/bugsnag/src/main/java/com/bugsnag/Bugsnag.java +++ b/bugsnag/src/main/java/com/bugsnag/Bugsnag.java @@ -22,6 +22,7 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; public class Bugsnag implements Closeable { private static final Logger LOGGER = LoggerFactory.getLogger(Bugsnag.class); @@ -241,10 +242,11 @@ public void setRedactedKeys(String... redactedKeys) { /** * Set which exception classes should be ignored (not sent) by Bugsnag. + * Uses Java regex patterns for matching exception class names. * - * @param discardClasses a list of exception classes to ignore + * @param discardClasses compiled regex patterns to match exception class names */ - public void setDiscardClasses(String... discardClasses) { + public void setDiscardClasses(Pattern... discardClasses) { config.setDiscardClasses(discardClasses); } diff --git a/bugsnag/src/main/java/com/bugsnag/BugsnagAppender.java b/bugsnag/src/main/java/com/bugsnag/BugsnagAppender.java index 07f42b56..590b00b1 100644 --- a/bugsnag/src/main/java/com/bugsnag/BugsnagAppender.java +++ b/bugsnag/src/main/java/com/bugsnag/BugsnagAppender.java @@ -262,7 +262,12 @@ private Bugsnag createBugsnag() { bugsnag.setRedactedKeys(redactedKeys.toArray(new String[0])); } - bugsnag.setDiscardClasses(discardClasses.toArray(new String[0])); + Pattern[] discardPatterns = new Pattern[discardClasses.size()]; + int idx = 0; + for (String pattern : discardClasses) { + discardPatterns[idx++] = Pattern.compile(pattern); + } + bugsnag.setDiscardClasses(discardPatterns); if (!enabledReleaseStages.isEmpty()) { bugsnag.setEnabledReleaseStages(enabledReleaseStages.toArray(new String[0])); @@ -406,24 +411,34 @@ public void setIgnoredClass(String ignoredClass) { } /** - * @see Bugsnag#setDiscardClasses(String...) + * @see Bugsnag#setDiscardClasses(Pattern...) */ public void setDiscardClass(String discardClass) { this.discardClasses.add(discardClass); if (bugsnag != null) { - bugsnag.setDiscardClasses(this.discardClasses.toArray(new String[0])); + Pattern[] discardPatterns = new Pattern[this.discardClasses.size()]; + int idx = 0; + for (String pattern : this.discardClasses) { + discardPatterns[idx++] = Pattern.compile(pattern); + } + bugsnag.setDiscardClasses(discardPatterns); } } /** - * @see Bugsnag#setDiscardClasses(String...) + * @see Bugsnag#setDiscardClasses(Pattern...) */ public void setDiscardClasses(String discardClasses) { this.discardClasses.addAll(split(discardClasses)); if (bugsnag != null) { - bugsnag.setDiscardClasses(this.discardClasses.toArray(new String[0])); + Pattern[] discardPatterns = new Pattern[this.discardClasses.size()]; + int idx = 0; + for (String pattern : this.discardClasses) { + discardPatterns[idx++] = Pattern.compile(pattern); + } + bugsnag.setDiscardClasses(discardPatterns); } } diff --git a/bugsnag/src/main/java/com/bugsnag/Configuration.java b/bugsnag/src/main/java/com/bugsnag/Configuration.java index 19a085f8..f6424e2d 100644 --- a/bugsnag/src/main/java/com/bugsnag/Configuration.java +++ b/bugsnag/src/main/java/com/bugsnag/Configuration.java @@ -257,28 +257,47 @@ public void setRedactedKeys(String[] redactedKeys) { this.redactedKeys = redactedKeys; } - public String[] getDiscardClasses() { - return discardClassStringPatterns.toArray(new String[0]); + public Pattern[] getDiscardClasses() { + return discardClassRegexPatterns.toArray(new Pattern[0]); } /** * Set which exception classes should be ignored (not sent) by Bugsnag. - * Supports Java regex patterns for matching exception class names. - * For exact matches, use the fully qualified class name without regex metacharacters. - * For pattern matching, use standard Java regex syntax (e.g., "java\\.io\\..*" to match all java.io exceptions). + * Uses Java regex patterns for matching exception class names. * - * @param discardClasses a list of exception class name patterns (regex) to ignore + * @param discardClasses a list of compiled regex patterns to match exception class names */ - public void setDiscardClasses(String[] discardClasses) { + public void setDiscardClasses(Pattern[] discardClasses) { this.discardClassRegexPatterns.clear(); this.discardClassStringPatterns.clear(); if (discardClasses != null) { - for (String pattern : discardClasses) { - if (pattern != null && !pattern.isEmpty()) { + for (Pattern pattern : discardClasses) { + if (pattern != null) { + // Store pattern + this.discardClassRegexPatterns.add(pattern); + // Store string representation for serialization + this.discardClassStringPatterns.add(pattern.pattern()); + } + } + } + } + + /** + * Set which exception classes should be ignored (not sent) by Bugsnag. + * Compiles the provided strings as Java regex patterns. + * + * @param discardClasses a list of regex pattern strings to match exception class names + */ + public void setDiscardClassesFromStrings(String[] discardClasses) { + this.discardClassRegexPatterns.clear(); + this.discardClassStringPatterns.clear(); + if (discardClasses != null) { + for (String patternStr : discardClasses) { + if (patternStr != null && !patternStr.isEmpty()) { // Store original pattern string - this.discardClassStringPatterns.add(pattern); + this.discardClassStringPatterns.add(patternStr); // Compile as regex pattern - this.discardClassRegexPatterns.add(Pattern.compile(pattern)); + this.discardClassRegexPatterns.add(Pattern.compile(patternStr)); } } } diff --git a/bugsnag/src/test/java/com/bugsnag/AppenderTest.java b/bugsnag/src/test/java/com/bugsnag/AppenderTest.java index e0e5756a..a6e99b0b 100644 --- a/bugsnag/src/test/java/com/bugsnag/AppenderTest.java +++ b/bugsnag/src/test/java/com/bugsnag/AppenderTest.java @@ -20,6 +20,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; /** * Test for the Bugsnag Appender @@ -148,9 +149,19 @@ public void testBugsnagConfig() { assertTrue(redactedKeys.contains("credit_card_number")); assertEquals(2, config.getDiscardClasses().length); - ArrayList discardClasses = new ArrayList(Arrays.asList(config.getDiscardClasses())); - assertTrue(discardClasses.contains("com.example.Custom")); - assertTrue(discardClasses.contains("java.io.IOException")); + Pattern[] discardPatterns = config.getDiscardClasses(); + boolean hasCustom = false; + boolean hasIoException = false; + for (Pattern pattern : discardPatterns) { + if (pattern.pattern().equals("com.example.Custom")) { + hasCustom = true; + } + if (pattern.pattern().equals("java.io.IOException")) { + hasIoException = true; + } + } + assertTrue(hasCustom); + assertTrue(hasIoException); assertEquals(2, config.getEnabledReleaseStages().size()); assertTrue(config.getEnabledReleaseStages().contains("development")); diff --git a/bugsnag/src/test/java/com/bugsnag/BugsnagTest.java b/bugsnag/src/test/java/com/bugsnag/BugsnagTest.java index 91d0cf89..b2f6525f 100644 --- a/bugsnag/src/test/java/com/bugsnag/BugsnagTest.java +++ b/bugsnag/src/test/java/com/bugsnag/BugsnagTest.java @@ -62,15 +62,15 @@ public void testIgnoreClasses() { assertTrue(bugsnag.notify(new RuntimeException())); assertTrue(bugsnag.notify(new TestException())); - // Ignore just RuntimeException (escape dots for regex) - bugsnag.setDiscardClasses(Pattern.quote(RuntimeException.class.getName())); + // Ignore just RuntimeException (compile pattern for exact match) + bugsnag.setDiscardClasses(Pattern.compile(Pattern.quote(RuntimeException.class.getName()))); assertFalse(bugsnag.notify(new RuntimeException())); assertTrue(bugsnag.notify(new TestException())); - // Ignore both (escape special regex characters) + // Ignore both (compile patterns for exact matches) bugsnag.setDiscardClasses( - Pattern.quote(RuntimeException.class.getName()), - Pattern.quote(TestException.class.getName()) + Pattern.compile(Pattern.quote(RuntimeException.class.getName())), + Pattern.compile(Pattern.quote(TestException.class.getName())) ); assertFalse(bugsnag.notify(new RuntimeException())); assertFalse(bugsnag.notify(new TestException())); diff --git a/bugsnag/src/test/java/com/bugsnag/ConfigurationDiscardClassesTest.java b/bugsnag/src/test/java/com/bugsnag/ConfigurationDiscardClassesTest.java index 76b47183..58bc1583 100644 --- a/bugsnag/src/test/java/com/bugsnag/ConfigurationDiscardClassesTest.java +++ b/bugsnag/src/test/java/com/bugsnag/ConfigurationDiscardClassesTest.java @@ -7,6 +7,8 @@ import org.junit.Before; import org.junit.Test; +import java.util.regex.Pattern; + /** * Test for Configuration.shouldIgnoreClass with regex pattern matching */ @@ -21,7 +23,7 @@ public void setUp() { @Test public void testExactMatch() { - config.setDiscardClasses(new String[] {"com.example.CustomException"}); + config.setDiscardClasses(new Pattern[] {Pattern.compile("com.example.CustomException")}); assertTrue(config.shouldIgnoreClass("com.example.CustomException")); assertFalse(config.shouldIgnoreClass("com.example.OtherException")); @@ -29,7 +31,7 @@ public void testExactMatch() { @Test public void testWildcardMatch() { - config.setDiscardClasses(new String[] {"com\\.example\\..*"}); + config.setDiscardClasses(new Pattern[] {Pattern.compile("com\\.example\\..*")}); assertTrue(config.shouldIgnoreClass("com.example.CustomException")); assertTrue(config.shouldIgnoreClass("com.example.OtherException")); @@ -39,7 +41,7 @@ public void testWildcardMatch() { @Test public void testMultipleWildcards() { - config.setDiscardClasses(new String[] {"com\\..*\\.Exception"}); + config.setDiscardClasses(new Pattern[] {Pattern.compile("com\\..*\\.Exception")}); assertTrue(config.shouldIgnoreClass("com.example.Exception")); assertTrue(config.shouldIgnoreClass("com.other.Exception")); @@ -48,7 +50,7 @@ public void testMultipleWildcards() { @Test public void testQuestionMarkWildcard() { - config.setDiscardClasses(new String[] {"com\\.example\\.Exception."}); + config.setDiscardClasses(new Pattern[] {Pattern.compile("com\\.example\\.Exception.")}); assertTrue(config.shouldIgnoreClass("com.example.Exception1")); assertTrue(config.shouldIgnoreClass("com.example.ExceptionX")); @@ -58,10 +60,10 @@ public void testQuestionMarkWildcard() { @Test public void testMultiplePatterns() { - config.setDiscardClasses(new String[] { - "java\\.io\\..*", - "com\\.example\\.CustomException", - "org\\..*\\.SpecialException" + config.setDiscardClasses(new Pattern[] { + Pattern.compile("java\\.io\\..*"), + Pattern.compile("com\\.example\\.CustomException"), + Pattern.compile("org\\..*\\.SpecialException") }); assertTrue(config.shouldIgnoreClass("java.io.IOException")); @@ -74,20 +76,23 @@ public void testMultiplePatterns() { @Test public void testGetDiscardClassesReturnsOriginalPatterns() { - String[] patterns = new String[] {"com\\.example\\..*", "java\\.io\\.IOException"}; + Pattern[] patterns = new Pattern[] { + Pattern.compile("com\\.example\\..*"), + Pattern.compile("java\\.io\\.IOException") + }; config.setDiscardClasses(patterns); - String[] retrieved = config.getDiscardClasses(); + Pattern[] retrieved = config.getDiscardClasses(); assertEquals(2, retrieved.length); - // Check that patterns are returned (not regex) + // Check that patterns are returned boolean hasWildcard = false; boolean hasExact = false; - for (String pattern : retrieved) { - if (pattern.equals("com\\.example\\..*")) { + for (Pattern pattern : retrieved) { + if (pattern.pattern().equals("com\\.example\\..*")) { hasWildcard = true; } - if (pattern.equals("java\\.io\\.IOException")) { + if (pattern.pattern().equals("java\\.io\\.IOException")) { hasExact = true; } } @@ -97,7 +102,7 @@ public void testGetDiscardClassesReturnsOriginalPatterns() { @Test public void testEmptyAndNullPatterns() { - config.setDiscardClasses(new String[] {}); + config.setDiscardClasses(new Pattern[] {}); assertFalse(config.shouldIgnoreClass("com.example.Exception")); config.setDiscardClasses(null); @@ -107,7 +112,7 @@ public void testEmptyAndNullPatterns() { @Test public void testSpecialCharactersAreEscaped() { // In regex, $ is a special character (end of line), so it needs to be escaped - config.setDiscardClasses(new String[] {"com\\.example\\.Exception\\$Inner"}); + config.setDiscardClasses(new Pattern[] {Pattern.compile("com\\.example\\.Exception\\$Inner")}); assertTrue(config.shouldIgnoreClass("com.example.Exception$Inner")); assertFalse(config.shouldIgnoreClass("com.example.ExceptionXInner")); diff --git a/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/IgnoredExceptionScenario.java b/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/IgnoredExceptionScenario.java index ef0dfc3d..27178676 100644 --- a/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/IgnoredExceptionScenario.java +++ b/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/IgnoredExceptionScenario.java @@ -2,6 +2,8 @@ import com.bugsnag.Bugsnag; +import java.util.regex.Pattern; + /** * Attempts to send an ignored handled exception to Bugsnag, which should not result * in any operation. @@ -15,7 +17,7 @@ public IgnoredExceptionScenario(Bugsnag bugsnag) { @Override public void run() { - bugsnag.setDiscardClasses("java.lang.RuntimeException"); + bugsnag.setDiscardClasses(Pattern.compile("java.lang.RuntimeException")); bugsnag.notify(new RuntimeException("Should never appear")); } diff --git a/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/IgnoredExceptionWildcardScenario.java b/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/IgnoredExceptionWildcardScenario.java index b4a10935..810f3131 100644 --- a/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/IgnoredExceptionWildcardScenario.java +++ b/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/IgnoredExceptionWildcardScenario.java @@ -2,6 +2,8 @@ import com.bugsnag.Bugsnag; +import java.util.regex.Pattern; + /** * Attempts to send ignored handled exceptions using regex patterns to Bugsnag, * which should not result in any operation. @@ -15,7 +17,7 @@ public IgnoredExceptionWildcardScenario(Bugsnag bugsnag) { @Override public void run() { // Use regex pattern to ignore all java.lang exceptions - bugsnag.setDiscardClasses("java\\.lang\\..*"); + bugsnag.setDiscardClasses(Pattern.compile("java\\.lang\\..*")); // These should all be ignored due to the regex pattern bugsnag.notify(new RuntimeException("Should never appear")); diff --git a/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/MultipleWildcardPatternsScenario.java b/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/MultipleWildcardPatternsScenario.java index 4a98701c..aa4d15e3 100644 --- a/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/MultipleWildcardPatternsScenario.java +++ b/features/fixtures/scenarios/src/main/java/com/bugsnag/mazerunner/scenarios/MultipleWildcardPatternsScenario.java @@ -2,6 +2,8 @@ import com.bugsnag.Bugsnag; +import java.util.regex.Pattern; + /** * Tests multiple regex patterns working together. */ @@ -15,9 +17,9 @@ public MultipleWildcardPatternsScenario(Bugsnag bugsnag) { public void run() { // Set multiple regex patterns: matching specific packages and classes bugsnag.setDiscardClasses( - "java\\.io\\..*", // All java.io exceptions - "java\\.lang\\.IllegalStateException", // Exact match - "java\\.lang\\.Illegal.*" // All IllegalXException classes + Pattern.compile("java\\.io\\..*"), // All java.io exceptions + Pattern.compile("java\\.lang\\.IllegalStateException"), // Exact match + Pattern.compile("java\\.lang\\.Illegal.*") // All IllegalXException classes ); // These should all be ignored From d40d29cbe7f48cbe87cb741ad3231692dfc0767d Mon Sep 17 00:00:00 2001 From: richard elms Date: Thu, 18 Dec 2025 14:57:17 +0100 Subject: [PATCH 9/9] refac --- .../java/com/bugsnag/BugsnagAppender.java | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/bugsnag/src/main/java/com/bugsnag/BugsnagAppender.java b/bugsnag/src/main/java/com/bugsnag/BugsnagAppender.java index 590b00b1..ad53ba40 100644 --- a/bugsnag/src/main/java/com/bugsnag/BugsnagAppender.java +++ b/bugsnag/src/main/java/com/bugsnag/BugsnagAppender.java @@ -19,6 +19,7 @@ import java.net.Proxy; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -262,12 +263,7 @@ private Bugsnag createBugsnag() { bugsnag.setRedactedKeys(redactedKeys.toArray(new String[0])); } - Pattern[] discardPatterns = new Pattern[discardClasses.size()]; - int idx = 0; - for (String pattern : discardClasses) { - discardPatterns[idx++] = Pattern.compile(pattern); - } - bugsnag.setDiscardClasses(discardPatterns); + bugsnag.setDiscardClasses(compileDiscardPatterns(discardClasses)); if (!enabledReleaseStages.isEmpty()) { bugsnag.setEnabledReleaseStages(enabledReleaseStages.toArray(new String[0])); @@ -298,6 +294,21 @@ public boolean onError(Report report) { return bugsnag; } + /** + * Compiles a collection of pattern strings into an array of Pattern objects. + * + * @param patternStrings the collection of pattern strings to compile + * @return an array of compiled Pattern objects + */ + private Pattern[] compileDiscardPatterns(Collection patternStrings) { + Pattern[] patterns = new Pattern[patternStrings.size()]; + int idx = 0; + for (String pattern : patternStrings) { + patterns[idx++] = Pattern.compile(pattern); + } + return patterns; + } + /** * Add a callback to execute code before/after every notification to Bugsnag. * @@ -417,12 +428,7 @@ public void setDiscardClass(String discardClass) { this.discardClasses.add(discardClass); if (bugsnag != null) { - Pattern[] discardPatterns = new Pattern[this.discardClasses.size()]; - int idx = 0; - for (String pattern : this.discardClasses) { - discardPatterns[idx++] = Pattern.compile(pattern); - } - bugsnag.setDiscardClasses(discardPatterns); + bugsnag.setDiscardClasses(compileDiscardPatterns(this.discardClasses)); } } @@ -433,12 +439,7 @@ public void setDiscardClasses(String discardClasses) { this.discardClasses.addAll(split(discardClasses)); if (bugsnag != null) { - Pattern[] discardPatterns = new Pattern[this.discardClasses.size()]; - int idx = 0; - for (String pattern : this.discardClasses) { - discardPatterns[idx++] = Pattern.compile(pattern); - } - bugsnag.setDiscardClasses(discardPatterns); + bugsnag.setDiscardClasses(compileDiscardPatterns(this.discardClasses)); } }