Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/design/plans/2026-09-16-implementation-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -4037,7 +4037,7 @@ The phase with the most native↔JS round-tripping, hence the most device testin
- [x] **9.5** `addBreadcrumb(crumb)` and `setBreadcrumbFilter(cb)`. Breadcrumbs are built through the SDK's exchange factory on both platforms, so the bridge constructs rather than forwards. Device: WOD_LX1 `AMRJCP4718402860` retained `breadcrumb-filter immediate 30dccd7246d8 REDACTED` and `breadcrumb-filter rewrite 30dccd7246d8 REDACTED` at level `info`, and the unsettled crumb was absent; simulator `6FA9B3E8-26C7-4232-AA2C-537D9DF32957` retained `breadcrumb-filter immediate e62610c1b82b REDACTED` and `breadcrumb-filter rewrite e62610c1b82b REDACTED` at level `info`, and the unsettled crumb was absent. The iPhone XS (KRSFT) was already running BareExample (pid 43281), so that run was not taken.
- [x] **9.6** `addNetworkEvent` for stacks the SDK does not auto-instrument. WOD_LX1 `AMRJCP4718402860` retained `https://bugsee-e2e.invalid/add/89dc3cdd4491` (`mechanism` `react-native`, `type` `complete`) and omitted the hung url (the filter logged `hung nonce=89dc3cdd4491`). iOS builds a `BugseeNetworkEvent` (`eventWithID:`) because beta3's factory `createNetworkEvent` returns nil, then `addNetworkEvent:requiresFiltering:YES`. Simulator `6FA9B3E8-26C7-4232-AA2C-537D9DF32957` retained `https://bugsee-e2e.invalid/add/0b8a3f41b546` and omitted the hung url (filter logged `hung nonce=0b8a3f41b546`). iPhone XS KRSFT `345BA7FE-2C29-5722-892A-BFCB1FD34D0C` retained `https://bugsee-e2e.invalid/add/5013161d5890` and omitted the hung url (`hung nonce=5013161d5890`). Both events are `mechanism` `react-native`, `type` `complete`. `duration` stayed 90.
- [x] **9.7** Dedup across the two console streams, *before* either filter runs — otherwise the user's callback runs twice on one line by two routes. Device test in **both** Debug and Release; RN only routes `console.*` through `RCTLog` under `__DEV__`, and Hermes release builds commonly strip console calls. After the one-shot stdout/stderr echo drop was restored, WOD_LX1 `AMRJCP4718402860` Debug retained one `BUGSEE_E2E dedup-line 9370533d0f4d #1` (source 98, level 3, `calls=1`, `dev=true`) and Release retained one `BUGSEE_E2E dedup-line 5269f19051b7 #1` (`dev=false`, `calls=1`). Simulator `6FA9B3E8-26C7-4232-AA2C-537D9DF32957` Debug retained one `BUGSEE_E2E dedup-line 5d34148a4685 #1` (source 98, level 3, `calls=1`, `dev=true`) and Release retained one `BUGSEE_E2E dedup-line dd8a7164a049 #1` (`dev=false`, `calls=1`). iPhone XS KRSFT was running BareExample, so that run was not taken.
- [ ] Review gate.
- [x] Review gate. Passed at `2b56960`: an unfinished log, network, or breadcrumb filter drops the line, the Android borrow stays 9s, the iOS data-request deadline stays 450ms, `patchXhr` and a JS `fetch` patch stay off, and console dedup still drops the native echo before the user callback. One minor stays out: one Android message can accumulate more than 32 echo credits inside a single append. `setLogFilter` now subscribes before it enables the native filter.

---

Expand Down
14 changes: 14 additions & 0 deletions packages/react-native/src/logs/__tests__/filter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,20 @@ describe('setLogFilter', () => {
expect(native.logFilterRequestSubscribeCallCount()).toBe(1);
});

it('subscribes once, and before the native filter is installed', () => {
const original = native.onLogFilterRequest.bind(native);
native.onLogFilterRequest = (listener) => {
expect(native.setLogFilterEnabled).not.toHaveBeenCalled();
return original(listener);
};
Bugsee.setLogFilter((line) => line);
expect(native.setLogFilterEnabled).toHaveBeenCalledWith(true);
Bugsee.setLogFilter((line) => line);
Bugsee.setLogFilter();
Bugsee.setLogFilter((line) => line);
expect(native.logFilterRequestSubscribeCallCount()).toBe(1);
});

it('rejects a non-function and keeps the filter already installed', async () => {
Bugsee.setLogFilter((line) => `kept:${line}`);
const set = Bugsee.setLogFilter as (callback?: unknown) => void;
Expand Down
16 changes: 9 additions & 7 deletions packages/react-native/src/logs/filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,24 @@ let subscribed = false;
* `undefined` or `null` clears it, which tells native to uninstall the filter
* so lines are recorded without one.
*
* The native request is subscribed to once and stays subscribed: clearing the
* callback stops native from asking, and a later callback must still be able
* to answer.
* The JS listener is subscribed before the native filter is installed, so a
* line filtered on the installing turn still has a listener. On iOS a request
* emitted in that gap stays in the pending table until process death. The
* subscription stays: clearing the callback stops native from asking, and a
* later callback must still be able to answer.
*/
export function setLogFilter(callback?: LogFilter | null): void {
if (callback != null && typeof callback !== 'function') {
throw new TypeError(
`Bugsee.setLogFilter requires a function, got ${typeof callback}`,
);
}
current = callback ?? undefined;
NativeBugsee.setLogFilterEnabled(current !== undefined);
if (current !== undefined && !subscribed) {
subscribed = true;
if (callback != null && !subscribed) {
NativeBugsee.onLogFilterRequest(onLogFilterRequest);
subscribed = true;
}
current = callback ?? undefined;
NativeBugsee.setLogFilterEnabled(current !== undefined);
}

/**
Expand Down
Loading