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:
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:
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;
}
Tracking Consent Management
For GDPR and privacy compliance, manage user tracking consent.
Setting 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);
});
Consent Banner Example
<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
- Use Cases - Practical applications of advanced features
- Best Practices - Production deployment recommendations
- Performance Monitoring - Use advanced features for performance analysis
- Error Tracking - Enhanced error tracking with context