Skip to content

Commit 761d3b1

Browse files
fix: ignoreErrors now works on Native (#4948)
* add android (tested) and ios (untested) * do not use integration/ refactor to also accept strings and regex * simplify logic * use JS pattern for strings on native layers (Android OK/ iOS WIP). Split regex from string matches * apply changes to iOS * lint fix * fix kotlin format * update changelog * fix android tests * update changelog * add space * add reference to regex * fix kotlin tests / add js tests * fix ios tests and fix some logic issues * manual lint: * nit * more manual nits * more nits * lint * final nit? * Update CHANGELOG.md * Apply suggestions from code review Co-authored-by: Antonis Lilis <[email protected]> * will it fix lint? * rollback delted code * lint fix? --------- Co-authored-by: Antonis Lilis <[email protected]>
1 parent 72b0956 commit 761d3b1

File tree

8 files changed

+387
-4
lines changed

8 files changed

+387
-4
lines changed

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,25 @@
66
> make sure you follow our [migration guide](https://docs.sentry.io/platforms/react-native/migration/) first.
77
<!-- prettier-ignore-end -->
88
9+
## Unreleased
10+
11+
### Fixes
12+
13+
- ignoreError now filters Native errors ([#4948](https://github.com/getsentry/sentry-react-native/pull/4948))
14+
15+
You can use strings to filter errors or RegEx for filtering with a pattern.
16+
17+
example:
18+
19+
```typescript
20+
ignoreErrors: [
21+
'1234', // Will filter any error message that contains 1234.
22+
'.*1234', // Will not filter as regex, instead will filter messages that contains '.*1234"
23+
/.*1234/, // Regex will filter any error message that ends with 1234
24+
/.*1234.*/ // Regex will filter any error message that contains 1234.
25+
]
26+
```
27+
928
## 7.0.0-beta.1
1029

1130
### Upgrading from 6.x to 7.0

packages/core/RNSentryAndroidTester/app/src/test/java/io/sentry/react/RNSentryModuleImplTest.kt

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,4 +185,91 @@ class RNSentryModuleImplTest {
185185

186186
assertEquals(breadcrumb, result)
187187
}
188+
189+
@Test
190+
fun `trySetIgnoreErrors sets only regex patterns`() {
191+
val options = SentryAndroidOptions()
192+
val rnOptions =
193+
JavaOnlyMap.of(
194+
"ignoreErrorsRegex",
195+
com.facebook.react.bridge.JavaOnlyArray
196+
.of("^Foo.*", "Bar$"),
197+
)
198+
module.trySetIgnoreErrors(options, rnOptions)
199+
assertEquals(listOf("^Foo.*", "Bar$"), options.ignoredErrors!!.map { it.filterString })
200+
}
201+
202+
@Test
203+
fun `trySetIgnoreErrors sets only string patterns`() {
204+
val options = SentryAndroidOptions()
205+
val rnOptions =
206+
JavaOnlyMap.of(
207+
"ignoreErrorsStr",
208+
com.facebook.react.bridge.JavaOnlyArray
209+
.of("ExactError", "AnotherError"),
210+
)
211+
module.trySetIgnoreErrors(options, rnOptions)
212+
assertEquals(listOf(".*\\QExactError\\E.*", ".*\\QAnotherError\\E.*"), options.ignoredErrors!!.map { it.filterString })
213+
}
214+
215+
@Test
216+
fun `trySetIgnoreErrors sets both regex and string patterns`() {
217+
val options = SentryAndroidOptions()
218+
val rnOptions =
219+
JavaOnlyMap.of(
220+
"ignoreErrorsRegex",
221+
com.facebook.react.bridge.JavaOnlyArray
222+
.of("^Foo.*"),
223+
"ignoreErrorsStr",
224+
com.facebook.react.bridge.JavaOnlyArray
225+
.of("ExactError"),
226+
)
227+
module.trySetIgnoreErrors(options, rnOptions)
228+
assertEquals(listOf("^Foo.*", ".*\\QExactError\\E.*"), options.ignoredErrors!!.map { it.filterString })
229+
}
230+
231+
@Test
232+
fun `trySetIgnoreErrors sets nothing if neither is present`() {
233+
val options = SentryAndroidOptions()
234+
val rnOptions = JavaOnlyMap.of()
235+
module.trySetIgnoreErrors(options, rnOptions)
236+
assertNull(options.ignoredErrors)
237+
}
238+
239+
@Test
240+
fun `trySetIgnoreErrors with string containing regex special characters should match literally if Pattern_quote is used`() {
241+
val options = SentryAndroidOptions()
242+
val special = "I like chocolate (and tomato)."
243+
val rnOptions =
244+
JavaOnlyMap.of(
245+
"ignoreErrorsStr",
246+
com.facebook.react.bridge.JavaOnlyArray
247+
.of(special),
248+
)
249+
module.trySetIgnoreErrors(options, rnOptions)
250+
251+
assertEquals(listOf(".*\\QI like chocolate (and tomato).\\E.*"), options.ignoredErrors!!.map { it.filterString })
252+
253+
val regex = Regex(options.ignoredErrors!![0].filterString)
254+
assertTrue(regex.matches("I like chocolate (and tomato)."))
255+
assertTrue(regex.matches(" I like chocolate (and tomato). "))
256+
assertTrue(regex.matches("I like chocolate (and tomato). And vanilla."))
257+
}
258+
259+
@Test
260+
fun `trySetIgnoreErrors with string containing star should not match everything if Pattern_quote is used`() {
261+
val options = SentryAndroidOptions()
262+
val special = "Error*WithStar"
263+
val rnOptions =
264+
JavaOnlyMap.of(
265+
"ignoreErrorsStr",
266+
com.facebook.react.bridge.JavaOnlyArray
267+
.of(special),
268+
)
269+
module.trySetIgnoreErrors(options, rnOptions)
270+
assertEquals(listOf(".*\\QError*WithStar\\E.*"), options.ignoredErrors!!.map { it.filterString })
271+
272+
val regex = Regex(options.ignoredErrors!![0].filterString)
273+
assertTrue(regex.matches("Error*WithStar"))
274+
}
188275
}

packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.mm

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,4 +500,119 @@ - (void)testFetchNativeStackFramesByInstructionsOnDeviceSymbolication
500500
XCTAssertTrue([actual isEqualToDictionary:expected]);
501501
}
502502

503+
- (void)testIgnoreErrorsDropsMatchingExceptionValue
504+
{
505+
RNSentry *rnSentry = [[RNSentry alloc] init];
506+
NSError *error = nil;
507+
NSDictionary *mockedOptions = @{
508+
@"dsn" : @"https://[email protected]/1234567",
509+
@"ignoreErrorsRegex" : @[ @"IgnoreMe.*" ]
510+
};
511+
SentryOptions *options = [rnSentry createOptionsWithDictionary:mockedOptions error:&error];
512+
XCTAssertNotNil(options);
513+
XCTAssertNil(error);
514+
SentryEvent *event = [[SentryEvent alloc] init];
515+
SentryException *exception = [SentryException alloc];
516+
exception.value = @"IgnoreMe: This should be ignored";
517+
event.exceptions = @[ exception ];
518+
SentryEvent *result = options.beforeSend(event);
519+
XCTAssertNil(result, @"Event with matching exception.value should be dropped");
520+
}
521+
522+
- (void)testIgnoreErrorsDropsMatchingEventMessage
523+
{
524+
RNSentry *rnSentry = [[RNSentry alloc] init];
525+
NSError *error = nil;
526+
NSDictionary *mockedOptions = @{
527+
@"dsn" : @"https://[email protected]/1234567",
528+
@"ignoreErrorsStr" : @[ @"DropThisError" ]
529+
};
530+
SentryOptions *options = [rnSentry createOptionsWithDictionary:mockedOptions error:&error];
531+
XCTAssertNotNil(options);
532+
XCTAssertNil(error);
533+
SentryEvent *event = [[SentryEvent alloc] init];
534+
SentryMessage *msg = [SentryMessage alloc];
535+
msg.message = @"DropThisError: should be dropped";
536+
event.message = msg;
537+
SentryEvent *result = options.beforeSend(event);
538+
XCTAssertNil(result, @"Event with matching event.message.formatted should be dropped");
539+
}
540+
541+
- (void)testIgnoreErrorsDoesNotDropNonMatchingEvent
542+
{
543+
RNSentry *rnSentry = [[RNSentry alloc] init];
544+
NSError *error = nil;
545+
NSDictionary *mockedOptions = @{
546+
@"dsn" : @"https://[email protected]/1234567",
547+
@"ignoreErrorsRegex" : @[ @"IgnoreMe.*" ]
548+
};
549+
SentryOptions *options = [rnSentry createOptionsWithDictionary:mockedOptions error:&error];
550+
XCTAssertNotNil(options);
551+
XCTAssertNil(error);
552+
SentryEvent *event = [[SentryEvent alloc] init];
553+
SentryException *exception = [SentryException alloc];
554+
exception.value = @"SomeOtherError: should not be ignored";
555+
event.exceptions = @[ exception ];
556+
SentryMessage *msg = [SentryMessage alloc];
557+
msg.message = @"SomeOtherMessage";
558+
event.message = msg;
559+
SentryEvent *result = options.beforeSend(event);
560+
XCTAssertNotNil(result, @"Event with non-matching error should not be dropped");
561+
}
562+
563+
- (void)testIgnoreErrorsDropsMatchingExactString
564+
{
565+
RNSentry *rnSentry = [[RNSentry alloc] init];
566+
NSError *error = nil;
567+
NSDictionary *mockedOptions = @{
568+
@"dsn" : @"https://[email protected]/1234567",
569+
@"ignoreErrorsStr" : @[ @"ExactError" ]
570+
};
571+
SentryOptions *options = [rnSentry createOptionsWithDictionary:mockedOptions error:&error];
572+
XCTAssertNotNil(options);
573+
XCTAssertNil(error);
574+
SentryEvent *event = [[SentryEvent alloc] init];
575+
SentryMessage *msg = [SentryMessage alloc];
576+
msg.message = @"ExactError";
577+
event.message = msg;
578+
SentryEvent *result = options.beforeSend(event);
579+
XCTAssertNil(result, @"Event with exactly matching string should be dropped");
580+
}
581+
582+
- (void)testIgnoreErrorsRegexAndStringBothWork
583+
{
584+
RNSentry *rnSentry = [[RNSentry alloc] init];
585+
NSError *error = nil;
586+
NSDictionary *mockedOptions = @{
587+
@"dsn" : @"https://[email protected]/1234567",
588+
@"ignoreErrorsStr" : @[ @"ExactError" ],
589+
@"ignoreErrorsRegex" : @[ @"IgnoreMe.*" ],
590+
591+
};
592+
SentryOptions *options = [rnSentry createOptionsWithDictionary:mockedOptions error:&error];
593+
XCTAssertNotNil(options);
594+
XCTAssertNil(error);
595+
// Test regex match
596+
SentryEvent *event1 = [[SentryEvent alloc] init];
597+
SentryException *exception = [SentryException alloc];
598+
exception.value = @"IgnoreMe: This should be ignored";
599+
event1.exceptions = @[ exception ];
600+
SentryEvent *result1 = options.beforeSend(event1);
601+
XCTAssertNil(result1, @"Event with matching regex should be dropped");
602+
// Test exact string match
603+
SentryEvent *event2 = [[SentryEvent alloc] init];
604+
SentryMessage *msg = [SentryMessage alloc];
605+
msg.message = @"ExactError";
606+
event2.message = msg;
607+
SentryEvent *result2 = options.beforeSend(event2);
608+
XCTAssertNil(result2, @"Event with exactly matching string should be dropped");
609+
// Test non-matching
610+
SentryEvent *event3 = [[SentryEvent alloc] init];
611+
SentryMessage *msg3 = [SentryMessage alloc];
612+
msg3.message = @"OtherError";
613+
event3.message = msg3;
614+
SentryEvent *result3 = options.beforeSend(event3);
615+
XCTAssertNotNil(result3, @"Event with non-matching error should not be dropped");
616+
}
617+
503618
@end

packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,15 @@
8383
import java.net.URI;
8484
import java.net.URISyntaxException;
8585
import java.nio.charset.Charset;
86+
import java.util.ArrayList;
8687
import java.util.HashMap;
8788
import java.util.Iterator;
8889
import java.util.List;
8990
import java.util.Map;
9091
import java.util.Properties;
9192
import java.util.Set;
9293
import java.util.concurrent.CountDownLatch;
94+
import java.util.regex.Pattern;
9395
import org.jetbrains.annotations.NotNull;
9496
import org.jetbrains.annotations.Nullable;
9597
import org.jetbrains.annotations.TestOnly;
@@ -320,6 +322,8 @@ protected void getSentryAndroidOptions(
320322
// we want to ignore it on the native side to avoid sending it twice.
321323
options.addIgnoredExceptionForType(JavascriptException.class);
322324

325+
trySetIgnoreErrors(options, rnOptions);
326+
323327
options.setBeforeSend(
324328
(event, hint) -> {
325329
setEventOriginTag(event);
@@ -1126,4 +1130,36 @@ private boolean isFrameMetricsAggregatorAvailable() {
11261130
}
11271131
return uri.getScheme() + "://" + uri.getHost();
11281132
}
1133+
1134+
@TestOnly
1135+
protected void trySetIgnoreErrors(SentryAndroidOptions options, ReadableMap rnOptions) {
1136+
ReadableArray regErrors = null;
1137+
ReadableArray strErrors = null;
1138+
if (rnOptions.hasKey("ignoreErrorsRegex")) {
1139+
regErrors = rnOptions.getArray("ignoreErrorsRegex");
1140+
}
1141+
if (rnOptions.hasKey("ignoreErrorsStr")) {
1142+
strErrors = rnOptions.getArray("ignoreErrorsStr");
1143+
}
1144+
if (regErrors == null && strErrors == null) {
1145+
return;
1146+
}
1147+
1148+
int regSize = regErrors != null ? regErrors.size() : 0;
1149+
int strSize = strErrors != null ? strErrors.size() : 0;
1150+
List<String> list = new ArrayList<>(regSize + strSize);
1151+
if (regErrors != null) {
1152+
for (int i = 0; i < regErrors.size(); i++) {
1153+
list.add(regErrors.getString(i));
1154+
}
1155+
}
1156+
if (strErrors != null) {
1157+
// Use the same behaviour of JavaScript instead of Android when dealing with strings.
1158+
for (int i = 0; i < strErrors.size(); i++) {
1159+
String pattern = ".*" + Pattern.quote(strErrors.getString(i)) + ".*";
1160+
list.add(pattern);
1161+
}
1162+
}
1163+
options.setIgnoredErrors(list);
1164+
}
11291165
}

0 commit comments

Comments
 (0)