@goliapkg/sentori-react-native 0.5.2 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/handlers/network.js +3 -3
- package/lib/handlers/network.js.map +1 -1
- package/lib/navigation.d.ts +4 -5
- package/lib/navigation.d.ts.map +1 -1
- package/lib/navigation.js +47 -30
- package/lib/navigation.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/navigation.test.ts +90 -63
- package/src/__tests__/tracing.test.ts +9 -0
- package/src/handlers/network.ts +3 -3
- package/src/navigation.ts +46 -32
package/lib/handlers/network.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { startSpan } from '@goliapkg/sentori-core';
|
|
1
|
+
import { normalizeUrl, startSpan } from '@goliapkg/sentori-core';
|
|
2
2
|
import { addBreadcrumb } from '../breadcrumbs';
|
|
3
3
|
import { getConfig } from '../config';
|
|
4
4
|
let _installed = false;
|
|
@@ -36,7 +36,7 @@ function patchFetch() {
|
|
|
36
36
|
// attached to error events at capture time and serve a different
|
|
37
37
|
// surface (the "last 100 things" timeline on the issue page).
|
|
38
38
|
const span = startSpan('http.client', {
|
|
39
|
-
name: `${method.toUpperCase()} ${scrubbed}`,
|
|
39
|
+
name: `${method.toUpperCase()} ${normalizeUrl(scrubbed)}`,
|
|
40
40
|
tags: { 'http.method': method.toUpperCase(), 'http.url': scrubbed },
|
|
41
41
|
});
|
|
42
42
|
// Inject traceparent header on outbound requests.
|
|
@@ -101,7 +101,7 @@ function patchXhr() {
|
|
|
101
101
|
const method = this.__sentoriMethod ?? 'GET';
|
|
102
102
|
const url = scrubUrl(this.__sentoriUrl ?? '');
|
|
103
103
|
const span = startSpan('http.client', {
|
|
104
|
-
name: `${method} ${url}`,
|
|
104
|
+
name: `${method} ${normalizeUrl(url)}`,
|
|
105
105
|
tags: { 'http.method': method, 'http.url': url },
|
|
106
106
|
});
|
|
107
107
|
this.__sentoriSpan = span;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"network.js","sourceRoot":"","sources":["../../src/handlers/network.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"network.js","sourceRoot":"","sources":["../../src/handlers/network.ts"],"names":[],"mappings":"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"}
|
package/lib/navigation.d.ts
CHANGED
|
@@ -9,9 +9,9 @@ export type NavigationRefLike = {
|
|
|
9
9
|
};
|
|
10
10
|
/**
|
|
11
11
|
* Subscribe to react-navigation state changes and emit a
|
|
12
|
-
* `react.navigation` span per
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* `react.navigation` span per screen (including the initial one),
|
|
13
|
+
* each a fresh trace root. The span is kept active while the screen
|
|
14
|
+
* is current; child spans created in that window attribute up to it.
|
|
15
15
|
*
|
|
16
16
|
* import { NavigationContainer, useNavigationContainerRef } from '@react-navigation/native'
|
|
17
17
|
* import { useTraceNavigation } from '@goliapkg/sentori-react-native'
|
|
@@ -22,8 +22,7 @@ export type NavigationRefLike = {
|
|
|
22
22
|
* return <NavigationContainer ref={navigationRef}>{...}</NavigationContainer>
|
|
23
23
|
* }
|
|
24
24
|
*
|
|
25
|
-
* Each span carries `{ from, to }`
|
|
26
|
-
* route name as the span name.
|
|
25
|
+
* Each span carries `{ nav.from, nav.to }` tags.
|
|
27
26
|
*/
|
|
28
27
|
export declare function useTraceNavigation(navigationRef: NavigationRefLike): void;
|
|
29
28
|
//# sourceMappingURL=navigation.d.ts.map
|
package/lib/navigation.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"AA0BA;;kDAEkD;AAClD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,WAAW,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;IAClE,eAAe,EAAE,MAAM;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;CACrD,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,iBAAiB,GAAG,IAAI,CA+CzE"}
|
package/lib/navigation.js
CHANGED
|
@@ -1,22 +1,31 @@
|
|
|
1
1
|
// Phase 35 sub-C: react-navigation auto-instrumentation.
|
|
2
|
+
// Phase 39 sub-B: the open `react.navigation` span is also made the
|
|
3
|
+
// active span for that screen's lifetime, so the screen's
|
|
4
|
+
// `http.client` spans (and any other startSpan() calls) attach to it
|
|
5
|
+
// as children — one trace per screen instead of one per request.
|
|
2
6
|
//
|
|
3
7
|
// Mount `useTraceNavigation(navigationRef)` next to your
|
|
4
|
-
// `<NavigationContainer ref={navigationRef}
|
|
5
|
-
//
|
|
6
|
-
//
|
|
8
|
+
// `<NavigationContainer ref={navigationRef}>`. Span names are
|
|
9
|
+
// `<from> → <to>` (or just the route name for the first screen) so
|
|
10
|
+
// the trace list reads as a navigation log.
|
|
7
11
|
//
|
|
8
|
-
// react-navigation is an OPTIONAL peer dependency — apps that
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
12
|
+
// react-navigation is an OPTIONAL peer dependency — apps that don't
|
|
13
|
+
// use it never have to install it. The hook doesn't import from
|
|
14
|
+
// @react-navigation/native; consumers pass in the ref they already
|
|
15
|
+
// have, and we read its state via the public `getCurrentRoute()`.
|
|
16
|
+
//
|
|
17
|
+
// Caveat (active-span on RN is a module variable): requests fired
|
|
18
|
+
// from a `setTimeout` / background poll / detached promise after the
|
|
19
|
+
// screen settled may not see the nav span as active. If you want such
|
|
20
|
+
// a request parented to the current screen, pass it explicitly:
|
|
21
|
+
// `startSpan(op, { parent: activeSpan() })`.
|
|
13
22
|
import { useEffect, useRef } from 'react';
|
|
14
|
-
import { startSpan } from '@goliapkg/sentori-core';
|
|
23
|
+
import { setActiveSpan, startSpan } from '@goliapkg/sentori-core';
|
|
15
24
|
/**
|
|
16
25
|
* Subscribe to react-navigation state changes and emit a
|
|
17
|
-
* `react.navigation` span per
|
|
18
|
-
*
|
|
19
|
-
*
|
|
26
|
+
* `react.navigation` span per screen (including the initial one),
|
|
27
|
+
* each a fresh trace root. The span is kept active while the screen
|
|
28
|
+
* is current; child spans created in that window attribute up to it.
|
|
20
29
|
*
|
|
21
30
|
* import { NavigationContainer, useNavigationContainerRef } from '@react-navigation/native'
|
|
22
31
|
* import { useTraceNavigation } from '@goliapkg/sentori-react-native'
|
|
@@ -27,45 +36,53 @@ import { startSpan } from '@goliapkg/sentori-core';
|
|
|
27
36
|
* return <NavigationContainer ref={navigationRef}>{...}</NavigationContainer>
|
|
28
37
|
* }
|
|
29
38
|
*
|
|
30
|
-
* Each span carries `{ from, to }`
|
|
31
|
-
* route name as the span name.
|
|
39
|
+
* Each span carries `{ nav.from, nav.to }` tags.
|
|
32
40
|
*/
|
|
33
41
|
export function useTraceNavigation(navigationRef) {
|
|
34
|
-
// Latest route name we've observed.
|
|
35
|
-
// recorded yet" (initial mount).
|
|
42
|
+
// Latest route name we've observed.
|
|
36
43
|
const lastRouteRef = useRef(null);
|
|
37
|
-
// Span
|
|
38
|
-
//
|
|
44
|
+
// Span for the screen the user is currently on. Finished when the
|
|
45
|
+
// next screen is entered (or on unmount).
|
|
39
46
|
const openSpanRef = useRef(null);
|
|
40
47
|
useEffect(() => {
|
|
41
48
|
if (typeof navigationRef.addListener !== 'function')
|
|
42
49
|
return;
|
|
43
50
|
if (typeof navigationRef.getCurrentRoute !== 'function')
|
|
44
51
|
return;
|
|
45
|
-
//
|
|
46
|
-
//
|
|
52
|
+
// Each screen gets its own trace root — detach from whatever the
|
|
53
|
+
// previous screen's span was (we keep it active, so without
|
|
54
|
+
// `parent: null` the new one would nest under it).
|
|
55
|
+
const openScreenSpan = (from, to) => {
|
|
56
|
+
const span = startSpan('react.navigation', {
|
|
57
|
+
name: from ? `${from} → ${to}` : to,
|
|
58
|
+
parent: null,
|
|
59
|
+
tags: { 'nav.from': from ?? '', 'nav.to': to },
|
|
60
|
+
});
|
|
61
|
+
openSpanRef.current = span;
|
|
62
|
+
setActiveSpan(span);
|
|
63
|
+
lastRouteRef.current = to;
|
|
64
|
+
};
|
|
65
|
+
// Open a span for the initial screen so its requests are grouped
|
|
66
|
+
// too (auth / config / first data load are usually the busiest
|
|
67
|
+
// screen of a session).
|
|
47
68
|
const initial = navigationRef.getCurrentRoute()?.name ?? null;
|
|
48
|
-
|
|
69
|
+
if (initial !== null)
|
|
70
|
+
openScreenSpan(null, initial);
|
|
71
|
+
else
|
|
72
|
+
lastRouteRef.current = null;
|
|
49
73
|
const unsubscribe = navigationRef.addListener('state', () => {
|
|
50
74
|
const next = navigationRef.getCurrentRoute()?.name ?? null;
|
|
51
75
|
const prev = lastRouteRef.current;
|
|
52
76
|
if (next === null || next === prev)
|
|
53
77
|
return;
|
|
54
|
-
// Close the prior span (if any) before opening the new one so
|
|
55
|
-
// the trace looks like a sequence, not nested.
|
|
56
78
|
openSpanRef.current?.finish({ status: 'ok' });
|
|
57
|
-
|
|
58
|
-
name: prev ? `${prev} → ${next}` : next,
|
|
59
|
-
tags: { 'nav.from': prev ?? '', 'nav.to': next },
|
|
60
|
-
});
|
|
61
|
-
openSpanRef.current = span;
|
|
62
|
-
lastRouteRef.current = next;
|
|
79
|
+
openScreenSpan(prev, next);
|
|
63
80
|
});
|
|
64
81
|
return () => {
|
|
65
82
|
unsubscribe();
|
|
66
|
-
// Close any still-open span on unmount so we don't leak it.
|
|
67
83
|
openSpanRef.current?.finish({ status: 'ok' });
|
|
68
84
|
openSpanRef.current = null;
|
|
85
|
+
setActiveSpan(null);
|
|
69
86
|
};
|
|
70
87
|
}, [navigationRef]);
|
|
71
88
|
}
|
package/lib/navigation.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigation.js","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,EAAE;AACF,yDAAyD;AACzD,8DAA8D;AAC9D
|
|
1
|
+
{"version":3,"file":"navigation.js","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,oEAAoE;AACpE,0DAA0D;AAC1D,qEAAqE;AACrE,iEAAiE;AACjE,EAAE;AACF,yDAAyD;AACzD,8DAA8D;AAC9D,mEAAmE;AACnE,4CAA4C;AAC5C,EAAE;AACF,oEAAoE;AACpE,gEAAgE;AAChE,mEAAmE;AACnE,kEAAkE;AAClE,EAAE;AACF,kEAAkE;AAClE,qEAAqE;AACrE,sEAAsE;AACtE,gEAAgE;AAChE,6CAA6C;AAE7C,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAE1C,OAAO,EAAE,aAAa,EAAE,SAAS,EAAmB,MAAM,wBAAwB,CAAC;AAUnF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,kBAAkB,CAAC,aAAgC;IACjE,oCAAoC;IACpC,MAAM,YAAY,GAAG,MAAM,CAAgB,IAAI,CAAC,CAAC;IACjD,kEAAkE;IAClE,0CAA0C;IAC1C,MAAM,WAAW,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAC;IAEpD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,OAAO,aAAa,CAAC,WAAW,KAAK,UAAU;YAAE,OAAO;QAC5D,IAAI,OAAO,aAAa,CAAC,eAAe,KAAK,UAAU;YAAE,OAAO;QAEhE,iEAAiE;QACjE,4DAA4D;QAC5D,mDAAmD;QACnD,MAAM,cAAc,GAAG,CAAC,IAAmB,EAAE,EAAU,EAAE,EAAE;YACzD,MAAM,IAAI,GAAG,SAAS,CAAC,kBAAkB,EAAE;gBACzC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE;gBACnC,MAAM,EAAE,IAAI;gBACZ,IAAI,EAAE,EAAE,UAAU,EAAE,IAAI,IAAI,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE;aAC/C,CAAC,CAAC;YACH,WAAW,CAAC,OAAO,GAAG,IAAI,CAAC;YAC3B,aAAa,CAAC,IAAI,CAAC,CAAC;YACpB,YAAY,CAAC,OAAO,GAAG,EAAE,CAAC;QAC5B,CAAC,CAAC;QAEF,iEAAiE;QACjE,+DAA+D;QAC/D,wBAAwB;QACxB,MAAM,OAAO,GAAG,aAAa,CAAC,eAAe,EAAE,EAAE,IAAI,IAAI,IAAI,CAAC;QAC9D,IAAI,OAAO,KAAK,IAAI;YAAE,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;;YAC/C,YAAY,CAAC,OAAO,GAAG,IAAI,CAAC;QAEjC,MAAM,WAAW,GAAG,aAAa,CAAC,WAAW,CAAC,OAAO,EAAE,GAAG,EAAE;YAC1D,MAAM,IAAI,GAAG,aAAa,CAAC,eAAe,EAAE,EAAE,IAAI,IAAI,IAAI,CAAC;YAC3D,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC;YAClC,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI;gBAAE,OAAO;YAC3C,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YAC9C,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;QAEH,OAAO,GAAG,EAAE;YACV,WAAW,EAAE,CAAC;YACd,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YAC9C,WAAW,CAAC,OAAO,GAAG,IAAI,CAAC;YAC3B,aAAa,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC;AACtB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goliapkg/sentori-react-native",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "Sentori SDK for React Native \u2014 JS-layer error capture, native crash handlers (iOS / Android), batched transport, fetch + react-navigation tracing.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://sentori.golia.jp",
|
|
@@ -64,6 +64,6 @@
|
|
|
64
64
|
"access": "public"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@goliapkg/sentori-core": "0.
|
|
67
|
+
"@goliapkg/sentori-core": "0.4.0"
|
|
68
68
|
}
|
|
69
69
|
}
|
|
@@ -1,16 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
__resetTraceContextForTests,
|
|
3
|
+
__useFallbackTraceContextForTests,
|
|
4
|
+
activeSpan,
|
|
5
|
+
clearSpans,
|
|
6
|
+
drainSpans,
|
|
7
|
+
setActiveSpan,
|
|
8
|
+
startSpan,
|
|
9
|
+
} from '@goliapkg/sentori-core';
|
|
10
|
+
import { afterAll, afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
3
11
|
|
|
4
12
|
import { type NavigationRefLike, useTraceNavigation } from '../navigation';
|
|
5
13
|
|
|
6
14
|
// We test the hook without a React renderer (sdk/react-native has no
|
|
7
|
-
// react-test-renderer
|
|
8
|
-
// the
|
|
9
|
-
//
|
|
10
|
-
//
|
|
15
|
+
// react-test-renderer / @testing-library dev-dep). `harness` mirrors
|
|
16
|
+
// the hook's effect body 1:1 — when production changes, this changes
|
|
17
|
+
// too. The point is to verify the observable contract: spans pushed
|
|
18
|
+
// in the right order + the open screen span left active.
|
|
11
19
|
|
|
12
|
-
// FakeNav mirrors @react-navigation/native's NavigationContainerRef
|
|
13
|
-
// surface — `addListener('state', cb)` + `getCurrentRoute()`.
|
|
14
20
|
class FakeNav implements NavigationRefLike {
|
|
15
21
|
private listeners: Array<() => void> = [];
|
|
16
22
|
private route: { name: string } | undefined;
|
|
@@ -18,131 +24,152 @@ class FakeNav implements NavigationRefLike {
|
|
|
18
24
|
setInitialRoute(name: string): void {
|
|
19
25
|
this.route = { name };
|
|
20
26
|
}
|
|
21
|
-
|
|
22
27
|
addListener(_event: 'state', listener: () => void): () => void {
|
|
23
28
|
this.listeners.push(listener);
|
|
24
29
|
return () => {
|
|
25
30
|
this.listeners = this.listeners.filter((l) => l !== listener);
|
|
26
31
|
};
|
|
27
32
|
}
|
|
28
|
-
|
|
29
33
|
getCurrentRoute(): { name: string } | undefined {
|
|
30
34
|
return this.route;
|
|
31
35
|
}
|
|
32
|
-
|
|
33
36
|
go(name: string): void {
|
|
34
37
|
this.route = { name };
|
|
35
38
|
this.listeners.forEach((l) => l());
|
|
36
39
|
}
|
|
37
40
|
}
|
|
38
41
|
|
|
39
|
-
// Re-implements the hook's effect body for test purposes. Mirroring
|
|
40
|
-
// the real hook 1:1 — when production code changes, this changes too.
|
|
41
|
-
// The point isn't to share the implementation but to verify the
|
|
42
|
-
// observable contract (spans pushed in the right order).
|
|
43
42
|
function harness(navigationRef: NavigationRefLike): () => void {
|
|
44
|
-
let lastRoute: null | string =
|
|
45
|
-
|
|
46
|
-
let openSpan: ReturnType<typeof import('@goliapkg/sentori-core').startSpan> | null = null;
|
|
43
|
+
let lastRoute: null | string = null;
|
|
44
|
+
let openSpan: ReturnType<typeof startSpan> | null = null;
|
|
47
45
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
const openScreenSpan = (from: null | string, to: string) => {
|
|
47
|
+
const span = startSpan('react.navigation', {
|
|
48
|
+
name: from ? `${from} → ${to}` : to,
|
|
49
|
+
parent: null,
|
|
50
|
+
tags: { 'nav.from': from ?? '', 'nav.to': to },
|
|
51
|
+
});
|
|
52
|
+
openSpan = span;
|
|
53
|
+
setActiveSpan(span);
|
|
54
|
+
lastRoute = to;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const initial = navigationRef.getCurrentRoute()?.name ?? null;
|
|
58
|
+
if (initial !== null) openScreenSpan(null, initial);
|
|
59
|
+
else lastRoute = null;
|
|
51
60
|
|
|
52
61
|
const unsub = navigationRef.addListener('state', () => {
|
|
53
62
|
const next = navigationRef.getCurrentRoute()?.name ?? null;
|
|
54
63
|
if (next === null || next === lastRoute) return;
|
|
55
|
-
|
|
56
64
|
openSpan?.finish({ status: 'ok' });
|
|
57
|
-
|
|
58
|
-
name: lastRoute ? `${lastRoute} → ${next}` : next,
|
|
59
|
-
tags: { 'nav.from': lastRoute ?? '', 'nav.to': next },
|
|
60
|
-
});
|
|
61
|
-
openSpan = span;
|
|
62
|
-
lastRoute = next;
|
|
65
|
+
openScreenSpan(lastRoute, next);
|
|
63
66
|
});
|
|
64
67
|
|
|
65
68
|
return () => {
|
|
66
69
|
unsub();
|
|
67
70
|
openSpan?.finish({ status: 'ok' });
|
|
68
71
|
openSpan = null;
|
|
72
|
+
setActiveSpan(null);
|
|
69
73
|
};
|
|
70
74
|
}
|
|
71
75
|
|
|
72
|
-
beforeEach(() =>
|
|
73
|
-
|
|
76
|
+
beforeEach(() => {
|
|
77
|
+
// Navigation relies on setActiveSpan, which is a no-op on the
|
|
78
|
+
// ALS impl that bun (Node) picks. In production this runs on RN,
|
|
79
|
+
// where the fallback impl is in effect — exercise that one.
|
|
80
|
+
__useFallbackTraceContextForTests();
|
|
81
|
+
clearSpans();
|
|
82
|
+
});
|
|
83
|
+
afterEach(() => {
|
|
84
|
+
setActiveSpan(null);
|
|
85
|
+
clearSpans();
|
|
86
|
+
});
|
|
87
|
+
afterAll(() => {
|
|
88
|
+
__resetTraceContextForTests();
|
|
89
|
+
});
|
|
74
90
|
|
|
75
91
|
describe('useTraceNavigation', () => {
|
|
76
|
-
test('
|
|
77
|
-
// Sanity: the exported symbol exists with the right shape.
|
|
92
|
+
test('exports a hook', () => {
|
|
78
93
|
expect(typeof useTraceNavigation).toBe('function');
|
|
79
94
|
});
|
|
80
95
|
|
|
81
|
-
test('initial mount
|
|
96
|
+
test('initial mount opens a span for the first screen and leaves it active', () => {
|
|
82
97
|
const nav = new FakeNav();
|
|
83
98
|
nav.setInitialRoute('Home');
|
|
84
99
|
const cleanup = harness(nav);
|
|
100
|
+
|
|
101
|
+
// span not finished yet (still on Home) → buffer empty
|
|
85
102
|
expect(drainSpans()).toHaveLength(0);
|
|
103
|
+
expect(activeSpan()).not.toBeNull();
|
|
104
|
+
|
|
86
105
|
cleanup();
|
|
106
|
+
const spans = drainSpans();
|
|
107
|
+
expect(spans).toHaveLength(1);
|
|
108
|
+
expect(spans[0]?.op).toBe('react.navigation');
|
|
109
|
+
expect(spans[0]?.name).toBe('Home');
|
|
110
|
+
expect(spans[0]?.tags).toEqual({ 'nav.from': '', 'nav.to': 'Home' });
|
|
87
111
|
});
|
|
88
112
|
|
|
89
|
-
test('
|
|
90
|
-
const nav = new FakeNav();
|
|
91
|
-
nav.setInitialRoute('Home');
|
|
113
|
+
test('mount with no current route → no span, no active', () => {
|
|
114
|
+
const nav = new FakeNav(); // no initial route
|
|
92
115
|
const cleanup = harness(nav);
|
|
93
|
-
|
|
94
|
-
nav.go('Settings');
|
|
95
|
-
nav.go('Profile');
|
|
96
|
-
|
|
97
|
-
const spans = drainSpans();
|
|
98
|
-
expect(spans.length).toBeGreaterThanOrEqual(1);
|
|
99
|
-
const first = spans[0]!;
|
|
100
|
-
expect(first.op).toBe('react.navigation');
|
|
101
|
-
expect(first.name).toBe('Home → Settings');
|
|
102
|
-
expect(first.tags).toEqual({ 'nav.from': 'Home', 'nav.to': 'Settings' });
|
|
103
|
-
expect(first.status).toBe('ok');
|
|
104
|
-
|
|
116
|
+
expect(activeSpan()).toBeNull();
|
|
105
117
|
cleanup();
|
|
118
|
+
expect(drainSpans()).toHaveLength(0);
|
|
106
119
|
});
|
|
107
120
|
|
|
108
|
-
test('
|
|
121
|
+
test('http.client span during a screen becomes a child of the nav span', () => {
|
|
109
122
|
const nav = new FakeNav();
|
|
110
123
|
nav.setInitialRoute('Home');
|
|
111
124
|
const cleanup = harness(nav);
|
|
125
|
+
const navSpan = activeSpan()!;
|
|
112
126
|
|
|
113
|
-
|
|
114
|
-
|
|
127
|
+
// simulate the network handler opening a request span (no explicit
|
|
128
|
+
// parent → inherits the active nav span)
|
|
129
|
+
const req = startSpan('http.client', { name: 'GET /x' });
|
|
130
|
+
expect(req.parentSpanId).toBe(navSpan.spanId);
|
|
131
|
+
expect(req.traceId).toBe(navSpan.traceId);
|
|
132
|
+
req.finish({ status: 'ok' });
|
|
115
133
|
|
|
116
134
|
cleanup();
|
|
135
|
+
const spans = drainSpans();
|
|
136
|
+
const navTrace = spans.find((s) => s.op === 'react.navigation')!.traceId;
|
|
137
|
+
expect(spans.every((s) => s.traceId === navTrace)).toBe(true);
|
|
117
138
|
});
|
|
118
139
|
|
|
119
|
-
test('
|
|
140
|
+
test('each screen is its own trace root (transitions do not nest)', () => {
|
|
120
141
|
const nav = new FakeNav();
|
|
121
142
|
nav.setInitialRoute('A');
|
|
122
143
|
const cleanup = harness(nav);
|
|
123
|
-
|
|
124
144
|
nav.go('B');
|
|
125
145
|
nav.go('C');
|
|
126
|
-
|
|
146
|
+
cleanup();
|
|
127
147
|
|
|
128
|
-
const spans = drainSpans();
|
|
129
|
-
expect(spans
|
|
130
|
-
expect(spans
|
|
131
|
-
expect(spans
|
|
148
|
+
const spans = drainSpans().filter((s) => s.op === 'react.navigation');
|
|
149
|
+
expect(spans).toHaveLength(3);
|
|
150
|
+
expect(spans.map((s) => s.name)).toEqual(['A', 'A → B', 'B → C']);
|
|
151
|
+
expect(spans.every((s) => s.parentSpanId === null)).toBe(true);
|
|
152
|
+
expect(new Set(spans.map((s) => s.traceId)).size).toBe(3);
|
|
153
|
+
});
|
|
132
154
|
|
|
155
|
+
test('same-route state event does not open a new span', () => {
|
|
156
|
+
const nav = new FakeNav();
|
|
157
|
+
nav.setInitialRoute('Home');
|
|
158
|
+
const cleanup = harness(nav);
|
|
159
|
+
nav.go('Home');
|
|
133
160
|
cleanup();
|
|
161
|
+
expect(drainSpans().filter((s) => s.op === 'react.navigation')).toHaveLength(1);
|
|
134
162
|
});
|
|
135
163
|
|
|
136
|
-
test('cleanup
|
|
164
|
+
test('cleanup finishes the open span and clears the active span', () => {
|
|
137
165
|
const nav = new FakeNav();
|
|
138
166
|
nav.setInitialRoute('Home');
|
|
139
167
|
const cleanup = harness(nav);
|
|
140
|
-
|
|
141
168
|
nav.go('Settings');
|
|
142
169
|
cleanup();
|
|
143
170
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
expect(spans
|
|
171
|
+
expect(activeSpan()).toBeNull();
|
|
172
|
+
const spans = drainSpans().filter((s) => s.op === 'react.navigation');
|
|
173
|
+
expect(spans.map((s) => s.name)).toEqual(['Home', 'Home → Settings']);
|
|
147
174
|
});
|
|
148
175
|
});
|
|
@@ -105,6 +105,15 @@ describe('RN network handler tracing', () => {
|
|
|
105
105
|
expect(tp).toMatch(/^00-[0-9a-f]{32}-[0-9a-f]{16}-01$/);
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
+
test('span name normalizes id-like segments; tag keeps full scrubbed url', async () => {
|
|
109
|
+
await fetch('https://api.example.com/users/123/orders/456?token=abc');
|
|
110
|
+
const sp = drainSpans()[0]!;
|
|
111
|
+
expect(sp.name).toBe('GET https://api.example.com/users/{id}/orders/{id}');
|
|
112
|
+
// RN's scrubUrl redacts auth params in the tag, but keeps path + host.
|
|
113
|
+
expect(sp.tags['http.url']).toContain('https://api.example.com/users/123/orders/456');
|
|
114
|
+
expect(sp.tags['http.url']).not.toContain('token=abc');
|
|
115
|
+
});
|
|
116
|
+
|
|
108
117
|
test('5xx → span.status = "error"', async () => {
|
|
109
118
|
recorderQueue = [{ status: 503 }];
|
|
110
119
|
await fetch('https://api.example.com/x');
|
package/src/handlers/network.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { startSpan } from '@goliapkg/sentori-core';
|
|
1
|
+
import { normalizeUrl, startSpan } from '@goliapkg/sentori-core';
|
|
2
2
|
|
|
3
3
|
import { addBreadcrumb } from '../breadcrumbs';
|
|
4
4
|
import { getConfig } from '../config';
|
|
@@ -42,7 +42,7 @@ function patchFetch(): void {
|
|
|
42
42
|
// attached to error events at capture time and serve a different
|
|
43
43
|
// surface (the "last 100 things" timeline on the issue page).
|
|
44
44
|
const span = startSpan('http.client', {
|
|
45
|
-
name: `${method.toUpperCase()} ${scrubbed}`,
|
|
45
|
+
name: `${method.toUpperCase()} ${normalizeUrl(scrubbed)}`,
|
|
46
46
|
tags: { 'http.method': method.toUpperCase(), 'http.url': scrubbed },
|
|
47
47
|
});
|
|
48
48
|
|
|
@@ -130,7 +130,7 @@ function patchXhr(): void {
|
|
|
130
130
|
const method = this.__sentoriMethod ?? 'GET';
|
|
131
131
|
const url = scrubUrl(this.__sentoriUrl ?? '');
|
|
132
132
|
const span = startSpan('http.client', {
|
|
133
|
-
name: `${method} ${url}`,
|
|
133
|
+
name: `${method} ${normalizeUrl(url)}`,
|
|
134
134
|
tags: { 'http.method': method, 'http.url': url },
|
|
135
135
|
});
|
|
136
136
|
this.__sentoriSpan = span;
|
package/src/navigation.ts
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
// Phase 35 sub-C: react-navigation auto-instrumentation.
|
|
2
|
+
// Phase 39 sub-B: the open `react.navigation` span is also made the
|
|
3
|
+
// active span for that screen's lifetime, so the screen's
|
|
4
|
+
// `http.client` spans (and any other startSpan() calls) attach to it
|
|
5
|
+
// as children — one trace per screen instead of one per request.
|
|
2
6
|
//
|
|
3
7
|
// Mount `useTraceNavigation(navigationRef)` next to your
|
|
4
|
-
// `<NavigationContainer ref={navigationRef}
|
|
5
|
-
//
|
|
6
|
-
//
|
|
8
|
+
// `<NavigationContainer ref={navigationRef}>`. Span names are
|
|
9
|
+
// `<from> → <to>` (or just the route name for the first screen) so
|
|
10
|
+
// the trace list reads as a navigation log.
|
|
7
11
|
//
|
|
8
|
-
// react-navigation is an OPTIONAL peer dependency — apps that
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
12
|
+
// react-navigation is an OPTIONAL peer dependency — apps that don't
|
|
13
|
+
// use it never have to install it. The hook doesn't import from
|
|
14
|
+
// @react-navigation/native; consumers pass in the ref they already
|
|
15
|
+
// have, and we read its state via the public `getCurrentRoute()`.
|
|
16
|
+
//
|
|
17
|
+
// Caveat (active-span on RN is a module variable): requests fired
|
|
18
|
+
// from a `setTimeout` / background poll / detached promise after the
|
|
19
|
+
// screen settled may not see the nav span as active. If you want such
|
|
20
|
+
// a request parented to the current screen, pass it explicitly:
|
|
21
|
+
// `startSpan(op, { parent: activeSpan() })`.
|
|
13
22
|
|
|
14
23
|
import { useEffect, useRef } from 'react';
|
|
15
24
|
|
|
16
|
-
import { startSpan, type SpanHandle } from '@goliapkg/sentori-core';
|
|
25
|
+
import { setActiveSpan, startSpan, type SpanHandle } from '@goliapkg/sentori-core';
|
|
17
26
|
|
|
18
27
|
/** Minimal contract: anything with `addListener('state', cb)` and
|
|
19
28
|
* `getCurrentRoute()` works. The real @react-navigation/native
|
|
@@ -25,9 +34,9 @@ export type NavigationRefLike = {
|
|
|
25
34
|
|
|
26
35
|
/**
|
|
27
36
|
* Subscribe to react-navigation state changes and emit a
|
|
28
|
-
* `react.navigation` span per
|
|
29
|
-
*
|
|
30
|
-
*
|
|
37
|
+
* `react.navigation` span per screen (including the initial one),
|
|
38
|
+
* each a fresh trace root. The span is kept active while the screen
|
|
39
|
+
* is current; child spans created in that window attribute up to it.
|
|
31
40
|
*
|
|
32
41
|
* import { NavigationContainer, useNavigationContainerRef } from '@react-navigation/native'
|
|
33
42
|
* import { useTraceNavigation } from '@goliapkg/sentori-react-native'
|
|
@@ -38,48 +47,53 @@ export type NavigationRefLike = {
|
|
|
38
47
|
* return <NavigationContainer ref={navigationRef}>{...}</NavigationContainer>
|
|
39
48
|
* }
|
|
40
49
|
*
|
|
41
|
-
* Each span carries `{ from, to }`
|
|
42
|
-
* route name as the span name.
|
|
50
|
+
* Each span carries `{ nav.from, nav.to }` tags.
|
|
43
51
|
*/
|
|
44
52
|
export function useTraceNavigation(navigationRef: NavigationRefLike): void {
|
|
45
|
-
// Latest route name we've observed.
|
|
46
|
-
// recorded yet" (initial mount).
|
|
53
|
+
// Latest route name we've observed.
|
|
47
54
|
const lastRouteRef = useRef<null | string>(null);
|
|
48
|
-
// Span
|
|
49
|
-
//
|
|
55
|
+
// Span for the screen the user is currently on. Finished when the
|
|
56
|
+
// next screen is entered (or on unmount).
|
|
50
57
|
const openSpanRef = useRef<null | SpanHandle>(null);
|
|
51
58
|
|
|
52
59
|
useEffect(() => {
|
|
53
60
|
if (typeof navigationRef.addListener !== 'function') return;
|
|
54
61
|
if (typeof navigationRef.getCurrentRoute !== 'function') return;
|
|
55
62
|
|
|
56
|
-
//
|
|
57
|
-
//
|
|
63
|
+
// Each screen gets its own trace root — detach from whatever the
|
|
64
|
+
// previous screen's span was (we keep it active, so without
|
|
65
|
+
// `parent: null` the new one would nest under it).
|
|
66
|
+
const openScreenSpan = (from: null | string, to: string) => {
|
|
67
|
+
const span = startSpan('react.navigation', {
|
|
68
|
+
name: from ? `${from} → ${to}` : to,
|
|
69
|
+
parent: null,
|
|
70
|
+
tags: { 'nav.from': from ?? '', 'nav.to': to },
|
|
71
|
+
});
|
|
72
|
+
openSpanRef.current = span;
|
|
73
|
+
setActiveSpan(span);
|
|
74
|
+
lastRouteRef.current = to;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Open a span for the initial screen so its requests are grouped
|
|
78
|
+
// too (auth / config / first data load are usually the busiest
|
|
79
|
+
// screen of a session).
|
|
58
80
|
const initial = navigationRef.getCurrentRoute()?.name ?? null;
|
|
59
|
-
|
|
81
|
+
if (initial !== null) openScreenSpan(null, initial);
|
|
82
|
+
else lastRouteRef.current = null;
|
|
60
83
|
|
|
61
84
|
const unsubscribe = navigationRef.addListener('state', () => {
|
|
62
85
|
const next = navigationRef.getCurrentRoute()?.name ?? null;
|
|
63
86
|
const prev = lastRouteRef.current;
|
|
64
87
|
if (next === null || next === prev) return;
|
|
65
|
-
|
|
66
|
-
// Close the prior span (if any) before opening the new one so
|
|
67
|
-
// the trace looks like a sequence, not nested.
|
|
68
88
|
openSpanRef.current?.finish({ status: 'ok' });
|
|
69
|
-
|
|
70
|
-
const span = startSpan('react.navigation', {
|
|
71
|
-
name: prev ? `${prev} → ${next}` : next,
|
|
72
|
-
tags: { 'nav.from': prev ?? '', 'nav.to': next },
|
|
73
|
-
});
|
|
74
|
-
openSpanRef.current = span;
|
|
75
|
-
lastRouteRef.current = next;
|
|
89
|
+
openScreenSpan(prev, next);
|
|
76
90
|
});
|
|
77
91
|
|
|
78
92
|
return () => {
|
|
79
93
|
unsubscribe();
|
|
80
|
-
// Close any still-open span on unmount so we don't leak it.
|
|
81
94
|
openSpanRef.current?.finish({ status: 'ok' });
|
|
82
95
|
openSpanRef.current = null;
|
|
96
|
+
setActiveSpan(null);
|
|
83
97
|
};
|
|
84
98
|
}, [navigationRef]);
|
|
85
99
|
}
|