@grafana/faro-react-native
v1.1.0
Published
Faro instrumentations, metas, transports for React Native.
Readme
@grafana/faro-react-native
Grafana Faro React Native SDK - Real User Monitoring for React Native applications.
Installation
npm install @grafana/faro-react-native
# or
yarn add @grafana/faro-react-nativeQuick Start
import { initializeFaro } from '@grafana/faro-react-native';
// Initialize Faro in your app entry point (e.g., App.tsx or index.js)
initializeFaro({
// Required
app: {
name: 'your-app-name',
version: '1.0.0',
environment: 'production', // optional
},
url: 'https://faro-collector-prod-YOUR-REGION.grafana.net/collect/YOUR_TOKEN_HERE', // required when fetch transport enabled
// Transports - optional
apiKey: 'your-api-key',
enableTransports: {
fetch: true, // default
offline: false, // optional, caches when offline
console: false, // optional, logs to Metro for debugging
},
transports: [], // optional, extra transports
// Instrumentation flags - optional
enableConsoleCapture: true,
enableErrorReporting: true,
enableUserActions: true,
enableCrashReporting: false,
anrTracking: false, // optional, Android only
anrOptions: {}, // optional
cpuUsageVitals: true,
memoryUsageVitals: true,
refreshRateVitals: false,
fetchVitalsInterval: 30000, // optional, ms
frameMonitoringOptions: {}, // optional, when refreshRateVitals true
enableTracing: false, // optional, requires @grafana/faro-react-native-tracing
tracingOptions: {
instrumentationOptions: {
// Traces fetch() by default. Enable this for axios/direct XHR apps.
enableXhrInstrumentation: false,
},
}, // optional
// Instrumentation options - optional
consoleCaptureOptions: {},
userActionsOptions: {},
ignoreUrls: [],
// Session - optional
sessionTracking: {
enabled: true,
persistent: false,
inactivityTimeout: 15 * 60 * 1000,
sessionExpirationTime: 4 * 60 * 60 * 1000,
maxSessionPersistenceTime: 15 * 60 * 1000,
// Optional: sampling: new SamplingRate(0.1) or new SamplingFunction((ctx) => ...)
// Omit sampling to record all sessions (default).
// generateSessionId, onSessionChange, session.attributes - optional
},
// faro-core - optional
dedupe: true,
metas: [],
instrumentations: [], // extra instrumentations (built-ins from flags above)
// ignoreErrors, beforeSend, preserveOriginalError, internalLoggerLevel, parseStacktrace - optional
});Features
Core Instrumentations
Always enabled:
- Session Instrumentation - Tracks user sessions
- View Instrumentation - Tracks screen/route changes
- App State Instrumentation - Tracks when app goes to background/foreground
- Performance Instrumentation - Monitors CPU usage, memory usage, and app startup time using native OS APIs
- Startup Instrumentation - Automatically tracks app startup duration from process start
- HTTP Instrumentation - Tracks
fetch()requests and correlates them with user actions - XHR Instrumentation - Tracks
XMLHttpRequestcalls when tracing is disabled; withenableTracing: true, enable XHR tracing viatracingOptions.instrumentationOptions.enableXhrInstrumentation
Enabled by default (opt-out via config):
- Console Instrumentation - Captures console logs, warnings, and errors —
enableConsoleCapture(default: true) - Errors Instrumentation - Captures unhandled errors and promise rejections —
enableErrorReporting(default: true) - User Actions Instrumentation - Tracks user interactions with components —
enableUserActions(default: true)
Disabled by default (opt-in via config):
- Frame Monitoring Instrumentation - Monitors refresh rate / frame drops —
refreshRateVitals(default: false) - ANR Instrumentation - Detects Application Not Responding (Android only) —
anrTracking(default: false) - Crash Reporting Instrumentation - Captures native crashes —
enableCrashReporting(default: false) - Tracing Instrumentation - OpenTelemetry distributed tracing (requires
@grafana/faro-react-native-tracing) —enableTracing(default: false)
React Integration
- Error Boundary - Catch and report React component errors with
FaroErrorBoundary
Tracking User Actions
The SDK provides intelligent user action tracking with:
- Intelligent Duration Tracking: Automatically determines when actions complete
- HTTP Request Correlation: Tracks HTTP requests triggered by user actions
- Automatic Lifecycle Management: No manual
end()calls needed with HOC - Halt State: Waits for pending async operations before ending actions
1. Using the HOC (Higher-Order Component) - Recommended
Wrap your touchable components with withFaroUserAction for automatic tracking. Create one HOC per action (as in the demo app):
import { TouchableOpacity, Text } from 'react-native';
import { withFaroUserAction } from '@grafana/faro-react-native';
// Create tracked components at module level (one per action name)
const SubmitButton = withFaroUserAction(TouchableOpacity, 'submit_form');
const LoadDataButton = withFaroUserAction(TouchableOpacity, 'load_data');
function MyForm() {
return (
<SubmitButton onPress={handleSubmit}>
<Text>Submit</Text>
</SubmitButton>
);
}
function DataLoader() {
return (
<LoadDataButton onPress={handleLoadData}>
<Text>Load Data</Text>
</LoadDataButton>
);
}Automatic Features:
- User action starts on press
- HTTP requests triggered by the action are automatically correlated
- Action ends ~100ms after the last activity (HTTP request completion)
- If HTTP requests are pending, enters "halt" state and waits up to 10 seconds
- No manual
end()call required!
You can override the action name and add context per instance with faroActionName and faroContext:
<SubmitButton
onPress={handleSubmit}
faroActionName="custom_action_name"
faroContext={{ formType: 'contact', userId: '123' }}
>
<Text>Submit</Text>
</SubmitButton>Example with HTTP (matches demo's FetchButton pattern):
const FetchButton = withFaroUserAction(TouchableOpacity, 'tap_with_http_request');
function DataLoader() {
const handleLoad = async () => {
// HTTP request correlated with the action; action waits for it before ending
const response = await fetch('https://api.example.com/data');
const data = await response.json();
setData(data);
};
return (
<FetchButton onPress={handleLoad}>
<Text>Load Data</Text>
</FetchButton>
);
}2. Using Manual Tracking
For workflows where you need explicit control. The action auto-ends after ~100ms if no HTTP requests are pending; call action?.end() when done for accurate duration with async work.
Simple (fire-and-forget) — matches demo pattern:
import { trackUserAction } from '@grafana/faro-react-native';
function handleManualAction() {
trackUserAction('manual_user_action_demo', {
source: 'my_screen',
count: '1',
});
// Controller auto-ends after ~100ms
}Complex (async work) — call end() when done:
async function handleComplexAction() {
const action = trackUserAction('complex_workflow', {
step: '1',
userId: '123',
});
await performSomeWork();
// End when done for accurate duration (otherwise auto-ends ~100ms after start)
action?.end();
}How Intelligent Duration Tracking Works
- User Action Starts: When button is pressed or
trackUserAction()is called - Monitor Activity: Tracks HTTP requests started during the action
- Detect Completion:
- If no HTTP requests: Ends after ~100ms
- If HTTP requests pending: Enters "halt" state
- Wait for HTTP: Action stays in halt state until all HTTP requests complete
- Auto-End: Once all activity stops, action automatically ends
- Timeout: If pending operations take too long (>10s), action forcibly ends
Benefits:
- Accurate action duration including async operations
- Correlate errors/events with the user action that triggered them
- Better understanding of user flows and performance
- No need to manually manage action lifecycle
User Identification
Associate telemetry data with specific users:
import { faro } from '@grafana/faro-react-native';
faro.api.setUser({
id: 'user-123',
username: 'john_doe',
email: '[email protected]',
attributes: {
plan: 'premium',
signupDate: '2024-01-01',
},
});Custom Events
Track custom business events:
import { faro } from '@grafana/faro-react-native';
faro.api.pushEvent('purchase_completed', {
productId: 'abc123',
amount: '99.99',
currency: 'USD',
});Logging
Send log messages with different severity levels using the LogLevel enum:
import { faro, LogLevel } from '@grafana/faro-react-native';
// Default level (LogLevel.LOG) when no level is specified
faro.api.pushLog(['Application started']);
// Explicit log levels
faro.api.pushLog(['User signed in'], { level: LogLevel.INFO });
faro.api.pushLog(['Cache hit ratio: 0.95'], { level: LogLevel.DEBUG });
faro.api.pushLog(['Retrying request, attempt 3'], { level: LogLevel.WARN });
faro.api.pushLog(['Payment processing failed'], { level: LogLevel.ERROR });
faro.api.pushLog(['Entering auth flow'], { level: LogLevel.TRACE });Available log levels (from LogLevel enum):
| Level | Description |
| ---------------- | -------------------------------------------------- |
| LogLevel.TRACE | Fine-grained diagnostic information |
| LogLevel.DEBUG | Detailed information useful during development |
| LogLevel.INFO | General informational messages |
| LogLevel.LOG | Standard log messages (default when omitted) |
| LogLevel.WARN | Potentially harmful situations |
| LogLevel.ERROR | Error events that might still allow the app to run |
You can also attach additional context to log messages:
faro.api.pushLog(['Order placed'], {
level: LogLevel.INFO,
context: {
orderId: 'order-456',
userId: 'user-123',
},
});Performance Measurements
Track performance metrics:
import { faro } from '@grafana/faro-react-native';
const startTime = Date.now();
await performHeavyOperation();
const duration = Date.now() - startTime;
faro.api.pushMeasurement({
type: 'heavy_operation',
values: {
duration,
},
});Manual Error Tracking
Report errors manually:
import { faro } from '@grafana/faro-react-native';
try {
await riskyOperation();
} catch (error) {
faro.api.pushError(error, {
context: {
operation: 'riskyOperation',
userId: '123',
},
});
}React Error Boundary
Catch and report React component errors automatically with Faro's Error Boundary:
Using the Component
import { FaroErrorBoundary } from '@grafana/faro-react-native';
import { Text, View } from 'react-native';
function App() {
return (
<FaroErrorBoundary fallback={<Text>Something went wrong</Text>}>
<YourApp />
</FaroErrorBoundary>
);
}Using the HOC
import { withFaroErrorBoundary } from '@grafana/faro-react-native';
import { Text } from 'react-native';
const MyComponent = () => <Text>Hello</Text>;
export default withFaroErrorBoundary(MyComponent, {
fallback: <Text>Error occurred</Text>,
});Advanced Configuration
import { FaroErrorBoundary } from '@grafana/faro-react-native';
function App() {
return (
<FaroErrorBoundary
// Static fallback UI
fallback={<ErrorScreen />}
// OR: Dynamic fallback with error details and reset function
fallback={(error, resetError) => (
<View>
<Text>Error: {error.message}</Text>
<Button title="Try Again" onPress={resetError} />
</View>
)}
// Modify error before it's sent to Faro
beforeCapture={(error) => {
console.log('About to capture:', error);
}}
// React to errors
onError={(error) => {
console.error('Error caught:', error);
}}
// Lifecycle hooks
onMount={() => console.log('Error boundary mounted')}
onUnmount={(error) => console.log('Unmounting, had error:', error)}
onReset={(error) => console.log('Resetting from error:', error)}
// Pass additional options to faro.api.pushError
pushErrorOptions={{
context: {
screen: 'HomeScreen',
},
}}
>
<YourApp />
</FaroErrorBoundary>
);
}Error Boundary Props:
| Prop | Type | Description |
| ------------------ | ------------------------------------------------ | ------------------------------------------------------------------------ |
| fallback | ReactElement \| (error, reset) => ReactElement | UI to show when an error occurs. Can be static or a render function. |
| beforeCapture | (error: Error) => void | Called before error is sent to Faro. Use to modify or inspect the error. |
| onError | (error: Error) => void | Called after error is caught. Use for logging or analytics. |
| onMount | () => void | Called when error boundary mounts. |
| onReset | (error: Error \| null) => void | Called when error boundary is reset (via resetError function). |
| onUnmount | (error: Error \| null) => void | Called when error boundary unmounts. Receives error if one was caught. |
| pushErrorOptions | PushErrorOptions | Additional options passed to faro.api.pushError(). |
| children | ReactNode \| () => ReactNode | Component(s) to wrap with error boundary. |
Features:
- ✅ Automatic Error Reporting: Errors are automatically sent to Faro
- ✅ Component Stack Traces: Includes React component stack in error reports
- ✅ Custom Fallback UI: Show user-friendly error messages
- ✅ Error Reset: Programmatically recover from errors
- ✅ Lifecycle Hooks: React to error boundary lifecycle events
- ✅ Flexible Configuration: Static or dynamic fallback, custom error handling
Best Practices:
Wrap your entire app for global error catching:
<FaroErrorBoundary fallback={<GlobalErrorScreen />}> <App /> </FaroErrorBoundary>Wrap critical sections for granular error handling:
<FaroErrorBoundary fallback={<CheckoutError />}> <CheckoutFlow /> </FaroErrorBoundary>Use dynamic fallback for better UX:
fallback={(error, resetError) => ( <ErrorView error={error} onRetry={resetError} /> )}Combine with ErrorsInstrumentation for comprehensive error tracking:
- Error Boundary catches React component errors
- ErrorsInstrumentation catches unhandled errors and promise rejections
Configuration
Instrumentation Flags
Instrumentations are controlled by flags in ReactNativeConfig. Pass these to initializeFaro():
// Key instrumentation flags (all in ReactNativeConfig)
enableErrorReporting?: boolean; // default: true
enableConsoleCapture?: boolean; // default: true
enableUserActions?: boolean; // default: true
enableCrashReporting?: boolean; // default: false
anrTracking?: boolean; // default: false (Android only)
cpuUsageVitals?: boolean; // default: true
memoryUsageVitals?: boolean; // default: true
refreshRateVitals?: boolean; // default: falseConsole Instrumentation Configuration
The console instrumentation can be configured with advanced options:
import { initializeFaro, LogLevel } from '@grafana/faro-react-native';
initializeFaro({
url: 'https://your-faro-collector-url',
app: {
name: 'my-app',
version: '1.0.0',
},
consoleCaptureOptions: {
// Configure which log levels to capture
// By default: [LogLevel.DEBUG, LogLevel.TRACE, LogLevel.LOG] are disabled
disabledLevels: [LogLevel.DEBUG, LogLevel.TRACE],
// Treat console.error as log instead of error (default: false)
consoleErrorAsLog: false,
// Enable advanced error serialization for better error details (default: false)
// When enabled, payloads may become larger but include more error context
serializeErrors: true,
// Optional: Custom error serializer function
errorSerializer: (args) => {
return args
.map((arg) => {
if (typeof arg === 'object') {
return JSON.stringify(arg, null, 2); // Pretty print objects
}
return String(arg);
})
.join(' ');
},
},
});Console Instrumentation Features:
- Configurable Log Levels: Choose which console methods to capture (log, info, warn, error, debug, trace)
- Smart Object Serialization: Automatically converts objects to JSON strings instead of
[object Object] - Advanced Error Serialization: Extract detailed error information including:
- Error message and type
- Stack frames with file, function, line, and column information
- Better handling of Error objects in console.error
- Flexible Error Handling: Choose to send console.error as:
- Errors (default): Appears in error tracking views
- Logs: Appears in log views with error context
- Custom Serializers: Provide your own logic for converting console arguments to strings
- Unpatch Support: Clean up console patching when needed
Example Use Cases:
// Capture all console levels including debug
initializeFaro({
url: 'https://your-faro-collector-url',
app: { name: 'my-app', version: '1.0.0' },
enableConsoleCapture: true,
consoleCaptureOptions: {
disabledLevels: [], // Capture everything
},
});
// Send console.error as logs instead of errors
initializeFaro({
// ...
consoleCaptureOptions: {
consoleErrorAsLog: true,
},
});
// Enable detailed error serialization
initializeFaro({
// ...
consoleCaptureOptions: {
serializeErrors: true, // Extract stack frames and error details
},
});Errors Instrumentation Configuration
The errors instrumentation now includes enhanced features for React Native error tracking:
import { initializeFaro, ErrorsInstrumentation } from '@grafana/faro-react-native';
initializeFaro({
url: 'https://your-faro-collector-url',
app: {
name: 'my-app',
version: '1.0.0',
},
instrumentations: [
new ErrorsInstrumentation({
// Ignore specific errors by message pattern
ignoreErrors: [/network timeout/i, /cancelled/i, /aborted/i],
// Enable error deduplication (default: true)
// Prevents sending the same error multiple times within a time window
enableDeduplication: true,
// Deduplication time window in milliseconds (default: 5000)
// Errors with same message/stack within this window are considered duplicates
deduplicationWindow: 5000,
// Maximum number of errors to track for deduplication (default: 50)
maxDeduplicationEntries: 50,
}),
],
});Enhanced Errors Instrumentation Features:
React Native Stack Trace Parsing: Automatically parses React Native stack traces into structured stack frames
- Supports multiple formats: Dev mode, Release/minified, Metro bundler, Native calls
- Extracts function name, filename, line number, and column number
- Handles platform-specific stack trace formats (iOS/Android)
Platform Context: Automatically includes platform information with every error:
- Platform OS (ios/android)
- Platform version
- JavaScript engine (Hermes detection)
Error Deduplication: Prevents duplicate error reports
- Tracks errors by message and stack trace
- Configurable time window (default: 5 seconds)
- Memory-efficient with configurable maximum entries
Error Filtering: Ignore specific errors using regex patterns
- Filter by error message
- Useful for ignoring known non-critical errors
- Reduces noise in error tracking
Automatic Error Capture:
- Unhandled JavaScript errors (via ErrorUtils)
- Unhandled promise rejections
- Preserves original error handlers
Example - Different Stack Trace Formats Handled:
// Dev mode: at functionName (file.js:123:45)
// Release: functionName@123:456
// Native: at functionName (native)
// Metro: at Object.functionName (/path/to/file.js:123:456)All formats are automatically parsed and converted to structured stack frames sent to Grafana Cloud.
Example - Platform Context Included:
Every error report includes:
{
platform: "ios", // or "android"
platformVersion: "17.0", // iOS/Android version
isHermes: "true" // JavaScript engine
}Use Cases:
// Ignore network-related errors
new ErrorsInstrumentation({
ignoreErrors: [/network/i, /fetch failed/i],
});
// Increase deduplication window for high-frequency errors
new ErrorsInstrumentation({
deduplicationWindow: 10000, // 10 seconds
});
// Disable deduplication for debugging
new ErrorsInstrumentation({
enableDeduplication: false,
});Custom Configuration
import { initializeFaro } from '@grafana/faro-react-native';
import { ConsoleInstrumentation, ErrorsInstrumentation } from '@grafana/faro-react-native';
initializeFaro({
url: 'https://your-faro-collector-url',
app: {
name: 'my-app',
version: '1.0.0',
environment: 'production',
},
instrumentations: [new ConsoleInstrumentation(), new ErrorsInstrumentation()],
// Add custom metas
metas: [
// Your custom meta implementations
],
});Session Configuration
The SDK supports both persistent and volatile session tracking with configurable expiration and inactivity timeouts:
import { initializeFaro, SamplingFunction, SamplingRate } from '@grafana/faro-react-native';
initializeFaro({
url: 'https://your-faro-collector-url',
app: {
name: 'my-app',
version: '1.0.0',
},
sessionTracking: {
enabled: true, // default: true
persistent: true, // default: false (volatile)
// Configurable timeouts (all in ms):
inactivityTimeout: 15 * 60 * 1000, // default: 15 min
sessionExpirationTime: 4 * 60 * 60 * 1000, // default: 4 h
maxSessionPersistenceTime: 15 * 60 * 1000, // default: 15 min
// Optional: session sampling (omit = all sessions recorded)
// sampling: new SamplingRate(0.1), // 10% fixed
// sampling: new SamplingFunction((context) =>
// context.meta.app?.environment === 'production' ? 0.1 : 1
// ),
// Optional: Custom session ID generator
generateSessionId: () => 'custom-session-id',
// Optional: Callback when session changes
onSessionChange: (previousSession, newSession) => {
console.log('Session changed:', previousSession?.id, '->', newSession?.id);
},
// Optional: Initial session attributes
session: {
attributes: {
customAttribute: 'value',
},
},
},
});Session Types:
Persistent Sessions (
persistent: true): Stored in AsyncStorage and survive app restarts. Sessions expire aftersessionExpirationTime(default 4 h) orinactivityTimeout(default 15 min).Volatile Sessions (
persistent: false, default): Stored in memory only. Each app launch creates a new session.
Sampling: Set sessionTracking.sampling to a SamplingRate (fixed 0–1) or SamplingFunction (dynamic, receives context.meta). Omit sampling to record all sessions. The decision is made once per session.
Defaults: persistent=false, inactivityTimeout=15min, sessionExpirationTime=4h, maxSessionPersistenceTime=15min
Session events:
The SDK emits session_start when a new session is created (including when session metadata changes to a new session id). Resuming a valid persisted session does not emit additional lifecycle events.
Default Session Attributes
Every telemetry event automatically includes default session attributes with device and SDK information. These attributes match the Grafana Faro Flutter SDK format for cross-platform compatibility.
Automatically Collected Attributes:
| Attribute | Description | iOS Example | Android Example |
| ---------------------- | -------------------- | --------------- | --------------------- |
| faro_sdk_version | SDK version | 2.0.2 | 2.0.2 |
| react_native_version | React Native version | 0.75.1 | 0.75.1 |
| device_os | Operating system | iOS | Android |
| device_os_version | OS version | 17.0 | 15 |
| device_os_detail | Detailed OS info | iOS 17.0 | Android 15 (SDK 35) |
| device_manufacturer | Manufacturer | apple | samsung |
| device_model | Raw model identifier | iPhone16,1 | SM-A155F |
| device_model_name | Human-readable model | iPhone 15 Pro | SM-A155F* |
| device_brand | Device brand | iPhone | samsung |
| device_is_physical | Physical or emulator | true | true |
| device_id | Unique device ID | uuid | uuid |
*Android does not provide a mapping from model codes to marketing names, so device_model_name equals device_model.
How It Works:
- Attributes are collected automatically during session initialization
- No manual configuration needed
- Uses existing
react-native-device-infodependency - Attributes are included with every telemetry event (logs, errors, measurements, etc.)
- Custom attributes can be added via
sessionTracking.session.attributes(default attributes take precedence)
Example Grafana Query:
# Filter events by device OS
{app_name="my-app"} | json | device_os="iOS"
# Filter by specific device model
{app_name="my-app"} | json | device_model="iPhone16,1"
# Group by manufacturer
{app_name="my-app"} | json | count by device_manufacturer
# Filter emulator vs physical devices
{app_name="my-app"} | json | device_is_physical="false"AppState Tracking
The SDK automatically tracks React Native app state changes (foreground/background/inactive). This is enabled by default and requires no additional configuration.
import { initializeFaro } from '@grafana/faro-react-native';
initializeFaro({
url: 'https://your-faro-collector-url',
app: {
name: 'my-app',
version: '1.0.0',
},
});App States:
- active: App is running in the foreground
- background: User switched to another app or home screen
- inactive: Transitional state (incoming call, control center on iOS)
- unknown: Initial state before first change (iOS only)
- extension: App extension is running (iOS only)
App State Events:
The SDK automatically emits app_lifecycle_changed events when the app state transitions:
{
event_name: "app_lifecycle_changed",
fromState: "active", // Previous AppState (active | background | inactive | unknown | extension)
toState: "background", // New state
duration: "5234", // Time spent in previous state (ms)
timestamp: "1701518400000" // Unix timestamp
}Use Cases:
- Track user engagement (foreground vs background time)
- Identify background-related crashes or errors
- Measure session duration by app state
- Optimize background task scheduling
- Detect performance issues after returning from background
Example Queries (Grafana Explore with Loki):
# View all app state changes
{app_name="my-app", kind="event"}
| json
| event_name="app_lifecycle_changed"
# Count background transitions (toState="background")
{app_name="my-app", kind="event"}
| json
| event_name="app_lifecycle_changed"
| toState="background"
# Count foreground transitions (toState="active")
{app_name="my-app", kind="event"}
| json
| event_name="app_lifecycle_changed"
| toState="active"
# Average time in foreground (unwrap duration)
{app_name="my-app", kind="event"}
| json
| event_name="app_lifecycle_changed"
| fromState="active"
| unwrap duration
| avgFor a complete example of app state tracking in action, see the demo application.
Performance Instrumentation Configuration
The Performance Instrumentation provides comprehensive performance monitoring for React Native applications, including CPU usage, memory usage, and app startup time tracking.
System Resource Monitoring (CPU & Memory)
The SDK automatically monitors system resources using native OS-level APIs for accurate metrics:
iOS Implementation:
- CPU: Uses
host_statistics()with differential calculation for precise CPU percentage - Memory: Uses
task_info()to measure RSS (Resident Set Size) in kilobytes
Android Implementation:
- CPU: Parses
/proc/[pid]/statwith differential calculation - Memory: Parses
/proc/[pid]/statusfor VmRSS in kilobytes
Configuration:
import { initializeFaro } from '@grafana/faro-react-native';
initializeFaro({
url: 'https://your-faro-collector-url',
app: {
name: 'my-app',
version: '1.0.0',
},
// Enable performance monitoring (defaults)
cpuUsageVitals: true,
memoryUsageVitals: true,
// Collection interval in milliseconds (default: 30000 - 30 seconds)
fetchVitalsInterval: 30000,
});Metrics Collected:
CPU Usage (
app_cpu_usagemeasurement):cpu_usage- CPU usage percentage (0-100+)- Collected periodically based on
fetchVitalsInterval - First reading establishes baseline (returns 0)
- Subsequent readings show actual CPU percentage
Memory Usage (
app_memorymeasurement):mem_usage- Memory usage in kilobytes (RSS)- Collected periodically based on
fetchVitalsInterval - Measures physical memory currently used by the app
Example Grafana Queries (Loki):
# Average CPU usage over time
{app_name="my-app", kind="measurement"}
| json
| type="app_cpu_usage"
| unwrap cpu_usage
| rate(1m)
# Memory usage spikes
{app_name="my-app", kind="measurement"}
| json
| type="app_memory"
| unwrap mem_usage
| topk(10)
# CPU usage during specific screen
{app_name="my-app", kind="measurement"}
| json
| type="app_cpu_usage"
| view_name="HomeScreen"
| unwrap cpu_usage
| avgPlatform Requirements:
- iOS: Requires iOS 13.4+
- Android: Requires API 21+ (Lollipop) for CPU monitoring, any version for memory
No Manual Setup Required!
- Native modules are automatically linked via CocoaPods (iOS) and Gradle (Android)
- OS-level APIs are used - no permissions needed
- Works out of the box with default configuration
Startup Performance Monitoring
The SDK automatically tracks app startup time from process start to Faro initialization. No configuration needed; it is always enabled.
Startup Metric (app_startup measurement):
appStartDuration- Time from process start to Faro init (milliseconds)coldStart- 1 for cold start, 0 for warm start- Measured using native OS APIs:
- iOS:
sysctl()withKERN_PROCto get process start time - Android: Parses process start time from system
- iOS:
Example Query:
# Average app startup time
{app_name="my-app", kind="measurement"}
| logfmt
| type="app_startup"
| unwrap value_total_duration_ms
| avgPerformance Best Practices
For Production:
initializeFaro({
url: '...',
app: { name: 'my-app', version: '1.0.0' },
cpuUsageVitals: true,
memoryUsageVitals: true,
fetchVitalsInterval: 30000, // 30 seconds - good balance
});For Debugging/Testing:
initializeFaro({
url: '...',
app: { name: 'my-app', version: '1.0.0' },
cpuUsageVitals: true,
memoryUsageVitals: true,
fetchVitalsInterval: 5000, // 5 seconds - more frequent for testing
});Use Cases:
- Detect Memory Leaks: Monitor memory growth over time
- Identify CPU Bottlenecks: Correlate CPU spikes with user actions
- Track Startup Performance: Measure app launch time improvements
- Performance Regression Testing: Compare metrics across app versions
- Resource-Based Crash Analysis: Correlate crashes with high memory/CPU usage
Navigation Integration
Faro provides seamless integration with React Navigation to automatically track screen changes.
Quick Start
import { useNavigationContainerRef } from '@react-navigation/native';
import { useFaroNavigation } from '@grafana/faro-react-native';
function App() {
const navigationRef = useNavigationContainerRef();
// Automatically track navigation changes
useFaroNavigation(navigationRef);
return <NavigationContainer ref={navigationRef}>{/* Your navigation */}</NavigationContainer>;
}Static Navigation API
For React Navigation 7+ static navigation:
import { createStaticNavigation, useNavigationContainerRef } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { useFaroNavigation } from '@grafana/faro-react-native';
const RootStack = createNativeStackNavigator({
screens: {
Home: { screen: HomeScreen, options: { title: 'Welcome' } },
Profile: { screen: ProfileScreen },
},
});
const Navigation = createStaticNavigation(RootStack);
function App() {
const navigationRef = useNavigationContainerRef();
useFaroNavigation(navigationRef);
return <Navigation ref={navigationRef} />;
}For detailed integration guides, advanced usage, and troubleshooting, see NAVIGATION_INTEGRATION.md.
Enhanced Device Meta
The Faro React Native SDK automatically collects comprehensive device information to provide better context for debugging and analytics.
Automatically Collected Information
The SDK collects the following device information synchronously:
- Device Info: Brand, model, device ID, type (mobile/tablet)
- System Info: OS name, OS version, app version
- Locale/Language: Device locales, timezone, primary language
Device Information
The SDK automatically collects device information and sends it as session attributes with every telemetry event. This matches the Faro Flutter SDK convention and provides comprehensive device context for mobile observability.
Session Attributes
All device information is sent as session attributes (not browser meta) to match Flutter SDK:
Core Attributes (matching Flutter SDK):
faro_sdk_version- SDK version (e.g., "1.0.0")react_native_version- React Native version (e.g., "0.75.1")device_os- Operating system name ("iOS" or "Android")device_os_version- OS version (e.g., "17.0", "14")device_os_detail- Detailed OS info (e.g., "iOS 17.0", "Android 14 (SDK 34)")device_manufacturer- Manufacturer (e.g., "apple", "samsung")device_model- Raw model identifier (e.g., "iPhone16,1", "SM-A155F")device_model_name- Human-readable name (e.g., "iPhone 15 Pro")device_brand- Device brand (e.g., "iPhone", "samsung")device_is_physical- Physical device or emulator ("true" or "false")device_id- Unique device identifier (UUID)
Additional Monitoring Attributes (React Native specific):
device_type- Device type ("mobile" or "tablet")device_memory_total- Total device memory in bytesdevice_memory_used- Currently used memory in bytesdevice_battery_level- Battery percentage (e.g., "85") - if availabledevice_is_charging- Whether charging ("true" or "false") - if availabledevice_low_power_mode- Low power mode enabled ("true" or "false") - if availabledevice_carrier- Mobile carrier name (e.g., "Verizon") - if available
These attributes are automatically collected during Faro initialization and included with all telemetry events.
Querying Device Information
Debug device-specific issues:
{service_name="MyApp", device_manufacturer="samsung"}
| logfmt
| kind="exception"Filter by OS version:
{service_name="MyApp", device_os="Android", device_os_version="14"}
| logfmtFind emulator vs physical device issues:
{service_name="MyApp", device_is_physical="false"}
| logfmt
| kind="exception"Track memory-related issues:
{service_name="MyApp"}
| logfmt
| device_memory_used > 1000000000
| kind="exception"Monitor low battery scenarios:
{service_name="MyApp"}
| logfmt
| device_battery_level <= "20"
| kind="exception"Notes
- All device info is collected automatically when Faro initializes
- Session attributes are included with every telemetry event
- All fields are optional and gracefully handle permission errors
- The React Native SDK sends an empty
pagemeta field to override faro-core's default web-specific page meta - Screen tracking is handled via
viewmeta instead ofpagemeta (matching Flutter SDK) - Battery, carrier, and low power mode info may not be available on all devices/OS versions
TypeScript
The package is written in TypeScript and includes type definitions out of the box.
import type { ReactNativeConfig, WithFaroUserActionProps } from '@grafana/faro-react-native';Examples
See the demo directory for a complete example application.
API Reference
Core API
initializeFaro(config: ReactNativeConfig): void- Initialize the Faro SDKfaro.api.pushEvent(name: string, attributes?: Record<string, string>)- Track custom eventsfaro.api.pushLog(message: string[], options?: PushLogOptions)- Send log messages. Useoptions.levelwith theLogLevelenum (TRACE,DEBUG,INFO,LOG,WARN,ERROR) to set severity. Defaults toLogLevel.LOG.faro.api.pushError(error: Error, options?: PushErrorOptions)- Report errorsfaro.api.pushMeasurement(measurement: Measurement)- Track performancefaro.api.setUser(user: User)- Identify usersfaro.api.resetUser()- Clear user identification
User Actions API
withFaroUserAction<P>(Component, defaultActionName)- HOC for tracking component interactionstrackUserAction(actionName, context?)- Manual user action tracking
Error Boundary API
FaroErrorBoundary- React component for catching and reporting component errorswithFaroErrorBoundary<P>(Component, errorBoundaryProps)- HOC for wrapping components with error boundary
Transports
Transports control where and how telemetry data is sent.
FetchTransport
Sends telemetry to a remote Faro collector (default):
import { initializeFaro, FetchTransport } from '@grafana/faro-react-native';
initializeFaro({
url: 'https://faro-collector-prod-YOUR-REGION.grafana.net/collect/YOUR_TOKEN_HERE',
app: { name: 'my-app', version: '1.0.0' },
// FetchTransport is automatically configured from the url
});ConsoleTransport
Logs telemetry to the console for debugging (useful during development). Enable via flag; no need to add it to transports:
initializeFaro({
url: 'https://faro-collector-prod-YOUR-REGION.grafana.net/collect/YOUR_TOKEN_HERE',
app: { name: 'my-app', version: '1.0.0' },
enableTransports: {
fetch: true,
console: true, // Logs to Metro with DEBUG level
},
});For custom options (e.g. different log level), add it to transports and set console: false to avoid duplicates:
import { initializeFaro, ConsoleTransport, LogLevel } from '@grafana/faro-react-native';
initializeFaro({
url: '...',
app: { name: 'my-app', version: '1.0.0' },
enableTransports: { fetch: true, console: false },
transports: [new ConsoleTransport({ level: LogLevel.INFO })],
});The ConsoleTransport prints formatted telemetry data to the console, showing:
- All metadata (device info, session, user, etc.)
- Event payloads (logs, errors, events, measurements)
- Structured JSON format for easy inspection
Use Cases:
- Local development and debugging
- Verify instrumentation is working correctly
- Inspect exact structure of events before they reach Grafana
- Test without sending data to production
- Run alongside FetchTransport for dual output
Example Output:
console.debug('New event', {
meta: {
browser: { name: 'iOS', version: '18.0', ... },
session: { id: 'abc123', ... },
...
},
logs: [{ message: 'Hello', level: 'info', ... }]
})Instrumentations
ConsoleInstrumentation- Console loggingErrorsInstrumentation- Error trackingSessionInstrumentation- Session managementViewInstrumentation- View/screen trackingAppStateInstrumentation- App state changesUserActionInstrumentation- User interaction trackingHttpInstrumentation- HTTP request tracking with user action correlationPerformanceInstrumentation- CPU and memory usage monitoringStartupInstrumentation- App startup time tracking
React Components
FaroErrorBoundary- Error boundary component for catching React errorswithFaroErrorBoundary- HOC for wrapping components with error boundary
Future Enhancements
TODO: PerformanceObserver Support
React Native Next (upcoming version) will include native PerformanceObserver support, which will enable greater feature parity with the web SDK's performance monitoring capabilities.
Planned Enhancements:
- Implement
PerformanceObserver-based instrumentation similar to web SDK - Support for performance entry types:
mark,measure,event,longtask - Real-time performance monitoring via observer callbacks
- Better integration with React Native's native performance APIs
- Enhanced performance timeline tracking
References:
- React Native PerformanceObserver API Documentation
- Web SDK Performance Instrumentation:
packages/web-sdk/src/instrumentations/performance/
Current State:
The current implementation uses custom performance utilities (performanceUtils.ts) that provide basic timing and marker functionality. Once React Native's PerformanceObserver is stable, we can migrate to a more comprehensive solution that matches the web SDK's capabilities.
License
Apache-2.0
