@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.
- package/LICENSE +201 -0
- package/README.md +833 -0
- package/dist/cjs/exporters/faroTraceExporter.js +101 -0
- package/dist/cjs/exporters/faroTraceExporter.js.map +1 -0
- package/dist/cjs/exporters/faroTraceExporter.utils.js +99 -0
- package/dist/cjs/exporters/faroTraceExporter.utils.js.map +1 -0
- package/dist/cjs/index.js +17 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/instrumentation.js +251 -0
- package/dist/cjs/instrumentation.js.map +1 -0
- package/dist/cjs/instrumentations/getDefaultOTELInstrumentations.js +77 -0
- package/dist/cjs/instrumentations/getDefaultOTELInstrumentations.js.map +1 -0
- package/dist/cjs/instrumentations/instrumentationUtils.js +89 -0
- package/dist/cjs/instrumentations/instrumentationUtils.js.map +1 -0
- package/dist/cjs/processors/faroMetaAttributesSpanProcessor.js +59 -0
- package/dist/cjs/processors/faroMetaAttributesSpanProcessor.js.map +1 -0
- package/dist/cjs/processors/httpRequestMonitorSpanProcessor.js +98 -0
- package/dist/cjs/processors/httpRequestMonitorSpanProcessor.js.map +1 -0
- package/dist/cjs/semconv.js +29 -0
- package/dist/cjs/semconv.js.map +1 -0
- package/dist/cjs/types.js +3 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/cjs/utils/sampler.js +21 -0
- package/dist/cjs/utils/sampler.js.map +1 -0
- package/dist/esm/exporters/faroTraceExporter.js +65 -0
- package/dist/esm/exporters/faroTraceExporter.js.map +1 -0
- package/dist/esm/exporters/faroTraceExporter.utils.js +88 -0
- package/dist/esm/exporters/faroTraceExporter.utils.js.map +1 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/instrumentation.js +183 -0
- package/dist/esm/instrumentation.js.map +1 -0
- package/dist/esm/instrumentations/getDefaultOTELInstrumentations.js +62 -0
- package/dist/esm/instrumentations/getDefaultOTELInstrumentations.js.map +1 -0
- package/dist/esm/instrumentations/instrumentationUtils.js +83 -0
- package/dist/esm/instrumentations/instrumentationUtils.js.map +1 -0
- package/dist/esm/processors/faroMetaAttributesSpanProcessor.js +54 -0
- package/dist/esm/processors/faroMetaAttributesSpanProcessor.js.map +1 -0
- package/dist/esm/processors/httpRequestMonitorSpanProcessor.js +93 -0
- package/dist/esm/processors/httpRequestMonitorSpanProcessor.js.map +1 -0
- package/dist/esm/semconv.js +26 -0
- package/dist/esm/semconv.js.map +1 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/utils/sampler.js +17 -0
- package/dist/esm/utils/sampler.js.map +1 -0
- package/dist/types/exporters/faroTraceExporter.d.ts +21 -0
- package/dist/types/exporters/faroTraceExporter.utils.d.ts +17 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/instrumentation.d.ts +46 -0
- package/dist/types/instrumentations/getDefaultOTELInstrumentations.d.ts +17 -0
- package/dist/types/instrumentations/instrumentationUtils.d.ts +26 -0
- package/dist/types/processors/faroMetaAttributesSpanProcessor.d.ts +22 -0
- package/dist/types/processors/httpRequestMonitorSpanProcessor.d.ts +18 -0
- package/dist/types/semconv.d.ts +19 -0
- package/dist/types/types.d.ts +31 -0
- package/dist/types/utils/sampler.d.ts +12 -0
- package/package.json +77 -0
- package/src/exporters/faroTraceExporter.test.ts +110 -0
- package/src/exporters/faroTraceExporter.ts +64 -0
- package/src/exporters/faroTraceExporter.utils.ts +105 -0
- package/src/index.ts +16 -0
- package/src/instrumentation.ts +241 -0
- package/src/instrumentations/getDefaultOTELInstrumentations.test.ts +70 -0
- package/src/instrumentations/getDefaultOTELInstrumentations.ts +83 -0
- package/src/instrumentations/instrumentationUtils.test.ts +107 -0
- package/src/instrumentations/instrumentationUtils.ts +106 -0
- package/src/processors/faroMetaAttributesSpanProcessor.test.ts +127 -0
- package/src/processors/faroMetaAttributesSpanProcessor.ts +71 -0
- package/src/processors/httpRequestMonitorSpanProcessor.ts +106 -0
- package/src/semconv.ts +31 -0
- package/src/types.ts +39 -0
- package/src/utils/sampler.test.ts +51 -0
- 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)
|