npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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-native

Quick 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 XMLHttpRequest calls when tracing is disabled; with enableTracing: true, enable XHR tracing via tracingOptions.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

  1. User Action Starts: When button is pressed or trackUserAction() is called
  2. Monitor Activity: Tracks HTTP requests started during the action
  3. Detect Completion:
    • If no HTTP requests: Ends after ~100ms
    • If HTTP requests pending: Enters "halt" state
  4. Wait for HTTP: Action stays in halt state until all HTTP requests complete
  5. Auto-End: Once all activity stops, action automatically ends
  6. 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:

  1. Wrap your entire app for global error catching:

    <FaroErrorBoundary fallback={<GlobalErrorScreen />}>
      <App />
    </FaroErrorBoundary>
  2. Wrap critical sections for granular error handling:

    <FaroErrorBoundary fallback={<CheckoutError />}>
      <CheckoutFlow />
    </FaroErrorBoundary>
  3. Use dynamic fallback for better UX:

    fallback={(error, resetError) => (
      <ErrorView error={error} onRetry={resetError} />
    )}
  4. 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: false

Console 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 after sessionExpirationTime (default 4 h) or inactivityTimeout (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-info dependency
  • 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
| avg

For 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]/stat with differential calculation
  • Memory: Parses /proc/[pid]/status for 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:

  1. CPU Usage (app_cpu_usage measurement):

    • cpu_usage - CPU usage percentage (0-100+)
    • Collected periodically based on fetchVitalsInterval
    • First reading establishes baseline (returns 0)
    • Subsequent readings show actual CPU percentage
  2. Memory Usage (app_memory measurement):

    • 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
| avg

Platform 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() with KERN_PROC to get process start time
    • Android: Parses process start time from system

Example Query:

# Average app startup time
{app_name="my-app", kind="measurement"}
| logfmt
| type="app_startup"
| unwrap value_total_duration_ms
| avg

Performance 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 bytes
  • device_memory_used - Currently used memory in bytes
  • device_battery_level - Battery percentage (e.g., "85") - if available
  • device_is_charging - Whether charging ("true" or "false") - if available
  • device_low_power_mode - Low power mode enabled ("true" or "false") - if available
  • device_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"}
| logfmt

Find 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 page meta field to override faro-core's default web-specific page meta
  • Screen tracking is handled via view meta instead of page meta (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 SDK
  • faro.api.pushEvent(name: string, attributes?: Record<string, string>) - Track custom events
  • faro.api.pushLog(message: string[], options?: PushLogOptions) - Send log messages. Use options.level with the LogLevel enum (TRACE, DEBUG, INFO, LOG, WARN, ERROR) to set severity. Defaults to LogLevel.LOG.
  • faro.api.pushError(error: Error, options?: PushErrorOptions) - Report errors
  • faro.api.pushMeasurement(measurement: Measurement) - Track performance
  • faro.api.setUser(user: User) - Identify users
  • faro.api.resetUser() - Clear user identification

User Actions API

  • withFaroUserAction<P>(Component, defaultActionName) - HOC for tracking component interactions
  • trackUserAction(actionName, context?) - Manual user action tracking

Error Boundary API

  • FaroErrorBoundary - React component for catching and reporting component errors
  • withFaroErrorBoundary<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 logging
  • ErrorsInstrumentation - Error tracking
  • SessionInstrumentation - Session management
  • ViewInstrumentation - View/screen tracking
  • AppStateInstrumentation - App state changes
  • UserActionInstrumentation - User interaction tracking
  • HttpInstrumentation - HTTP request tracking with user action correlation
  • PerformanceInstrumentation - CPU and memory usage monitoring
  • StartupInstrumentation - App startup time tracking

React Components

  • FaroErrorBoundary - Error boundary component for catching React errors
  • withFaroErrorBoundary - 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:

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