Skip to content

Advanced Features

This guide covers advanced RUM features for power users who want to get the most out of OpenObserve RUM monitoring.

Global Context Management

Global context allows you to attach custom attributes to all RUM events. This is useful for tracking business context, feature flags, user segments, and other application-specific data.

Setting Global Context

Set the entire global context at once:

openobserveRum.setGlobalContext({
  environment: 'production',
  deployment_version: 'v2.5.1',
  data_center: 'us-east-1',
  customer_tier: 'enterprise'
});

Setting Individual Properties

Add or update individual properties without replacing the entire context:

openobserveRum.setGlobalContextProperty('subscription_plan', 'premium');
openobserveRum.setGlobalContextProperty('feature_new_ui', true);
openobserveRum.setGlobalContextProperty('account_age_days', 45);

Getting Global Context

Retrieve the current global context:

const context = openobserveRum.getGlobalContext();
console.log(context);
// {
//   environment: 'production',
//   deployment_version: 'v2.5.1',
//   subscription_plan: 'premium',
//   ...
// }

Removing Properties

Remove a property from the global context:

openobserveRum.removeGlobalContextProperty('temporary_flag');

Use Cases

Feature Flags

Track which features are enabled for each user:

openobserveRum.setGlobalContext({
  feature_flags: {
    new_checkout: true,
    dark_mode: false,
    beta_features: true
  }
});

Then analyze performance or errors by feature flag: - "Are users with new_checkout experiencing more errors?" - "Is dark_mode affecting performance?"

A/B Testing

Track experiment variants:

openobserveRum.setGlobalContextProperty('experiment_checkout_flow', 'variant_b');
openobserveRum.setGlobalContextProperty('experiment_homepage', 'control');

Analyze results: - Compare conversion rates by variant - Check error rates across variants - Measure performance impact of experiments

Business Context

Add business-relevant context:

openobserveRum.setGlobalContext({
  account_type: 'business',
  subscription_tier: 'professional',
  monthly_spend: 'high',
  industry: 'fintech',
  company_size: '100-500'
});

Segment analysis by business metrics: - "Do enterprise customers have better performance?" - "Are fintech customers experiencing more errors?"

Technical Context

Add deployment and infrastructure context:

openobserveRum.setGlobalContext({
  deployment_id: 'deploy-2024-123',
  build_number: '1.2.345',
  canary_release: true,
  feature_branch: 'main'
});

Track deployment impact: - "Did the new deployment cause errors?" - "Is the canary release performing well?"

Best Practices

Keep it lean: Don't add too many properties (recommended < 20)

Use consistent naming: Use snake_case or camelCase consistently

Don't include sensitive data: Never add passwords, credit cards, or PII

Update when context changes: Update context when user changes state

Clean up on logout: Remove user-specific context when user logs out

function onLogout() {
  openobserveRum.clearUser();
  openobserveRum.setGlobalContext({}); // Clear user-specific context
  // Keep application-level context
  openobserveRum.setGlobalContextProperty('deployment_version', 'v2.5.1');
}

View Context Management

View context allows you to add context specific to the current view/page. This is especially useful for Single Page Applications (SPAs).

Setting View Name

Override the automatic view name for better organization:

// Automatic: "https://example.com/products?category=electronics"
// Custom: "Product Listing - Electronics"
openobserveRum.setViewName('Product Listing - Electronics');

Setting View Context

Add view-specific context that applies only to the current view:

openobserveRum.setViewContext({
  page_type: 'product_listing',
  category: 'electronics',
  filters_active: true,
  sort_order: 'price_asc',
  results_count: 42
});

Setting Individual View Properties

Update individual properties:

openobserveRum.setViewContextProperty('results_count', 156);
openobserveRum.setViewContextProperty('filters_active', false);

Getting View Context

Retrieve current view context:

const viewContext = openobserveRum.getViewContext();
console.log(viewContext);

SPA View Tracking

For Single Page Applications, manually track views when routes change:

React Example with React Router

import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { openobserveRum } from '@openobserve/browser-rum';

function App() {
  const location = useLocation();

  useEffect(() => {
    // Set custom view name based on route
    const routeNames = {
      '/': 'Home',
      '/products': 'Product Listing',
      '/product/:id': 'Product Details',
      '/cart': 'Shopping Cart',
      '/checkout': 'Checkout'
    };

    const viewName = routeNames[location.pathname] || location.pathname;
    openobserveRum.setViewName(viewName);

    // Add view-specific context
    openobserveRum.setViewContext({
      route: location.pathname,
      search: location.search
    });
  }, [location]);

  return <div>{/* App content */}</div>;
}

Vue Example with Vue Router

import { watch } from 'vue';
import { useRoute } from 'vue-router';
import { openobserveRum } from '@openobserve/browser-rum';

export default {
  setup() {
    const route = useRoute();

    watch(
      () => route.path,
      (newPath) => {
        // Set custom view name
        const viewName = route.meta?.title || route.name || newPath;
        openobserveRum.setViewName(viewName);

        // Add view context
        openobserveRum.setViewContext({
          route_name: route.name,
          route_params: route.params,
          route_meta: route.meta
        });
      },
      { immediate: true }
    );
  }
};

Use Cases

E-commerce Product Pages

// On product page load
openobserveRum.setViewName('Product Details - ' + product.name);
openobserveRum.setViewContext({
  product_id: product.id,
  product_category: product.category,
  product_price: product.price,
  in_stock: product.inStock,
  related_products_count: product.related.length
});

Dashboard Views

// On dashboard load
openobserveRum.setViewName('Analytics Dashboard');
openobserveRum.setViewContext({
  dashboard_type: 'analytics',
  time_range: '30d',
  widgets_count: 8,
  data_loaded: true
});

Search Results

// On search results
openobserveRum.setViewName('Search Results');
openobserveRum.setViewContext({
  search_query: sanitizeQuery(query),
  results_count: results.length,
  search_type: 'full_text',
  filters_applied: filters.length
});

Custom Timings

Custom timings allow you to measure application-specific operations that aren't captured by standard Web Vitals.

Adding Custom Timings

// Measure specific operation
const startTime = Date.now();

// ... perform operation ...

openobserveRum.addTiming('data_processing_complete', Date.now() - startTime);

Use Cases

API Data Loading

async function loadUserData() {
  const start = Date.now();

  try {
    const data = await fetch('/api/user/profile');
    const elapsed = Date.now() - start;

    openobserveRum.addTiming('user_profile_load_time', elapsed);

    return data;
  } catch (error) {
    openobserveLogs.logger.error('Failed to load user data', { error });
  }
}

Feature Initialization

async function initializeApp() {
  const initStart = Date.now();

  // Initialize various features
  await initAuth();
  openobserveRum.addTiming('auth_init_time', Date.now() - initStart);

  const configStart = Date.now();
  await loadConfig();
  openobserveRum.addTiming('config_load_time', Date.now() - configStart);

  const dataStart = Date.now();
  await loadInitialData();
  openobserveRum.addTiming('initial_data_load_time', Date.now() - dataStart);

  openobserveRum.addTiming('total_init_time', Date.now() - initStart);
}

User Journey Milestones

// Checkout flow timing
function trackCheckoutMilestone(step) {
  const stepTimes = {
    'cart_view': Date.now(),
    'shipping_info': Date.now(),
    'payment_info': Date.now(),
    'order_review': Date.now(),
    'order_complete': Date.now()
  };

  openobserveRum.addTiming(`checkout_${step}_reached`, stepTimes[step]);
}

// Usage
trackCheckoutMilestone('cart_view');
// ... user fills shipping info ...
trackCheckoutMilestone('shipping_info');

Heavy Computation

function processLargeDataset(data) {
  const start = performance.now();

  // Heavy processing
  const result = data.map(item => {
    // Complex calculations
    return transform(item);
  });

  const duration = performance.now() - start;
  openobserveRum.addTiming('dataset_processing_time', duration);

  return result;
}

For GDPR and privacy compliance, manage user tracking consent.

// User granted consent
openobserveRum.setTrackingConsent('granted');

// User denied consent
openobserveRum.setTrackingConsent('not-granted');

Before Initialization

Set consent before initializing RUM:

import { openobserveRum } from '@openobserve/browser-rum';

// Check stored consent preference
const hasConsent = localStorage.getItem('tracking_consent') === 'true';

// Initialize with tracking consent
openobserveRum.init({
  // ... config
  trackingConsent: hasConsent ? 'granted' : 'not-granted'
});

After Initialization

Update consent when user changes preference:

function handleConsentChange(userGrantedConsent) {
  const consent = userGrantedConsent ? 'granted' : 'not-granted';

  // Update RUM tracking consent
  openobserveRum.setTrackingConsent(consent);

  // Store preference
  localStorage.setItem('tracking_consent', userGrantedConsent);
}

// In your consent banner
document.getElementById('accept-cookies').addEventListener('click', () => {
  handleConsentChange(true);
});

document.getElementById('decline-cookies').addEventListener('click', () => {
  handleConsentChange(false);
});
<div id="consent-banner" style="display: none;">
  <p>We use cookies and tracking to improve your experience.</p>
  <button id="accept-tracking">Accept</button>
  <button id="decline-tracking">Decline</button>
</div>

<script>
// Show banner if no consent decision made
if (!localStorage.getItem('tracking_consent')) {
  document.getElementById('consent-banner').style.display = 'block';
}

document.getElementById('accept-tracking').addEventListener('click', () => {
  openobserveRum.setTrackingConsent('granted');
  localStorage.setItem('tracking_consent', 'granted');
  document.getElementById('consent-banner').style.display = 'none';
});

document.getElementById('decline-tracking').addEventListener('click', () => {
  openobserveRum.setTrackingConsent('not-granted');
  localStorage.setItem('tracking_consent', 'not-granted');
  document.getElementById('consent-banner').style.display = 'none';
});
</script>

How It Works

  • When consent is granted: RUM collects and sends all data
  • When consent is not-granted: RUM stops collecting and sending data
  • Buffered data is discarded when consent is not granted
  • Setting to granted will resume data collection going forward

Advanced Sampling Strategies

Control which sessions are tracked and recorded to optimize costs and performance.

Basic Sampling

Set overall session sampling and replay sampling rates:

openobserveRum.init({
  // ... other config
  sessionSampleRate: 100, // Track 100% of sessions
  sessionReplaySampleRate: 20 // But only record 20% of sessions
});

Conditional Sampling

Sample based on user properties or conditions:

// Sample more for paid users, less for free users
const user = getCurrentUser();
const sampleRate = user.isPaid ? 100 : 10;

openobserveRum.init({
  // ... other config
  sessionSampleRate: sampleRate
});

Route-Based Sampling

Record more sessions on critical pages:

import { openobserveRum } from '@openobserve/browser-rum';

// Initialize with low sampling
openobserveRum.init({
  // ... config
  sessionReplaySampleRate: 10 // 10% default
});

// Force recording on critical pages
if (window.location.pathname.includes('/checkout')) {
  openobserveRum.startSessionReplayRecording({ force: true });
}

if (window.location.pathname.includes('/payment')) {
  openobserveRum.startSessionReplayRecording({ force: true });
}

Error-Based Recording

Record session replay only when errors occur:

openobserveRum.init({
  // ... config
  sessionSampleRate: 100, // Track all sessions
  sessionReplaySampleRate: 0 // Don't record by default
});

// Start recording when error occurs
window.addEventListener('error', () => {
  openobserveRum.startSessionReplayRecording({ force: true });
});

window.addEventListener('unhandledrejection', () => {
  openobserveRum.startSessionReplayRecording({ force: true });
});

Time-Based Sampling

Sample differently based on time of day:

function getSampleRate() {
  const hour = new Date().getHours();

  // Higher sampling during business hours (9am - 5pm)
  if (hour >= 9 && hour <= 17) {
    return 50; // 50% during business hours
  }

  return 10; // 10% otherwise
}

openobserveRum.init({
  // ... config
  sessionReplaySampleRate: getSampleRate()
});

Cost Optimization Strategy

Recommended sampling strategy for cost optimization:

const user = getCurrentUser();
const isProduction = process.env.NODE_ENV === 'production';
const isCriticalPage = ['/checkout', '/payment', '/signup'].some(path =>
  window.location.pathname.includes(path)
);

let sessionSampleRate = 100; // Always track sessions
let replaySampleRate = 100;  // Default replay rate

if (isProduction) {
  if (isCriticalPage) {
    replaySampleRate = 100; // Record all critical page sessions
  } else if (user && user.isPaid) {
    replaySampleRate = 50; // Record 50% of paid user sessions
  } else {
    replaySampleRate = 10; // Record 10% of free user sessions
  }
} else {
  // Non-production: record everything
  replaySampleRate = 100;
}

openobserveRum.init({
  // ... other config
  sessionSampleRate,
  sessionReplaySampleRate: replaySampleRate
});

beforeSend Hook

The beforeSend hook allows you to intercept, modify, or discard events before they're sent to OpenObserve. This is powerful for data sanitization, filtering, and enrichment.

Basic Usage

openobserveRum.init({
  // ... other config
  beforeSend: (event) => {
    // Modify event
    event.context.custom_field = 'value';

    // Return event to send it
    return event;

    // Return false/undefined to discard event
    // return false;
  }
});

Use Cases

Sanitizing Sensitive Data

Remove sensitive information from URLs and errors:

openobserveRum.init({
  // ... config
  beforeSend: (event) => {
    // Remove query parameters from URLs
    if (event.view?.url) {
      event.view.url = event.view.url.split('?')[0];
    }

    // Sanitize error messages
    if (event.error?.message) {
      event.error.message = event.error.message
        .replace(/email=[^&]+/g, 'email=REDACTED')
        .replace(/token=[^&]+/g, 'token=REDACTED')
        .replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, 'EMAIL_REDACTED');
    }

    return event;
  }
});

Filtering Out Specific Errors

Ignore known or irrelevant errors:

openobserveRum.init({
  // ... config
  beforeSend: (event) => {
    if (event.type === 'error') {
      const message = event.error?.message || '';

      // Ignore browser extension errors
      if (message.includes('chrome-extension://')) {
        return false;
      }

      // Ignore third-party script errors
      if (event.error?.stack?.includes('google-analytics')) {
        return false;
      }

      // Ignore specific known errors
      const ignoredErrors = [
        'ResizeObserver loop limit exceeded',
        'Non-Error promise rejection captured',
        'Script error.' // Cross-origin script errors
      ];

      if (ignoredErrors.some(ignored => message.includes(ignored))) {
        return false;
      }
    }

    return event;
  }
});

Adding Custom Context

Enrich events with additional data:

openobserveRum.init({
  // ... config
  beforeSend: (event) => {
    // Add runtime information
    event.context = event.context || {};
    event.context.memory_usage = performance.memory?.usedJSHeapSize;
    event.context.connection_type = navigator.connection?.effectiveType;
    event.context.battery_level = navigator.getBattery?.()?.level;

    // Add business context
    const cart = getShoppingCart();
    if (cart) {
      event.context.cart_items = cart.length;
      event.context.cart_value = cart.total;
    }

    return event;
  }
});

Rate Limiting Events

Limit the number of certain event types:

const eventCounts = {};
const EVENT_LIMITS = {
  'error': 100,      // Max 100 errors per session
  'action': 1000,    // Max 1000 actions per session
  'resource': 500    // Max 500 resource events per session
};

openobserveRum.init({
  // ... config
  beforeSend: (event) => {
    const eventType = event.type;

    // Initialize counter
    if (!eventCounts[eventType]) {
      eventCounts[eventType] = 0;
    }

    // Check limit
    const limit = EVENT_LIMITS[eventType];
    if (limit && eventCounts[eventType] >= limit) {
      console.warn(`Event limit reached for ${eventType}`);
      return false; // Discard event
    }

    // Increment counter
    eventCounts[eventType]++;

    return event;
  }
});

Sampling by Event Type

Apply different sampling rates to different event types:

openobserveRum.init({
  // ... config
  beforeSend: (event) => {
    // Sample different event types at different rates
    const samplingRates = {
      'error': 1.0,      // 100% of errors
      'resource': 0.1,   // 10% of resource events
      'action': 0.5,     // 50% of user actions
      'view': 1.0        // 100% of view events
    };

    const rate = samplingRates[event.type] || 1.0;

    // Random sampling
    if (Math.random() > rate) {
      return false; // Discard event
    }

    return event;
  }
});

Categorizing Errors

Add error categories for better organization:

openobserveRum.init({
  // ... config
  beforeSend: (event) => {
    if (event.type === 'error') {
      const message = event.error?.message || '';
      const stack = event.error?.stack || '';

      // Categorize errors
      let category = 'unknown';
      let severity = 'medium';

      if (message.includes('Network') || message.includes('fetch')) {
        category = 'network';
        severity = 'high';
      } else if (message.includes('TypeError') || message.includes('ReferenceError')) {
        category = 'javascript';
        severity = 'high';
      } else if (stack.includes('node_modules')) {
        category = 'third_party';
        severity = 'low';
      } else if (message.includes('timeout')) {
        category = 'performance';
        severity = 'medium';
      }

      // Add category to event
      event.context = event.context || {};
      event.context.error_category = category;
      event.context.error_severity = severity;
    }

    return event;
  }
});

Debug Mode

Log events in development without sending them:

const isDevelopment = process.env.NODE_ENV === 'development';

openobserveRum.init({
  // ... config
  beforeSend: (event) => {
    if (isDevelopment) {
      console.log('[RUM Event]', event.type, event);
      // Optionally don't send in development
      // return false;
    }

    return event;
  }
});

Best Practices

Keep it fast: The beforeSend hook runs synchronously - avoid heavy operations

Don't throw errors: Errors in beforeSend will discard the event

Test thoroughly: Ensure you're not accidentally discarding important events

Document your filters: Keep track of what you're filtering and why

Monitor discarded events: Log when you discard events for debugging

openobserveRum.init({
  // ... config
  beforeSend: (event) => {
    try {
      // Your logic here
      if (shouldDiscard(event)) {
        console.debug('RUM event discarded:', event.type, event);
        return false;
      }

      return event;
    } catch (error) {
      console.error('Error in beforeSend hook:', error);
      return event; // Send event despite error
    }
  }
});

Next Steps