@logbrew/react-native 0.1.0 → 0.1.2
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/README.md +431 -18
- package/apollo.cjs +301 -0
- package/apollo.d.cts +70 -0
- package/apollo.d.ts +70 -0
- package/apollo.js +294 -0
- package/examples/apollo-link-spans.mjs +149 -0
- package/examples/index.mjs +29 -1
- package/examples/instrumentation-kit.mjs +304 -0
- package/examples/lifecycle-spans.mjs +131 -0
- package/examples/native-bridge-scope.mjs +107 -0
- package/examples/navigation-resource-spans.mjs +156 -0
- package/examples/package.json +8 -1
- package/examples/real-user-smoke.mjs +106 -9
- package/examples/resource-fetch-spans.mjs +133 -0
- package/examples/trace-correlation.mjs +135 -0
- package/global-errors.cjs +366 -0
- package/global-errors.d.cts +63 -0
- package/global-errors.d.ts +63 -0
- package/global-errors.js +7 -0
- package/index.cjs +625 -111
- package/index.d.cts +236 -0
- package/index.d.ts +236 -0
- package/index.js +613 -95
- package/index.native.js +18 -0
- package/instrumentation.cjs +639 -0
- package/instrumentation.d.cts +84 -0
- package/instrumentation.d.ts +84 -0
- package/instrumentation.js +634 -0
- package/lifecycle.cjs +129 -0
- package/lifecycle.d.cts +50 -0
- package/lifecycle.d.ts +50 -0
- package/lifecycle.js +121 -0
- package/metadata.cjs +175 -0
- package/metadata.js +165 -0
- package/metro.cjs +310 -0
- package/metro.d.cts +37 -0
- package/metro.d.ts +37 -0
- package/metro.js +6 -0
- package/native-bridge.cjs +127 -0
- package/native-bridge.d.cts +60 -0
- package/native-bridge.d.ts +60 -0
- package/native-bridge.js +125 -0
- package/package.json +128 -4
- package/release-artifacts.cjs +344 -0
- package/release-artifacts.d.cts +55 -0
- package/release-artifacts.d.ts +53 -0
- package/release-artifacts.js +8 -0
- package/resource-fetch.cjs +469 -0
- package/resource-fetch.d.cts +60 -0
- package/resource-fetch.d.ts +60 -0
- package/resource-fetch.js +464 -0
package/README.md
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# @logbrew/react-native
|
|
2
2
|
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/LogBrewCo/sdk/main/assets/brand/logbrew-logo-transparent-512.png" alt="LogBrew logo" width="96" height="96">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
3
7
|
React Native helpers for the public LogBrew JavaScript SDK.
|
|
4
8
|
|
|
5
|
-
This package is intentionally thin. It keeps all event validation, retry, flush, and shutdown behavior in `@logbrew/sdk`, while adding mobile-friendly helpers for screen views, app-state changes, handled JavaScript errors, provider/hook usage,
|
|
9
|
+
This package is intentionally thin. It keeps all event validation, retry, flush, and shutdown behavior in `@logbrew/sdk`, while adding mobile-friendly helpers for screen views, app-state changes, product actions, API milestones, handled JavaScript errors, provider/hook usage, active W3C trace correlation, explicit W3C trace propagation, opt-in lifecycle spans, opt-in resource fetch spans, opt-in reversible global fetch spans, app-owned native bridge scope sync, and reversible instrumentation setup.
|
|
6
10
|
|
|
7
11
|
## Install
|
|
8
12
|
|
|
@@ -38,7 +42,47 @@ const stopListening = createAppStateListener(client, AppState, {
|
|
|
38
42
|
});
|
|
39
43
|
```
|
|
40
44
|
|
|
41
|
-
For mobile apps, prefer an app-scoped public key through `clientKey`. `apiKey` is still accepted for compatibility with lower-level SDK examples
|
|
45
|
+
For mobile apps, prefer an app-scoped public key through `clientKey`. `apiKey` is still accepted for compatibility with lower-level SDK examples.
|
|
46
|
+
|
|
47
|
+
## Product Actions And API Milestones
|
|
48
|
+
|
|
49
|
+
Use explicit action and network helpers for important mobile funnel steps your app already understands. These events are designed for timelines and agent analysis without enabling broad automatic replay:
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
import {
|
|
53
|
+
captureReactNativeAction,
|
|
54
|
+
captureReactNativeNetwork,
|
|
55
|
+
createReactNativeTraceContext,
|
|
56
|
+
withLogBrewTrace
|
|
57
|
+
} from "@logbrew/react-native";
|
|
58
|
+
|
|
59
|
+
const trace = createReactNativeTraceContext({
|
|
60
|
+
traceparent: incomingTraceparent
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
withLogBrewTrace(trace, () => {
|
|
64
|
+
captureReactNativeAction(client, {
|
|
65
|
+
name: "checkout.submit",
|
|
66
|
+
screen: "Checkout",
|
|
67
|
+
sessionId: "session_123",
|
|
68
|
+
metadata: {
|
|
69
|
+
funnel: "checkout",
|
|
70
|
+
step: "submit"
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
captureReactNativeNetwork(client, {
|
|
75
|
+
method: "POST",
|
|
76
|
+
routeTemplate: "/api/checkout",
|
|
77
|
+
statusCode: 202,
|
|
78
|
+
durationMs: 128,
|
|
79
|
+
screen: "Checkout",
|
|
80
|
+
sessionId: "session_123"
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`routeTemplate` is stripped of query strings and hashes before capture. Keep metadata low-cardinality and primitive-only, such as screen names, route templates, funnel names, step names, status codes, durations, session IDs, or trace IDs. Active trace metadata overwrites caller-supplied trace keys so accidental spoofed IDs do not break correlation. Do not send request bodies, response bodies, authorization headers, user-entered form values, or full URLs with private query text. LogBrew does not patch global `fetch` or record visual replay from this package.
|
|
42
86
|
|
|
43
87
|
## Error Capture
|
|
44
88
|
|
|
@@ -62,43 +106,160 @@ try {
|
|
|
62
106
|
|
|
63
107
|
Set `includeStack: true` only when your app has decided stack text is safe to send. Non-`Error` thrown values are accepted and converted into issue messages so app error handlers do not need custom guards.
|
|
64
108
|
|
|
109
|
+
### Reversible nonfatal global reports
|
|
110
|
+
|
|
111
|
+
Install the optional global JavaScript handler before root registration when you want supported nonfatal `ErrorUtils` failures captured without an app-owned capture call:
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
import { installLogBrewReactNativeGlobalErrorHandler } from "@logbrew/react-native/global-errors";
|
|
115
|
+
|
|
116
|
+
const errorHandler = installLogBrewReactNativeGlobalErrorHandler({
|
|
117
|
+
client,
|
|
118
|
+
onDiagnostic({ code }) {
|
|
119
|
+
console.warn(`LogBrew error handler: ${code}`);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Roll back during teardown or when disabling the integration.
|
|
124
|
+
errorHandler.remove();
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Installation is idempotent for the active React Native `ErrorUtils` object. The wrapper captures a fixed-content, path-bounded issue for nonfatal global JavaScript errors and then calls the handler that was installed before it. Capture and diagnostic callback failures cannot prevent that prior handler from running. `remove()` reinstates the previous handler only while LogBrew still owns the global slot, so a later integration is not overwritten.
|
|
128
|
+
|
|
129
|
+
Fatal JavaScript errors are chained without capture. The JavaScript package has no synchronous native durable handoff and does not retain fatal events across process termination. Fatal replay requires a bounded synchronous native store plus next-launch delivery acknowledgement. Unhandled Promise rejections are not installed or patched because React Native does not expose one stable supported ownership seam across its runtimes.
|
|
130
|
+
|
|
131
|
+
Automatic events exclude the original error message, raw stack, arbitrary metadata, full URLs, hosts, query strings, and local absolute paths. `onDiagnostic` receives only a fixed code. This integration does not provide native crash capture, ANR/watchdog capture, fatal persistence, or exactly-once fatal replay.
|
|
132
|
+
|
|
133
|
+
When you prepare React Native release artifacts, wrap the app-owned Metro config once. Production bundles and source maps receive one matching Debug ID, while development and hot-reload serialization remain unchanged:
|
|
134
|
+
|
|
135
|
+
```js
|
|
136
|
+
// metro.config.js
|
|
137
|
+
const { getDefaultConfig, mergeConfig } = require("@react-native/metro-config");
|
|
138
|
+
const { withLogBrewMetroConfig } = require("@logbrew/react-native/metro");
|
|
139
|
+
|
|
140
|
+
module.exports = withLogBrewMetroConfig(
|
|
141
|
+
mergeConfig(getDefaultConfig(__dirname), {})
|
|
142
|
+
);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Then use the same release identity when capturing the error. The Metro-injected runtime registry connects each matching parsed JavaScript frame to its Debug ID without another app option:
|
|
146
|
+
|
|
147
|
+
```js
|
|
148
|
+
captureReactNativeError(client, error, {
|
|
149
|
+
platform: Platform,
|
|
150
|
+
appState: AppState,
|
|
151
|
+
screen: "Checkout",
|
|
152
|
+
release: "2026.06.18",
|
|
153
|
+
environment: "production",
|
|
154
|
+
service: "checkout-mobile",
|
|
155
|
+
runtime: "react-native"
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The wrapper composes an existing custom serializer, is idempotent, and adds no network behavior. A string-returning custom serializer may preserve Metro's default bundle code; a serializer that changes code must return `{ code, map }` so LogBrew cannot attach a mismatched source map. If an advanced build pipeline cannot use the wrapper, `debugIdMap` remains an explicit override and takes precedence over runtime discovery. LogBrew records up to 32 ordered path-only generated frames with matching Debug IDs, release/environment/service/runtime, and active trace IDs when available. It strips query strings, hashes, hosts, and local absolute paths from React Native frame data; raw stack text is still opt-in with `includeStack: true`. Hosted source-map lookup remains backend-owned and requires the matching uploaded release artifact.
|
|
160
|
+
|
|
65
161
|
## Provider And Hooks
|
|
66
162
|
|
|
67
163
|
```js
|
|
68
164
|
import { AppState, Platform } from "react-native";
|
|
69
165
|
import {
|
|
166
|
+
createReactNativeTraceContext,
|
|
70
167
|
LogBrewNativeProvider,
|
|
71
168
|
useLogBrewNativeActions
|
|
72
169
|
} from "@logbrew/react-native";
|
|
73
170
|
|
|
74
171
|
function CheckoutScreen() {
|
|
75
|
-
const {
|
|
172
|
+
const {
|
|
173
|
+
captureReactNativeAction,
|
|
174
|
+
captureReactNativeNetwork,
|
|
175
|
+
captureScreenView
|
|
176
|
+
} = useLogBrewNativeActions();
|
|
76
177
|
captureScreenView("Checkout");
|
|
178
|
+
captureReactNativeAction({
|
|
179
|
+
name: "checkout.view",
|
|
180
|
+
screen: "Checkout",
|
|
181
|
+
metadata: { funnel: "checkout", step: "view" }
|
|
182
|
+
});
|
|
183
|
+
captureReactNativeNetwork({
|
|
184
|
+
method: "GET",
|
|
185
|
+
routeTemplate: "/api/cart",
|
|
186
|
+
statusCode: 200,
|
|
187
|
+
durationMs: 42,
|
|
188
|
+
screen: "Checkout"
|
|
189
|
+
});
|
|
77
190
|
return null;
|
|
78
191
|
}
|
|
79
192
|
|
|
80
193
|
export function App({ client }) {
|
|
194
|
+
const trace = createReactNativeTraceContext({
|
|
195
|
+
traceparent: incomingTraceparent
|
|
196
|
+
});
|
|
81
197
|
return (
|
|
82
|
-
<LogBrewNativeProvider client={client} platform={Platform} appState={AppState}>
|
|
198
|
+
<LogBrewNativeProvider client={client} platform={Platform} appState={AppState} trace={trace}>
|
|
83
199
|
<CheckoutScreen />
|
|
84
200
|
</LogBrewNativeProvider>
|
|
85
201
|
);
|
|
86
202
|
}
|
|
87
203
|
```
|
|
88
204
|
|
|
89
|
-
The package ships a `react-native` entry that imports `AppState` and `Platform` for Metro, while the default Node entry accepts those dependencies explicitly. That keeps
|
|
205
|
+
The package ships a `react-native` entry that imports `AppState` and `Platform` for Metro, while the default Node entry accepts those dependencies explicitly. That keeps mobile setup explicit instead of pretending a Node process is a native runtime.
|
|
90
206
|
|
|
91
207
|
## Trace Propagation
|
|
92
208
|
|
|
209
|
+
Use an active trace when one product operation should connect screen views, logs, handled errors, actions, network milestones, explicit spans, and outbound request headers. `createReactNativeTraceContext()` continues a valid W3C `traceparent` with a fresh local span ID and falls back to a local root when the incoming value is missing or malformed:
|
|
210
|
+
|
|
211
|
+
```js
|
|
212
|
+
import {
|
|
213
|
+
createReactNativeSpanAttributes,
|
|
214
|
+
createReactNativeTraceContext,
|
|
215
|
+
createReactNativeTraceHeaders,
|
|
216
|
+
getReactNativeTraceMetadata,
|
|
217
|
+
getActiveLogBrewTrace,
|
|
218
|
+
withLogBrewTrace
|
|
219
|
+
} from "@logbrew/react-native";
|
|
220
|
+
|
|
221
|
+
const trace = createReactNativeTraceContext({
|
|
222
|
+
traceparent: incomingTraceparent
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
withLogBrewTrace(trace, activeTrace => {
|
|
226
|
+
client.log("evt_log_checkout", new Date().toISOString(), {
|
|
227
|
+
message: "checkout started",
|
|
228
|
+
level: "info",
|
|
229
|
+
metadata: {
|
|
230
|
+
screen: "Checkout",
|
|
231
|
+
...getReactNativeTraceMetadata(activeTrace)
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
client.span("evt_span_checkout", new Date().toISOString(), createReactNativeSpanAttributes({
|
|
235
|
+
name: "mobile.checkout",
|
|
236
|
+
status: "ok",
|
|
237
|
+
durationMs: 132,
|
|
238
|
+
trace: activeTrace
|
|
239
|
+
}));
|
|
240
|
+
console.log(getActiveLogBrewTrace()?.traceId);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const headers = createReactNativeTraceHeaders(trace);
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
For async handlers, keep the returned `trace` object and pass it explicitly after `await` boundaries, or use provider `trace` so hook helpers receive it directly. This avoids pretending React Native has a universal async context manager while still making event-handler correlation simple and predictable.
|
|
247
|
+
|
|
93
248
|
Use `createTraceparentFetch()` when a React Native app should connect mobile fetch work to backend traces. Propagation is target-scoped by default: no `traceparent` header is attached unless the request URL matches `tracePropagationTargets`.
|
|
94
249
|
|
|
95
250
|
```js
|
|
96
251
|
import {
|
|
252
|
+
createReactNativeTraceContext,
|
|
97
253
|
createReactNativeTraceparent,
|
|
98
254
|
createTraceparentFetch
|
|
99
255
|
} from "@logbrew/react-native";
|
|
100
256
|
|
|
257
|
+
const trace = createReactNativeTraceContext({
|
|
258
|
+
traceparent: incomingTraceparent
|
|
259
|
+
});
|
|
260
|
+
|
|
101
261
|
const tracedFetch = createTraceparentFetch({
|
|
262
|
+
trace,
|
|
102
263
|
traceparentFactory: () => createReactNativeTraceparent(),
|
|
103
264
|
tracePropagationTargets: [
|
|
104
265
|
"https://api.example.com/",
|
|
@@ -112,22 +273,274 @@ await tracedFetch("https://api.example.com/checkout", {
|
|
|
112
273
|
});
|
|
113
274
|
```
|
|
114
275
|
|
|
115
|
-
`tracePropagationTargets` accepts strings, regular expressions, or `(url) => boolean` functions.
|
|
276
|
+
When `traceparentFactory` is omitted, `createTraceparentFetch()` reuses the supplied or active trace context. `tracePropagationTargets` accepts strings, regular expressions, or `(url) => boolean` functions. String URL targets apply only to the same origin plus a path prefix, so `https://api.example.com/v1` covers `/v1/orders` on that origin but not `https://wrong.example.com` or `/v10`. Keep targets narrow so mobile requests do not send tracing headers to unrelated origins. If the API is cross-origin or behind a gateway, allow the `traceparent` request header there too.
|
|
277
|
+
|
|
278
|
+
## Lifecycle, Navigation, And Resource Spans
|
|
279
|
+
|
|
280
|
+
Use explicit span helpers when you want app foreground/background transitions, route changes, and API resources to appear in the same trace as mobile actions and errors. The AppState lifecycle listener records app-owned lifecycle spans without replacing the simpler action-only `createAppStateListener()`:
|
|
281
|
+
|
|
282
|
+
```js
|
|
283
|
+
import { createReactNativeTraceContext } from "@logbrew/react-native";
|
|
284
|
+
import { createAppStateLifecycleSpanListener } from "@logbrew/react-native/lifecycle";
|
|
285
|
+
|
|
286
|
+
const trace = createReactNativeTraceContext({
|
|
287
|
+
traceparent: incomingTraceparent
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const stopLifecycleTracing = createAppStateLifecycleSpanListener(client, AppState, {
|
|
291
|
+
trace,
|
|
292
|
+
platform: Platform,
|
|
293
|
+
screen: "Checkout",
|
|
294
|
+
sessionId: "session_123",
|
|
295
|
+
captureInitialState: true
|
|
296
|
+
});
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
`createAppStateLifecycleSpanListener()` captures the current AppState as primitive metadata, records transition names such as `app_state:active->background`, and measures duration from the previous observed state when possible. It does not patch React Native internals, derive session health, or inspect native bridge state.
|
|
300
|
+
|
|
301
|
+
The React Navigation listener accepts a navigation container ref shape without adding a React Navigation dependency:
|
|
302
|
+
|
|
303
|
+
```js
|
|
304
|
+
import {
|
|
305
|
+
captureReactNativeResourceSpan,
|
|
306
|
+
createReactNavigationSpanListener,
|
|
307
|
+
} from "@logbrew/react-native";
|
|
308
|
+
|
|
309
|
+
const stopNavigationTracing = createReactNavigationSpanListener(client, navigationRef, {
|
|
310
|
+
trace,
|
|
311
|
+
platform: Platform,
|
|
312
|
+
appState: AppState,
|
|
313
|
+
metadata: { flow: "checkout" }
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
captureReactNativeResourceSpan(client, {
|
|
317
|
+
trace,
|
|
318
|
+
method: "POST",
|
|
319
|
+
routeTemplate: "/api/checkout",
|
|
320
|
+
statusCode: 202,
|
|
321
|
+
durationMs: 171,
|
|
322
|
+
screen: "Checkout"
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
`createReactNavigationSpanListener()` listens for React Navigation `state` changes and uses `__unsafe_action__` dispatch timing when the container exposes it. Route names and query-stripped route paths are captured; route keys are omitted unless `includeRouteKey: true` is set because they can be high-cardinality. `captureReactNativeResourceSpan()` records app-owned resource spans without patching global `fetch`/XHR, reading request bodies, copying headers, or storing full URLs with query text.
|
|
327
|
+
|
|
328
|
+
For app-owned fetch calls where you want the resource span and outbound `traceparent` in one place, use the explicit resource-fetch subpath:
|
|
329
|
+
|
|
330
|
+
```js
|
|
331
|
+
import {
|
|
332
|
+
createReactNativeGraphQLMetadataFactory,
|
|
333
|
+
createReactNativeResourceFetch
|
|
334
|
+
} from "@logbrew/react-native/resource-fetch";
|
|
335
|
+
|
|
336
|
+
const resourceFetch = createReactNativeResourceFetch(client, {
|
|
337
|
+
trace,
|
|
338
|
+
platform: Platform,
|
|
339
|
+
appState: AppState,
|
|
340
|
+
screen: "Checkout",
|
|
341
|
+
measureResponseBodySize: true,
|
|
342
|
+
metadataFactory: createReactNativeGraphQLMetadataFactory({
|
|
343
|
+
endpoint: "/graphql"
|
|
344
|
+
}),
|
|
345
|
+
tracePropagationTargets: ["https://api.example.com/"]
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
await resourceFetch("https://api.example.com/graphql?email=hidden", {
|
|
349
|
+
method: "POST",
|
|
350
|
+
headers: { accept: "application/json" },
|
|
351
|
+
body: JSON.stringify({
|
|
352
|
+
query: "mutation CheckoutSubmit($email: String!) { checkout(email: $email) { id } }",
|
|
353
|
+
variables: { email: "hidden@example.com" }
|
|
354
|
+
})
|
|
355
|
+
});
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
`createReactNativeResourceFetch()` wraps the fetch function your app supplies, or the runtime `fetch` when available. It records status, method, duration, response-start timing, sanitized route template, screen, session, primitive metadata, response size when `Content-Length` is available, and trace correlation. `metadataFactory` is called after each fetch completes or fails so apps can add low-cardinality request metadata such as `graphqlOperationName` or `graphqlOperationType`; by default LogBrew does not parse GraphQL payloads. If you set `measureResponseBodySize: true`, LogBrew can fall back to measuring a cloned response body's byte length when the response omits `Content-Length`; it returns the original response untouched and does not store the response content. Metadata returned from the factory keeps only primitive values and drops sensitive request fields. It does not patch global `fetch` or XHR, inspect request or response bodies by default, capture arbitrary headers, or attach `traceparent` outside `tracePropagationTargets`. Pass `trace` explicitly after `await` boundaries or build the wrapper from provider/hook state so async resource spans stay correlated.
|
|
359
|
+
`createReactNativeGraphQLMetadataFactory()` is an explicit helper for GraphQL requests your app already owns. Pass `endpoint` as a route template, absolute URL without query/hash, `RegExp`, predicate, or an array of those when you use it with broader fetch/XHR instrumentation; LogBrew compares route templates and query-stripped URL paths before parsing. It reads only a JSON string request body to derive `graphqlOperationName` and `graphqlOperationType`, drops variables/query text/body fields, ignores large or non-JSON bodies, and can compose an existing primitive metadata factory. Do not use it on unrelated endpoints without an endpoint matcher.
|
|
360
|
+
|
|
361
|
+
If your app uses Apollo Client, use the optional Apollo subpath with the `ApolloLink` constructor your app already imports:
|
|
362
|
+
|
|
363
|
+
```js
|
|
364
|
+
import { ApolloLink } from "@apollo/client";
|
|
365
|
+
import { createReactNativeApolloLink } from "@logbrew/react-native/apollo";
|
|
366
|
+
|
|
367
|
+
const logbrewApolloLink = createReactNativeApolloLink(client, {
|
|
368
|
+
ApolloLink,
|
|
369
|
+
trace,
|
|
370
|
+
screen: "Checkout",
|
|
371
|
+
metadata: { flow: "checkout" }
|
|
372
|
+
});
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
`createReactNativeApolloLink()` returns an app-owned Apollo Link. It records one `graphql.<operationType> <operationName>` span when an operation completes or fails, writes one normalized W3C `traceparent` into the operation context by default, and keeps primitive metadata such as `graphqlOperationName`, `graphqlOperationType`, `framework`, and `source`. It does not add an Apollo dependency to default LogBrew installs, patch global fetch/XHR, capture query text, variables, payloads, response data, arbitrary headers, cookies, error messages, stacks, baggage, or tracestate. Pass `propagateTraceparent: false` if another Apollo link owns outbound propagation.
|
|
376
|
+
|
|
377
|
+
## Native Bridge Scope Sync
|
|
378
|
+
|
|
379
|
+
Use the native bridge subpath when JavaScript needs to pass the active LogBrew trace into a native module call your app owns. The helper builds a primitive-only scope payload and sends it through a callback or adapter method such as `setLogBrewScope()`:
|
|
380
|
+
|
|
381
|
+
```js
|
|
382
|
+
import { createReactNativeTraceContext } from "@logbrew/react-native";
|
|
383
|
+
import { withLogBrewNativeBridgeScope } from "@logbrew/react-native/native-bridge";
|
|
384
|
+
|
|
385
|
+
const trace = createReactNativeTraceContext({
|
|
386
|
+
traceparent: incomingTraceparent
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
await withLogBrewNativeBridgeScope(nativeCheckoutModule, {
|
|
390
|
+
trace,
|
|
391
|
+
logger: "NativeCheckout",
|
|
392
|
+
screen: "Checkout",
|
|
393
|
+
sessionId: "session_123",
|
|
394
|
+
metadata: {
|
|
395
|
+
routeTemplate: "/native/checkout"
|
|
396
|
+
}
|
|
397
|
+
}, async () => {
|
|
398
|
+
await nativeCheckoutModule.submitOrder();
|
|
399
|
+
});
|
|
400
|
+
```
|
|
116
401
|
|
|
117
|
-
|
|
402
|
+
`withLogBrewNativeBridgeScope()` syncs the scope before the callback and clears it afterward, including async callbacks. The payload contains only trace IDs, sampled flags, and primitive metadata. It does not install a native module, inspect native bridge arguments, sync user/session identity, capture payloads or headers, derive session health, or patch React Native internals.
|
|
118
403
|
|
|
119
|
-
|
|
404
|
+
## Reversible Instrumentation Setup
|
|
405
|
+
|
|
406
|
+
Use the instrumentation subpath when you want one setup call to install the app-owned pieces above and receive a resource fetch wrapper:
|
|
407
|
+
|
|
408
|
+
```js
|
|
409
|
+
import { createReactNativeTraceContext } from "@logbrew/react-native";
|
|
410
|
+
import { createLogBrewReactNativeInstrumentation } from "@logbrew/react-native/instrumentation";
|
|
411
|
+
|
|
412
|
+
const trace = createReactNativeTraceContext({
|
|
413
|
+
traceparent: incomingTraceparent
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
const instrumentation = createLogBrewReactNativeInstrumentation(client, {
|
|
417
|
+
trace,
|
|
418
|
+
platform: Platform,
|
|
419
|
+
appState: AppState,
|
|
420
|
+
navigationContainer: navigationRef,
|
|
421
|
+
nativeBridge: nativeCheckoutModule,
|
|
422
|
+
screen: "Checkout",
|
|
423
|
+
sessionId: "session_123",
|
|
424
|
+
tracePropagationTargets: ["https://api.example.com/"],
|
|
425
|
+
captureInitialLifecycleState: true,
|
|
426
|
+
captureInitialNavigationRoute: true
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
await instrumentation.resourceFetch("https://api.example.com/checkout", {
|
|
430
|
+
method: "POST"
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
instrumentation.remove();
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
`createLogBrewReactNativeInstrumentation()` composes existing AppState lifecycle spans, React Navigation spans, target-scoped resource fetch spans, and native bridge scope sync into a removable handle. It does not patch global `fetch`, XHR, React Navigation, AppState, or native modules by default; it only subscribes to the objects your app passes in and returns `remove()`/`stop()` so setup is reversible. Keep `tracePropagationTargets` narrow and continue to avoid request bodies, response bodies, arbitrary headers, full URLs with query text, and high-cardinality route keys.
|
|
437
|
+
|
|
438
|
+
If migrating an app with many existing `fetch(...)` calls, opt into reversible global fetch instrumentation explicitly:
|
|
439
|
+
|
|
440
|
+
```js
|
|
441
|
+
const instrumentation = createLogBrewReactNativeInstrumentation(client, {
|
|
442
|
+
trace,
|
|
443
|
+
screen: "Checkout",
|
|
444
|
+
instrumentGlobalFetch: true,
|
|
445
|
+
measureFetchResponseBodySize: true,
|
|
446
|
+
tracePropagationTargets: ["https://api.example.com/"]
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
await fetch("https://api.example.com/checkout", { method: "POST" });
|
|
450
|
+
instrumentation.remove();
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
With `instrumentGlobalFetch: true`, LogBrew wraps the current `globalThis.fetch`, records the same sanitized resource spans as `resourceFetch`, and puts the original function back only if LogBrew still owns the `fetch` slot. Response-start timing is measured when the fetch promise resolves, and total duration includes any explicit cloned-body sizing you opt into. Response size is read from `Content-Length` by default; set `measureFetchResponseBodySize: true` only when your app accepts clone-based response body sizing for responses without that header. Outbound `traceparent` remains target-scoped; LogBrew still does not patch XHR, read original request or response bodies, copy arbitrary headers, persist offline requests, capture full URLs with query/hash text, or inspect GraphQL payloads unless you explicitly pass the GraphQL metadata factory above.
|
|
454
|
+
|
|
455
|
+
Apps with older libraries that still use `XMLHttpRequest` can opt into reversible XHR instrumentation separately:
|
|
456
|
+
|
|
457
|
+
```js
|
|
458
|
+
const instrumentation = createLogBrewReactNativeInstrumentation(client, {
|
|
459
|
+
trace,
|
|
460
|
+
screen: "Checkout",
|
|
461
|
+
instrumentGlobalXMLHttpRequest: true,
|
|
462
|
+
measureXhrResponseBodySize: true,
|
|
463
|
+
tracePropagationTargets: ["https://api.example.com/"]
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
const xhr = new XMLHttpRequest();
|
|
467
|
+
xhr.open("POST", "https://api.example.com/checkout?email=hidden");
|
|
468
|
+
xhr.send(JSON.stringify({ ignored: "body is not captured" }));
|
|
469
|
+
instrumentation.remove();
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
With `instrumentGlobalXMLHttpRequest: true`, LogBrew patches only `XMLHttpRequest.prototype.open` and `send`, records sanitized XHR resource spans with status, response-start timing, and response size when `Content-Length` is available, and puts the original methods back when it is safe to do so. If you set `measureXhrResponseBodySize: true`, LogBrew can fall back to measuring the completed XHR response object's byte length without storing the response content. It writes a single `traceparent` through the app's existing `setRequestHeader` only for configured targets. It does not capture request bodies, response bodies, arbitrary request headers, arbitrary response headers, cookies, GraphQL payloads, full URLs with query/hash text, baggage, or tracestate.
|
|
473
|
+
If you pass `metadataFactory: createReactNativeGraphQLMetadataFactory({ endpoint: "/graphql" })`, XHR spans can derive the GraphQL operation name and type from JSON string request bodies your app already owns and skip unrelated endpoints. The helper still drops variables, query text, body fields, headers, payloads, response data, baggage, and tracestate; do not enable it broadly without endpoint matching.
|
|
474
|
+
|
|
475
|
+
## Release Artifact Preparation
|
|
476
|
+
|
|
477
|
+
Use the release-artifacts subpath after the wrapped React Native build has emitted a Metro bundle and source map. The helper preserves the Metro-injected Debug ID, strips embedded source content by default, writes a local manifest, and can run the installed upload path. It also injects a matching ID when used without the Metro wrapper for compatibility. Hosted JavaScript bundle upload is an explicit opt-in; rendered symbolicated issues and native crash symbolication remain separate service capabilities:
|
|
478
|
+
|
|
479
|
+
```js
|
|
480
|
+
import { prepareLogBrewReactNativeReleaseArtifacts } from "@logbrew/react-native/release-artifacts";
|
|
481
|
+
|
|
482
|
+
prepareLogBrewReactNativeReleaseArtifacts({
|
|
483
|
+
bundle: "dist/index.android.bundle",
|
|
484
|
+
sourcemap: "dist/index.android.bundle.map",
|
|
485
|
+
platform: "android",
|
|
486
|
+
release: "2026.06.18",
|
|
487
|
+
environment: "production",
|
|
488
|
+
service: "checkout-mobile",
|
|
489
|
+
root: process.cwd()
|
|
490
|
+
});
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
For a local loopback upload check, use the upload helper against a `localhost` or `127.0.0.1` endpoint:
|
|
494
|
+
|
|
495
|
+
```js
|
|
496
|
+
import { uploadLogBrewReactNativeReleaseArtifacts } from "@logbrew/react-native/release-artifacts";
|
|
497
|
+
|
|
498
|
+
uploadLogBrewReactNativeReleaseArtifacts({
|
|
499
|
+
bundle: "dist/index.android.bundle",
|
|
500
|
+
sourcemap: "dist/index.android.bundle.map",
|
|
501
|
+
platform: "android",
|
|
502
|
+
release: "2026.06.18",
|
|
503
|
+
environment: "production",
|
|
504
|
+
service: "checkout-mobile",
|
|
505
|
+
root: process.cwd(),
|
|
506
|
+
endpoint: "http://127.0.0.1:4319/retry-success",
|
|
507
|
+
maxRetries: 2,
|
|
508
|
+
retryDelay: 0
|
|
509
|
+
});
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
For a hosted release-artifact endpoint, keep the release-artifact auth value in an environment variable and opt in explicitly:
|
|
513
|
+
|
|
514
|
+
```js
|
|
515
|
+
uploadLogBrewReactNativeReleaseArtifacts({
|
|
516
|
+
bundle: "dist/index.android.bundle",
|
|
517
|
+
sourcemap: "dist/index.android.bundle.map",
|
|
518
|
+
projectId: "550e8400-e29b-41d4-a716-446655440000",
|
|
519
|
+
platform: "android",
|
|
520
|
+
release: "2026.06.18",
|
|
521
|
+
environment: "production",
|
|
522
|
+
service: "checkout-mobile",
|
|
523
|
+
root: process.cwd(),
|
|
524
|
+
endpoint: "https://api.logbrew.com/api/release-artifacts",
|
|
525
|
+
allowHostedUpload: true,
|
|
526
|
+
tokenEnv: "LOGBREW_RELEASE_ARTIFACT_AUTH"
|
|
527
|
+
});
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
The helper requires explicit `release`, `environment`, `service`, and `platform` metadata. Hosted uploads also require a UUID `projectId`; local preparation and loopback upload remain valid without it. It defaults minified bundle URLs to `app:///react-native/<platform>/...`, removes query strings and hashes from manifest URLs, and strips source paths under `root` or `stripSourcePrefix`. Hosted endpoints must use HTTPS and must not include embedded auth values, query strings, or fragments. The helper never uses normal SDK ingest keys or account/session API auth values. When `sourcemap` points at a final Hermes-composed map, the helper makes the bundle's `sourceMappingURL` point at that explicit map, so stale packager-map comments do not block manifest generation. The explicit Metro wrapper changes only app-owned serialization and one bounded runtime Debug-ID registry; neither helper patches Gradle, Xcode, global fetch/XHR, request payloads, or transport behavior.
|
|
531
|
+
|
|
532
|
+
React Native native symbols are handled as release artifacts, not runtime telemetry. For local dry-run validation, use the repo release-artifact tooling against app-owned build outputs such as `ios/build/.../*.dSYM`, `android/app/build/outputs/mapping/release/mapping.txt`, and `android/app/build/intermediates/merged_native_libs/.../*.so`. The current public SDK validates metadata and privacy boundaries only; backend upload, storage, lookup, and native symbolication are still backend-owned future support, so do not rely on normal runtime error capture for native crash symbolication yet.
|
|
533
|
+
|
|
534
|
+
## Example Source
|
|
535
|
+
|
|
536
|
+
The package includes example source for screen views, app-state metadata, handled JavaScript errors, provider/hooks, active trace correlation, target-scoped trace propagation, lifecycle/resource spans, native bridge scope sync, and reversible instrumentation setup. After installing, inspect the shipped examples with:
|
|
120
537
|
|
|
121
538
|
```bash
|
|
122
|
-
node node_modules/@logbrew/react-native/examples/index.mjs --help
|
|
123
539
|
node node_modules/@logbrew/react-native/examples/index.mjs --list
|
|
124
|
-
node node_modules/@logbrew/react-native/examples/index.mjs
|
|
125
|
-
node node_modules/@logbrew/react-native/examples/index.mjs
|
|
126
|
-
node node_modules/@logbrew/react-native/examples/index.mjs
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
npm --prefix node_modules/@logbrew/react-native/examples run real-user-smoke
|
|
540
|
+
node node_modules/@logbrew/react-native/examples/index.mjs instrumentation-kit
|
|
541
|
+
node node_modules/@logbrew/react-native/examples/index.mjs lifecycle-spans
|
|
542
|
+
node node_modules/@logbrew/react-native/examples/index.mjs native-bridge-scope
|
|
543
|
+
node node_modules/@logbrew/react-native/examples/index.mjs navigation-resource-spans
|
|
544
|
+
node node_modules/@logbrew/react-native/examples/index.mjs resource-fetch-spans
|
|
545
|
+
node node_modules/@logbrew/react-native/examples/index.mjs trace-correlation
|
|
131
546
|
```
|
|
132
|
-
|
|
133
|
-
The default launcher path runs `real-user-smoke`.
|