@grafana/faro-react-native-tracing 1.0.0-alpha.1

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.
Files changed (74) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +833 -0
  3. package/dist/cjs/exporters/faroTraceExporter.js +101 -0
  4. package/dist/cjs/exporters/faroTraceExporter.js.map +1 -0
  5. package/dist/cjs/exporters/faroTraceExporter.utils.js +99 -0
  6. package/dist/cjs/exporters/faroTraceExporter.utils.js.map +1 -0
  7. package/dist/cjs/index.js +17 -0
  8. package/dist/cjs/index.js.map +1 -0
  9. package/dist/cjs/instrumentation.js +251 -0
  10. package/dist/cjs/instrumentation.js.map +1 -0
  11. package/dist/cjs/instrumentations/getDefaultOTELInstrumentations.js +77 -0
  12. package/dist/cjs/instrumentations/getDefaultOTELInstrumentations.js.map +1 -0
  13. package/dist/cjs/instrumentations/instrumentationUtils.js +89 -0
  14. package/dist/cjs/instrumentations/instrumentationUtils.js.map +1 -0
  15. package/dist/cjs/processors/faroMetaAttributesSpanProcessor.js +59 -0
  16. package/dist/cjs/processors/faroMetaAttributesSpanProcessor.js.map +1 -0
  17. package/dist/cjs/processors/httpRequestMonitorSpanProcessor.js +98 -0
  18. package/dist/cjs/processors/httpRequestMonitorSpanProcessor.js.map +1 -0
  19. package/dist/cjs/semconv.js +29 -0
  20. package/dist/cjs/semconv.js.map +1 -0
  21. package/dist/cjs/types.js +3 -0
  22. package/dist/cjs/types.js.map +1 -0
  23. package/dist/cjs/utils/sampler.js +21 -0
  24. package/dist/cjs/utils/sampler.js.map +1 -0
  25. package/dist/esm/exporters/faroTraceExporter.js +65 -0
  26. package/dist/esm/exporters/faroTraceExporter.js.map +1 -0
  27. package/dist/esm/exporters/faroTraceExporter.utils.js +88 -0
  28. package/dist/esm/exporters/faroTraceExporter.utils.js.map +1 -0
  29. package/dist/esm/index.js +7 -0
  30. package/dist/esm/index.js.map +1 -0
  31. package/dist/esm/instrumentation.js +183 -0
  32. package/dist/esm/instrumentation.js.map +1 -0
  33. package/dist/esm/instrumentations/getDefaultOTELInstrumentations.js +62 -0
  34. package/dist/esm/instrumentations/getDefaultOTELInstrumentations.js.map +1 -0
  35. package/dist/esm/instrumentations/instrumentationUtils.js +83 -0
  36. package/dist/esm/instrumentations/instrumentationUtils.js.map +1 -0
  37. package/dist/esm/processors/faroMetaAttributesSpanProcessor.js +54 -0
  38. package/dist/esm/processors/faroMetaAttributesSpanProcessor.js.map +1 -0
  39. package/dist/esm/processors/httpRequestMonitorSpanProcessor.js +93 -0
  40. package/dist/esm/processors/httpRequestMonitorSpanProcessor.js.map +1 -0
  41. package/dist/esm/semconv.js +26 -0
  42. package/dist/esm/semconv.js.map +1 -0
  43. package/dist/esm/types.js +2 -0
  44. package/dist/esm/types.js.map +1 -0
  45. package/dist/esm/utils/sampler.js +17 -0
  46. package/dist/esm/utils/sampler.js.map +1 -0
  47. package/dist/types/exporters/faroTraceExporter.d.ts +21 -0
  48. package/dist/types/exporters/faroTraceExporter.utils.d.ts +17 -0
  49. package/dist/types/index.d.ts +7 -0
  50. package/dist/types/instrumentation.d.ts +46 -0
  51. package/dist/types/instrumentations/getDefaultOTELInstrumentations.d.ts +17 -0
  52. package/dist/types/instrumentations/instrumentationUtils.d.ts +26 -0
  53. package/dist/types/processors/faroMetaAttributesSpanProcessor.d.ts +22 -0
  54. package/dist/types/processors/httpRequestMonitorSpanProcessor.d.ts +18 -0
  55. package/dist/types/semconv.d.ts +19 -0
  56. package/dist/types/types.d.ts +31 -0
  57. package/dist/types/utils/sampler.d.ts +12 -0
  58. package/package.json +77 -0
  59. package/src/exporters/faroTraceExporter.test.ts +110 -0
  60. package/src/exporters/faroTraceExporter.ts +64 -0
  61. package/src/exporters/faroTraceExporter.utils.ts +105 -0
  62. package/src/index.ts +16 -0
  63. package/src/instrumentation.ts +241 -0
  64. package/src/instrumentations/getDefaultOTELInstrumentations.test.ts +70 -0
  65. package/src/instrumentations/getDefaultOTELInstrumentations.ts +83 -0
  66. package/src/instrumentations/instrumentationUtils.test.ts +107 -0
  67. package/src/instrumentations/instrumentationUtils.ts +106 -0
  68. package/src/processors/faroMetaAttributesSpanProcessor.test.ts +127 -0
  69. package/src/processors/faroMetaAttributesSpanProcessor.ts +71 -0
  70. package/src/processors/httpRequestMonitorSpanProcessor.ts +106 -0
  71. package/src/semconv.ts +31 -0
  72. package/src/types.ts +39 -0
  73. package/src/utils/sampler.test.ts +51 -0
  74. package/src/utils/sampler.ts +19 -0
package/README.md ADDED
@@ -0,0 +1,833 @@
1
+ # @grafana/faro-react-native-tracing
2
+
3
+ OpenTelemetry distributed tracing integration for Faro React Native SDK.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @grafana/faro-react-native-tracing
9
+ # or
10
+ yarn add @grafana/faro-react-native-tracing
11
+ ```
12
+
13
+ **Prerequisites:**
14
+
15
+ - `@grafana/faro-react-native` - Core Faro SDK for React Native
16
+ - React Native 0.70 or higher
17
+
18
+ ## Quick Start
19
+
20
+ Enable tracing via the `enableTracing` flag—no need to add `TracingInstrumentation` to `instrumentations`:
21
+
22
+ ```typescript
23
+ import { initializeFaro } from '@grafana/faro-react-native';
24
+
25
+ const faro = initializeFaro({
26
+ url: 'https://faro-collector-prod-YOUR-REGION.grafana.net/collect/YOUR_TOKEN_HERE',
27
+ app: {
28
+ name: 'my-react-native-app',
29
+ version: '1.0.0',
30
+ environment: 'production',
31
+ },
32
+ enableTracing: true,
33
+ tracingOptions: {
34
+ // Optional: Propagate trace headers to these URLs for distributed tracing
35
+ instrumentationOptions: {
36
+ propagateTraceHeaderCorsUrls: [/https:\/\/my-api\.com/],
37
+ },
38
+ },
39
+ });
40
+ ```
41
+
42
+ That's it! HTTP requests via `fetch()` are now automatically traced (and correlated with user actions when user action tracking is enabled) and sent to your Faro collector.
43
+
44
+ ## Features
45
+
46
+ ### 🚀 **Automatic Tracing**
47
+
48
+ - **Fetch Instrumentation**: HTTP requests are automatically traced with no code changes
49
+ - **Session Correlation**: Traces are correlated with Faro sessions for complete user journey tracking
50
+ - **User Action Correlation**: Traces are correlated with Faro user actions (when user action tracking is enabled)
51
+ - **User Context**: User information is automatically added to span attributes
52
+ - **Device Metadata**: Device and platform information included in traces (device model, OS, locale, etc.)
53
+
54
+ ### 🔗 **Distributed Tracing**
55
+
56
+ - **W3C Trace Context**: Standards-compliant trace propagation via HTTP headers
57
+ - **Context Propagation**: Seamlessly connect frontend traces to backend services
58
+ - **Configurable CORS**: Control which APIs receive trace headers
59
+
60
+ ### 🎯 **Manual Span Creation**
61
+
62
+ - **OTEL API Access**: Full OpenTelemetry API available via `faro.otel`
63
+ - **Custom Spans**: Create spans for critical business operations
64
+ - **Nested Spans**: Build complex trace hierarchies with parent-child relationships
65
+ - **Span Attributes**: Add custom metadata to spans for rich context
66
+ - **Span Events**: Add timestamped checkpoints within spans
67
+
68
+ ### 🔍 **Faro Integration**
69
+
70
+ - **User Action Correlation**: Spans are correlated with Faro user actions
71
+ - **Log Correlation**: Connect traces with logs using trace/span IDs
72
+ - **Error Correlation**: Errors are automatically linked to active spans
73
+ - **Measurement Correlation**: Performance metrics tied to traces
74
+
75
+ ### 🛡️ **Infinite Loop Prevention**
76
+
77
+ The tracing instrumentation is carefully designed to prevent infinite loops that can occur when tracing causes logging, which causes more tracing:
78
+
79
+ - **Automatic URL Filtering**: Collector URLs are automatically excluded from tracing
80
+ - **Trailing Slash Handling**: Handles URL variations (with/without trailing slashes)
81
+ - **Internal Logging**: Uses `internalLogger` instead of `console.log` internally
82
+ - **Batch Processing**: BatchSpanProcessor delays span export to avoid blocking
83
+ - **No Logging During Export**: Zero console output during trace export
84
+
85
+ ## Configuration Options
86
+
87
+ When using `enableTracing: true`, pass options via `tracingOptions` in your Faro config. For manual setup (e.g. custom config without makeRNConfig), add `TracingInstrumentation` to `instrumentations`:
88
+
89
+ ### Basic Configuration
90
+
91
+ ```typescript
92
+ // Via flag (recommended):
93
+ initializeFaro({
94
+ enableTracing: true,
95
+ tracingOptions: {
96
+ resourceAttributes: { 'deployment.environment': 'staging' },
97
+ instrumentationOptions: {
98
+ propagateTraceHeaderCorsUrls: [/https:\/\/api\.example\.com/],
99
+ },
100
+ },
101
+ });
102
+
103
+ // Or manually:
104
+ new TracingInstrumentation({
105
+ // Optional: Add custom resource attributes
106
+ resourceAttributes: {
107
+ 'service.namespace': 'mobile-apps',
108
+ 'deployment.environment': 'staging',
109
+ 'custom.attribute': 'value',
110
+ },
111
+
112
+ // Optional: Configure trace header propagation
113
+ instrumentationOptions: {
114
+ // URLs that should receive trace context headers
115
+ propagateTraceHeaderCorsUrls: [/https:\/\/api\.example\.com/, 'https://other-api.com'],
116
+
117
+ // Optional: Customize fetch instrumentation
118
+ fetchInstrumentationOptions: {
119
+ // Ignore network performance events (default: true)
120
+ ignoreNetworkEvents: true,
121
+
122
+ // Optional: Add custom attributes to spans
123
+ applyCustomAttributesOnSpan: (span, request, response) => {
124
+ span.setAttribute('http.custom', 'value');
125
+ span.setAttribute('http.request_id', request.headers['x-request-id']);
126
+ },
127
+ },
128
+ },
129
+ });
130
+ ```
131
+
132
+ ### Advanced Configuration
133
+
134
+ ```typescript
135
+ import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
136
+ import { FaroTraceExporter, FaroMetaAttributesSpanProcessor } from '@grafana/faro-react-native-tracing';
137
+
138
+ new TracingInstrumentation({
139
+ // Optional: Custom span processor (advanced)
140
+ spanProcessor: new FaroMetaAttributesSpanProcessor(
141
+ new BatchSpanProcessor(new FaroTraceExporter({ api: faro.api }), {
142
+ scheduledDelayMillis: 1000,
143
+ maxExportBatchSize: 30,
144
+ maxQueueSize: 100,
145
+ }),
146
+ faro.metas
147
+ ),
148
+
149
+ // Optional: Custom OTEL instrumentations (advanced)
150
+ instrumentations: [
151
+ // Your custom OpenTelemetry instrumentations
152
+ ],
153
+ });
154
+ ```
155
+
156
+ ## Usage Examples
157
+
158
+ ### Automatic HTTP Tracing
159
+
160
+ HTTP requests are automatically traced with no code changes:
161
+
162
+ ```typescript
163
+ // This fetch call is automatically traced
164
+ const response = await fetch('https://api.example.com/users');
165
+ const data = await response.json();
166
+
167
+ // The span includes:
168
+ // - HTTP method, URL, status code
169
+ // - Request/response headers (if configured)
170
+ // - Duration
171
+ // - Session ID, user info
172
+ // - Device metadata
173
+ ```
174
+
175
+ **Trace includes:**
176
+
177
+ - `http.method`: `GET`
178
+ - `http.url`: `https://api.example.com/users`
179
+ - `http.status_code`: `200`
180
+ - `session.id`: `abc123`
181
+ - `device.model`: `iPhone 15 Pro`
182
+ - `device.platform`: `iOS`
183
+ - Plus all Faro metas (user, device, session, etc.)
184
+
185
+ ### Manual Span Creation
186
+
187
+ Create custom spans for business operations:
188
+
189
+ ```typescript
190
+ import { faro } from '@grafana/faro-react-native';
191
+
192
+ async function processOrder(orderId: string) {
193
+ // Access OpenTelemetry API via faro.otel
194
+ const { trace } = faro.otel;
195
+ const tracer = trace.getTracer('my-app');
196
+
197
+ // Create a span
198
+ const span = tracer.startSpan('process-order', {
199
+ attributes: {
200
+ 'order.id': orderId,
201
+ 'operation.type': 'payment',
202
+ },
203
+ });
204
+
205
+ try {
206
+ // Your business logic here
207
+ const payment = await processPayment(orderId);
208
+
209
+ // Add attributes as you go
210
+ span.setAttribute('payment.amount', payment.amount);
211
+ span.setAttribute('payment.currency', payment.currency);
212
+
213
+ // Add an event (checkpoint)
214
+ span.addEvent('payment_validated', {
215
+ 'validator.id': payment.validatorId,
216
+ });
217
+
218
+ // Mark span as successful
219
+ span.setStatus({ code: SpanStatusCode.OK });
220
+ } catch (error) {
221
+ // Mark span as error
222
+ span.setStatus({
223
+ code: SpanStatusCode.ERROR,
224
+ message: error.message,
225
+ });
226
+ span.recordException(error);
227
+ throw error;
228
+ } finally {
229
+ // Always end the span
230
+ span.end();
231
+ }
232
+ }
233
+ ```
234
+
235
+ ### Nested Spans (Parent-Child Relationships)
236
+
237
+ Build complex trace hierarchies:
238
+
239
+ ```typescript
240
+ import { faro } from '@grafana/faro-react-native';
241
+ import { SpanStatusCode } from '@opentelemetry/api';
242
+
243
+ async function checkoutFlow(cartId: string) {
244
+ const { trace, context } = faro.otel;
245
+ const tracer = trace.getTracer('my-app');
246
+
247
+ // Create parent span
248
+ const parentSpan = tracer.startSpan('checkout-flow', {
249
+ attributes: { 'cart.id': cartId },
250
+ });
251
+
252
+ try {
253
+ // Use context.with() to make this span the active parent
254
+ await context.with(trace.setSpan(context.active(), parentSpan), async () => {
255
+ // Child span 1: Validate cart
256
+ const validateSpan = tracer.startSpan('validate-cart');
257
+ await validateCart(cartId);
258
+ validateSpan.setStatus({ code: SpanStatusCode.OK });
259
+ validateSpan.end();
260
+
261
+ // Child span 2: Process payment
262
+ const paymentSpan = tracer.startSpan('process-payment');
263
+ await processPayment(cartId);
264
+ paymentSpan.setStatus({ code: SpanStatusCode.OK });
265
+ paymentSpan.end();
266
+
267
+ // Child span 3: Send confirmation
268
+ const confirmSpan = tracer.startSpan('send-confirmation');
269
+ await sendConfirmation(cartId);
270
+ confirmSpan.setStatus({ code: SpanStatusCode.OK });
271
+ confirmSpan.end();
272
+ });
273
+
274
+ parentSpan.setStatus({ code: SpanStatusCode.OK });
275
+ } catch (error) {
276
+ parentSpan.setStatus({
277
+ code: SpanStatusCode.ERROR,
278
+ message: error.message,
279
+ });
280
+ parentSpan.recordException(error);
281
+ } finally {
282
+ parentSpan.end();
283
+ }
284
+ }
285
+ ```
286
+
287
+ ### Correlation with Faro User Actions
288
+
289
+ Spans are automatically correlated with user actions:
290
+
291
+ ```typescript
292
+ import { faro } from '@grafana/faro-react-native';
293
+ import { withFaroUserAction } from '@grafana/faro-react-native';
294
+ import { TouchableOpacity } from 'react-native';
295
+
296
+ // Create a tracked button
297
+ const TrackedButton = withFaroUserAction(TouchableOpacity, 'load-data');
298
+
299
+ function DataLoader() {
300
+ const handleLoad = async () => {
301
+ // This fetch will be traced AND correlated with the user action
302
+ // The span will include: faro.action.user.name = "load-data"
303
+ const response = await fetch('https://api.example.com/data');
304
+ const data = await response.json();
305
+ };
306
+
307
+ return (
308
+ <TrackedButton onPress={handleLoad}>
309
+ <Text>Load Data</Text>
310
+ </TrackedButton>
311
+ );
312
+ }
313
+ ```
314
+
315
+ ### Correlation with Faro Logs
316
+
317
+ Connect traces with logs for full observability:
318
+
319
+ ```typescript
320
+ import { faro } from '@grafana/faro-react-native';
321
+ import { SpanStatusCode } from '@opentelemetry/api';
322
+
323
+ function performOperation() {
324
+ const { trace, context } = faro.otel;
325
+ const tracer = trace.getTracer('my-app');
326
+ const span = tracer.startSpan('important-operation');
327
+
328
+ // Run operation within span context
329
+ context.with(trace.setSpan(context.active(), span), () => {
330
+ // This log will be correlated with the span
331
+ // via trace_id and span_id in the log meta
332
+ faro.api.pushLog(['Operation started'], {
333
+ context: 'operations',
334
+ level: 'info',
335
+ });
336
+
337
+ // Do work...
338
+
339
+ faro.api.pushLog(['Operation completed successfully'], {
340
+ context: 'operations',
341
+ level: 'info',
342
+ });
343
+
344
+ span.setStatus({ code: SpanStatusCode.OK });
345
+ span.end();
346
+ });
347
+ }
348
+ ```
349
+
350
+ ## How It Works
351
+
352
+ ### Architecture
353
+
354
+ ```
355
+ ┌─────────────────────────────────────────────────────────────┐
356
+ │ Your React Native App │
357
+ └──────────────────────┬──────────────────────────────────────┘
358
+ │
359
+ │ fetch() / XHR calls
360
+ ↓
361
+ ┌─────────────────────────────────────────────────────────────┐
362
+ │ FetchInstrumentation + XMLHttpRequestInstrumentation │
363
+ │ • Intercepts fetch() and XMLHttpRequest calls │
364
+ │ • Creates spans automatically │
365
+ │ • Propagates W3C Trace Context headers │
366
+ └──────────────────────┬──────────────────────────────────────┘
367
+ │
368
+ │ Spans
369
+ ↓
370
+ ┌─────────────────────────────────────────────────────────────┐
371
+ │ HttpRequestMonitorSpanProcessor (user action correlation) │
372
+ │ • Notifies httpRequestMonitor for user action halt logic │
373
+ └──────────────────────┬──────────────────────────────────────┘
374
+ │
375
+ │ Spans
376
+ ↓
377
+ ┌─────────────────────────────────────────────────────────────┐
378
+ │ FaroMetaAttributesSpanProcessor │
379
+ │ • Adds Faro metas (session, user) to spans │
380
+ │ • Device/service from resource │
381
+ └──────────────────────┬──────────────────────────────────────┘
382
+ │
383
+ │ Enriched spans
384
+ ↓
385
+ ┌─────────────────────────────────────────────────────────────┐
386
+ │ BatchSpanProcessor (OTEL) │
387
+ │ • Batches spans (max 30, delay 1000ms) │
388
+ │ • Reduces network requests │
389
+ └──────────────────────┬──────────────────────────────────────┘
390
+ │
391
+ │ Span batches
392
+ ↓
393
+ ┌─────────────────────────────────────────────────────────────┐
394
+ │ FaroTraceExporter │
395
+ │ • Converts spans to OTLP format │
396
+ │ • Sends to Faro collector via pushTraces() │
397
+ └──────────────────────┬──────────────────────────────────────┘
398
+ │
399
+ │ OTLP/HTTP
400
+ ↓
401
+ ┌─────────────────────────────────────────────────────────────┐
402
+ │ Grafana Cloud / Faro Collector │
403
+ │ • Receives traces │
404
+ │ • Stores in Tempo │
405
+ │ • Links traces ↔ logs ↔ metrics │
406
+ └─────────────────────────────────────────────────────────────┘
407
+ ```
408
+
409
+ ### Span Lifecycle
410
+
411
+ 1. **Span Creation**: FetchInstrumentation or XMLHttpRequestInstrumentation creates a span when fetch()/XHR is called; W3C Trace Context headers added to the request (if URL matches `propagateTraceHeaderCorsUrls`)
412
+ 2. **Enrichment**: HttpRequestMonitorSpanProcessor notifies for user action correlation; FaroMetaAttributesSpanProcessor adds session and user to spans (device/service come from resource)
413
+ 3. **Batching**: BatchSpanProcessor collects spans (max 30 spans or 1000ms delay)
414
+ 4. **Export**: FaroTraceExporter converts spans to OTLP format; also sends Faro events (e.g. `faro.tracing.fetch`) for CLIENT spans
415
+ 5. **Transmission**: Spans sent to Faro collector via `faro.api.pushTraces()`
416
+
417
+ ### Span Attributes
418
+
419
+ Every span includes these attributes:
420
+
421
+ **Standard OTEL Attributes:**
422
+
423
+ - `http.method` - HTTP method (GET, POST, etc.)
424
+ - `http.url` - Full URL
425
+ - `http.status_code` - HTTP status code
426
+ - `http.target` - URL path
427
+
428
+ **Faro Session Attributes:**
429
+
430
+ - `session.id` - Faro session ID
431
+
432
+ **User Attributes (if set):**
433
+
434
+ - `user.id` - User ID
435
+ - `user.name` - Username
436
+ - `user.email` - Email
437
+
438
+ **Device Attributes (from resource):**
439
+
440
+ - `device.model` - Device model (e.g., "iPhone 15 Pro")
441
+ - `device.brand` - Device manufacturer (e.g., "Apple")
442
+ - `device.platform` - OS name (e.g., "iOS")
443
+ - `device.os.version` - OS version (e.g., "17.0")
444
+ - `device.locale` - Device locale (e.g., "en-US")
445
+
446
+ **App Attributes (from resource):**
447
+
448
+ - `service.name` - App name from config
449
+ - `service.version` - App version
450
+ - `service.namespace` - App namespace (if set)
451
+ - `deployment.environment.name` - Environment (production, staging, etc.)
452
+
453
+ **User Action Attributes (if active):**
454
+
455
+ - `faro.action.user.name` - Name of user action
456
+ - `faro.action.user.parentId` - Parent action ID
457
+
458
+ ## Distributed Tracing
459
+
460
+ ### Connecting Frontend to Backend
461
+
462
+ To enable full distributed tracing, configure your backend to accept W3C Trace Context headers:
463
+
464
+ ```typescript
465
+ // Frontend (React Native)
466
+ new TracingInstrumentation({
467
+ instrumentationOptions: {
468
+ // Propagate headers to your API
469
+ propagateTraceHeaderCorsUrls: [/https:\/\/api\.example\.com/],
470
+ },
471
+ });
472
+
473
+ // Backend (Node.js + Express + OpenTelemetry)
474
+ const { W3CTraceContextPropagator } = require('@opentelemetry/core');
475
+ const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
476
+
477
+ const provider = new NodeTracerProvider();
478
+ provider.register({
479
+ propagator: new W3CTraceContextPropagator(),
480
+ });
481
+ ```
482
+
483
+ When configured correctly:
484
+
485
+ 1. Frontend creates a span for the HTTP request
486
+ 2. Frontend adds `traceparent` and `tracestate` headers to the request
487
+ 3. Backend extracts trace context from headers
488
+ 4. Backend creates child spans linked to frontend trace
489
+ 5. Both frontend and backend spans appear in the same trace in Grafana
490
+
491
+ ### Example Distributed Trace
492
+
493
+ ```
494
+ Trace: abc123-def456-ghi789
495
+
496
+ ├─ [Frontend] fetch POST /api/orders (250ms)
497
+ │ ├─ [Backend] POST /api/orders (200ms)
498
+ │ │ ├─ [Backend] validate-order (10ms)
499
+ │ │ ├─ [Backend] query-database (150ms)
500
+ │ │ └─ [Backend] send-email (40ms)
501
+ │ └─ [Frontend] parse-response (5ms)
502
+ ```
503
+
504
+ ## Troubleshooting
505
+
506
+ ### No traces appearing in Grafana Cloud
507
+
508
+ **Check session sampling:**
509
+
510
+ ```typescript
511
+ const session = faro.api.getSession();
512
+ console.log('Session:', session);
513
+ console.log('Is sampled:', session?.attributes?.isSampled);
514
+ ```
515
+
516
+ If `isSampled` is false, traces won't be collected. Adjust session sampling in the core SDK config (omit `sampling` for 100%):
517
+
518
+ ```typescript
519
+ import { initializeFaro, SamplingRate } from '@grafana/faro-react-native';
520
+
521
+ initializeFaro({
522
+ // ...
523
+ sessionTracking: {
524
+ sampling: new SamplingRate(1), // 100% of sessions
525
+ },
526
+ });
527
+ ```
528
+
529
+ **Verify collector URL:**
530
+
531
+ ```typescript
532
+ // Check that URL is correctly formatted
533
+ console.log('Collector URL:', faro.config.url);
534
+ ```
535
+
536
+ **Check network requests:**
537
+ Open React Native debugger → Network tab → Look for POST requests to `/collect/`
538
+
539
+ ### Infinite loops / exponential requests
540
+
541
+ **Symptoms:**
542
+
543
+ - App sends exponentially growing requests
544
+ - App becomes unresponsive
545
+ - Dev tools crash
546
+
547
+ **Cause:** Trace export requests are being traced, creating an infinite loop.
548
+
549
+ **Solution:** This should be automatically prevented. If you experience this issue:
550
+
551
+ 1. **Update to latest version** - Infinite loop prevention was fixed in recent versions
552
+ 2. **Check for custom span processors** - Don't use `console.log` in custom processors
553
+ 3. **Verify URL filtering** - Collector URLs should be automatically excluded
554
+
555
+ ```typescript
556
+ // Debug: Check which URLs are being ignored
557
+ const ignoredUrls = transport.getIgnoreUrls();
558
+ console.log('Ignored URLs:', ignoredUrls);
559
+ ```
560
+
561
+ ### OTEL is not available error
562
+
563
+ **Error:**
564
+
565
+ ```
566
+ OTEL is not available. Make sure tracing is initialised
567
+ ```
568
+
569
+ **Cause:** Trying to use `faro.otel` before TracingInstrumentation is initialized.
570
+
571
+ **Solution:**
572
+
573
+ ```typescript
574
+ // ✅ Correct: Initialize tracing first
575
+ const faro = initializeFaro({
576
+ instrumentations: [new TracingInstrumentation()],
577
+ });
578
+
579
+ // Then use faro.otel
580
+ const { trace } = faro.otel;
581
+
582
+ // ❌ Wrong: Using faro.otel without tracing instrumentation
583
+ const faro = initializeFaro({
584
+ instrumentations: [], // No tracing!
585
+ });
586
+ const { trace } = faro.otel; // Error!
587
+ ```
588
+
589
+ ### Trace IDs are all zeros (00000000000000000000000000000000)
590
+
591
+ **Cause:** TracerProvider is not registered as the global provider.
592
+
593
+ **Solution:** This should be automatic. If you're using a custom span processor or custom instrumentations, ensure you're not overriding the tracer provider registration.
594
+
595
+ ### Spans missing user/session/device context
596
+
597
+ **Cause:** FaroMetaAttributesSpanProcessor is not wrapping your span processor.
598
+
599
+ **Solution:** Use the default span processor or ensure you wrap your custom processor:
600
+
601
+ ```typescript
602
+ import { FaroMetaAttributesSpanProcessor } from '@grafana/faro-react-native-tracing';
603
+
604
+ new TracingInstrumentation({
605
+ spanProcessor: new FaroMetaAttributesSpanProcessor(yourCustomSpanProcessor, faro.metas),
606
+ });
607
+ ```
608
+
609
+ ### Backend spans not appearing in frontend trace
610
+
611
+ **Cause:** Backend is not configured to accept W3C Trace Context headers, or CORS is blocking headers.
612
+
613
+ **Solutions:**
614
+
615
+ 1. **Enable W3C propagator on backend:**
616
+
617
+ ```javascript
618
+ // Node.js + OpenTelemetry
619
+ const { W3CTraceContextPropagator } = require('@opentelemetry/core');
620
+ provider.register({
621
+ propagator: new W3CTraceContextPropagator(),
622
+ });
623
+ ```
624
+
625
+ 2. **Configure CORS to allow trace headers:**
626
+
627
+ ```javascript
628
+ // Express
629
+ app.use(
630
+ cors({
631
+ allowedHeaders: ['traceparent', 'tracestate'],
632
+ })
633
+ );
634
+ ```
635
+
636
+ 3. **Add backend URL to propagation list:**
637
+
638
+ ```typescript
639
+ new TracingInstrumentation({
640
+ instrumentationOptions: {
641
+ propagateTraceHeaderCorsUrls: [/https:\/\/your-backend\.com/],
642
+ },
643
+ });
644
+ ```
645
+
646
+ ## API Reference
647
+
648
+ ### TracingInstrumentation
649
+
650
+ Main instrumentation class for distributed tracing.
651
+
652
+ ```typescript
653
+ class TracingInstrumentation extends BaseInstrumentation {
654
+ constructor(options?: TracingInstrumentationOptions);
655
+ initialize(): void;
656
+ shutdown(): Promise<void>;
657
+ }
658
+ ```
659
+
660
+ **Options:**
661
+
662
+ ```typescript
663
+ interface TracingInstrumentationOptions {
664
+ // Custom OTEL resource attributes
665
+ resourceAttributes?: Attributes;
666
+
667
+ // Custom OTEL propagator (default: W3CTraceContextPropagator)
668
+ propagator?: TextMapPropagator;
669
+
670
+ // Custom OTEL context manager (default: ZoneContextManager)
671
+ contextManager?: ContextManager;
672
+
673
+ // Custom OTEL instrumentations (replaces default FetchInstrumentation)
674
+ instrumentations?: Instrumentation[];
675
+
676
+ // Custom span processor (replaces default BatchSpanProcessor)
677
+ spanProcessor?: SpanProcessor;
678
+
679
+ // Instrumentation options
680
+ instrumentationOptions?: {
681
+ // URLs to propagate trace headers to
682
+ propagateTraceHeaderCorsUrls?: Array<string | RegExp>;
683
+
684
+ // Fetch instrumentation options
685
+ fetchInstrumentationOptions?: {
686
+ // Custom attributes function
687
+ applyCustomAttributesOnSpan?: FetchCustomAttributeFunction;
688
+
689
+ // Ignore network events (default: true)
690
+ ignoreNetworkEvents?: boolean;
691
+ };
692
+ };
693
+ }
694
+ ```
695
+
696
+ ### FaroTraceExporter
697
+
698
+ Exports OpenTelemetry spans to Faro collector.
699
+
700
+ ```typescript
701
+ class FaroTraceExporter implements SpanExporter {
702
+ constructor(config: FaroTraceExporterConfig);
703
+ export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void;
704
+ shutdown(): Promise<void>;
705
+ }
706
+ ```
707
+
708
+ ### getDefaultOTELInstrumentations()
709
+
710
+ Returns default OpenTelemetry instrumentations for React Native.
711
+
712
+ ```typescript
713
+ function getDefaultOTELInstrumentations(options?: DefaultInstrumentationsOptions): Instrumentation[];
714
+ ```
715
+
716
+ Currently returns:
717
+
718
+ - `FetchInstrumentation` - Automatic HTTP tracing
719
+
720
+ ### faro.otel
721
+
722
+ Access OpenTelemetry APIs for manual tracing.
723
+
724
+ ```typescript
725
+ interface FaroOTEL {
726
+ trace: TraceAPI; // OpenTelemetry trace API
727
+ context: ContextAPI; // OpenTelemetry context API
728
+ }
729
+
730
+ // Usage
731
+ const { trace, context } = faro.otel;
732
+ const tracer = trace.getTracer('my-app');
733
+ const span = tracer.startSpan('operation');
734
+ ```
735
+
736
+ ## Best Practices
737
+
738
+ ### ✅ DO
739
+
740
+ - **Use automatic tracing** - Let FetchInstrumentation handle HTTP requests
741
+ - **Add meaningful attributes** - Include business context in spans
742
+ - **Use span events** - Add checkpoints for important operations
743
+ - **Set span status** - Mark spans as OK or ERROR
744
+ - **End spans** - Always call `span.end()` in a finally block
745
+ - **Use context.with()** - For nested spans
746
+ - **Correlate with user actions** - Use `withFaroUserAction` HOC
747
+ - **Configure propagation** - Add your APIs to `propagateTraceHeaderCorsUrls`
748
+ - **Sample sessions** - Use sampling for high-traffic apps
749
+
750
+ ### ❌ DON'T
751
+
752
+ - **Don't use console.log in span processors** - Causes infinite loops
753
+ - **Don't forget to end spans** - Causes memory leaks
754
+ - **Don't create spans for trivial operations** - Keep trace volume reasonable
755
+ - **Don't include PII in span attributes** - Respect user privacy
756
+ - **Don't trace internal URLs** - Collector URLs are auto-excluded
757
+ - **Don't create deeply nested spans** - Keep hierarchy shallow (< 10 levels)
758
+
759
+ ## Examples
760
+
761
+ See the demo app for complete examples:
762
+
763
+ - [TracingDemoScreen.tsx](../../demo/src/screens/TracingDemoScreen.tsx) - 10 comprehensive tracing scenarios
764
+ - [initialize.ts](../../demo/src/faro/initialize.ts) - Faro initialization with tracing
765
+
766
+ ## Performance Considerations
767
+
768
+ ### Overhead
769
+
770
+ - **Automatic tracing**: ~5-10ms per HTTP request
771
+ - **Manual spans**: ~0.5-1ms per span
772
+ - **Batch processing**: Minimal impact (1000ms delay)
773
+
774
+ ### Optimization Tips
775
+
776
+ 1. **Sample sessions** - Don't trace every session:
777
+
778
+ ```typescript
779
+ import { SamplingRate } from '@grafana/faro-react-native';
780
+
781
+ // ...
782
+ sessionTracking: {
783
+ sampling: new SamplingRate(0.1), // 10% of sessions
784
+ }
785
+ ```
786
+
787
+ 2. **Increase batch size** - Reduce network requests:
788
+
789
+ ```typescript
790
+ new BatchSpanProcessor(exporter, {
791
+ maxExportBatchSize: 50, // Default: 30
792
+ scheduledDelayMillis: 2000, // Default: 1000
793
+ });
794
+ ```
795
+
796
+ 3. **Filter spans** - Don't trace everything:
797
+
798
+ ```typescript
799
+ fetchInstrumentationOptions: {
800
+ applyCustomAttributesOnSpan: (span, request) => {
801
+ // Skip internal requests
802
+ if (request.url.includes('/health')) {
803
+ span.setAttribute('skip', true);
804
+ }
805
+ },
806
+ }
807
+ ```
808
+
809
+ ## TypeScript
810
+
811
+ The package is written in TypeScript and includes type definitions.
812
+
813
+ ```typescript
814
+ import type {
815
+ TracingInstrumentationOptions,
816
+ FaroTraceExporterConfig,
817
+ DefaultInstrumentationsOptions,
818
+ } from '@grafana/faro-react-native-tracing';
819
+ ```
820
+
821
+ ## License
822
+
823
+ Apache-2.0
824
+
825
+ ## Contributing
826
+
827
+ See the main repository [CONTRIBUTING.md](../../CONTRIBUTING.md) for contribution guidelines.
828
+
829
+ ## Support
830
+
831
+ - 📖 [Documentation](https://grafana.com/docs/grafana-cloud/monitor-applications/frontend-observability/)
832
+ - 💬 [GitHub Discussions](https://github.com/grafana/faro-react-native-sdk/discussions)
833
+ - 🐛 [Issue Tracker](https://github.com/grafana/faro-react-native-sdk/issues)