Watchlog DocsWatchlog Docs
Home
Get Started
Gen AI Monitoring
Integrations
Log Watchlist
Home
Get Started
Gen AI Monitoring
Integrations
Log Watchlist
  • Watchlog
  • Get Started
  • Custom Events
  • APM
  • Real User Monitoring (RUM)
  • Kubernetes Cluster Monitoring
  • Generative AI Monitoring
  • AI Traces Client Libraries Documentation
  • Browser Synthetic Tests

Real User Monitoring (RUM)

Access the RUM dashboard at https://app.watchlog.io/rum to see real-user traffic, Web Vitals, JavaScript errors, slow resources, and session activity across your applications.


React Icon React

Installation

npm install watchlog-react-rum

Optional: For Web Vitals support (CLS, LCP, INP, TTFB, FID):

npm install web-vitals

Quick Start (Recommended: Hook-based with RouterProvider)

For the most accurate route pattern detection (like Datadog), use the wrapped router functions from watchlog-react-rum/react-router-v6:

// src/main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { RouterProvider } from 'react-router-dom'
import { createBrowserRouter } from 'watchlog-react-rum/react-router-v6'
import { useWatchlogRUM } from 'watchlog-react-rum'
import App from './App'
import Home from './pages/Home'
import UserDetail from './pages/UserDetail'
import PostDetail from './pages/PostDetail'

// Create router with route definitions (for accurate route pattern extraction)
// IMPORTANT: Use createBrowserRouter from 'watchlog-react-rum/react-router-v6'
// This ensures route patterns are captured for accurate normalization
const router = createBrowserRouter([
  {
    path: '/',
    element: <App />,
    children: [
      {
        index: true,
        element: <Home />,
      },
      {
        path: 'users/:userId', // This exact pattern will be used for normalization
        element: <UserDetail />,
      },
      {
        path: 'posts/:postId',
        element: <PostDetail />,
      },
    ],
  },
])

// Root component to initialize RUM tracking
function Root() {
  useWatchlogRUM({
    apiKey: 'YOUR_API_KEY', // Copy from Watchlog panel
    endpoint: 'https://api.watchlog.io/rum', // or your self-hosted endpoint
    app: 'my-react-app', // Your application name (as shown in the dashboard)
    environment: 'production', // optional: 'production' | 'staging' | 'development'
    release: '1.0.0', // optional: release version
    debug: false, // Enable debug logging
    flushInterval: 10000, // Flush interval in milliseconds
    sampleRate: 0.5, // 0.0 to 1.0 - session sampling (max: 0.5 to prevent server overload)
    networkSampleRate: 0.1, // 0.0 to 1.0 - network request sampling (recommended: 0.1)
    interactionSampleRate: 0.1, // 0.0 to 1.0 - user interaction sampling (recommended: 0.1)
    enableWebVitals: true, // Capture LCP/INP/CLS/TTFB (requires web-vitals package)
    captureLongTasks: true, // Capture long tasks (>50ms on main thread)
    captureFetch: true, // Instrument window.fetch
    captureXHR: true, // Instrument XMLHttpRequest
    captureUserInteractions: false, // Set to true to enable click/scroll tracking
    captureBreadcrumbs: true, // Capture event breadcrumbs
    maxBreadcrumbs: 100, // Maximum number of breadcrumbs to keep
    beforeSend: (event) => {
      // Optional: filter or modify events before sending
      // Return null to drop the event
      
      // Example: redact email-like strings from error messages
      if (event.type === 'error' && typeof event.data?.message === 'string') {
        event.data.message = event.data.message.replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+/gi, '[redacted]')
      }
      
      // Example: strip query strings from page URLs
      if (event.context?.page?.url) {
        const u = new URL(event.context.page.url)
        u.search = ''
        event.context.page.url = u.toString()
      }
      
      return event
    }
  })

  return <RouterProvider router={router} />
}

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <Root />
  </React.StrictMode>
)

Important: Using the wrapped createBrowserRouter from watchlog-react-rum/react-router-v6 ensures that route patterns (like /users/:userId or /users/:uuid) are extracted directly from your route definitions, providing the most accurate normalization.

Alternative: BrowserRouter Setup

If you're using BrowserRouter instead of RouterProvider, the SDK will still work but will use params-based route reconstruction:

// src/App.jsx
import { BrowserRouter } from 'react-router-dom'
import { useWatchlogRUM } from 'watchlog-react-rum'

function App() {
  useWatchlogRUM({
    apiKey: 'YOUR_API_KEY',
    endpoint: 'https://api.watchlog.io/rum',
    app: 'my-react-app',
    // ... other config options
  })

  return (
    <BrowserRouter>
      {/* Your app routes */}
    </BrowserRouter>
  )
}

export default App

Manual SDK API (Advanced)

For more control, you can use the SDK directly:

import WatchlogRUM from 'watchlog-react-rum'

// Initialize once at app startup
WatchlogRUM.init({
  apiKey: 'YOUR_API_KEY',
  endpoint: 'https://api.watchlog.io/rum',
  app: 'my-react-app',
  debug: true,
  flushInterval: 10000,
})

// Send custom metric
WatchlogRUM.custom('button_clicked', 1, { extra: 'data' })

// Manually capture errors
WatchlogRUM.captureError(new Error('Something went wrong'), {
  component: 'MyComponent',
  props: { userId: 123 }
})

// Add breadcrumbs
WatchlogRUM.addBreadcrumb('user', 'User clicked button', 'info', {
  buttonId: 'submit'
})

// Flush buffered events (e.g., before manual unload)
WatchlogRUM.flush(true)

Note: When using manual API, you need to manually track route changes:

import { useEffect } from 'react'
import { useLocation } from 'react-router-dom'
import WatchlogRUM from 'watchlog-react-rum'

function MyComponent() {
  const location = useLocation()
  
  useEffect(() => {
    // Manually track page views
    WatchlogRUM.bufferEvent({
      type: 'page_view',
      path: location.pathname,
      normalizedPath: location.pathname, // or compute normalized path
      navType: 'navigate'
    })
  }, [location.pathname])
  
  return <div>...</div>
}

Event Types Collected

RUM automatically captures several event types:

  • session_start — Emitted when a new session begins
  • page_view — Route/page navigations (SPA & traditional)
  • session_end — Emitted when session ends (on unload)
  • web_vital — LCP / INP / CLS / TTFB / FID values, with attribution when available
  • performance — Navigation timing, paint metrics, and resource timing
  • resource — Browser resource timings (duration, size, initiator type)
  • network — Fetch/XHR requests with detailed timing and size information
  • error — JavaScript errors with message, stack, fingerprint and top paths
  • longtask — Long-running JavaScript tasks (>50ms on main thread)
  • interaction — User interactions (clicks, scrolls, form submissions) if enabled
  • custom — Optional, user-defined events (e.g., business actions)

Example payloads

page_view

{
  "type": "page_view",
  "ts": 1730540000123,
  "seq": 1,
  "context": {
    "apiKey": "your-api-key",
    "app": "my-react-app",
    "sessionId": "sess-abc123",
    "deviceId": "dev-xyz789",
    "environment": "production",
    "release": "1.0.0",
    "page": {
      "url": "https://site.com/user/42",
      "path": "/user/42",
      "normalizedPath": "/user/:userId",
      "referrer": "https://google.com",
      "title": "User Profile"
    },
    "client": {
      "userAgent": "Mozilla/5.0 ...",
      "language": "en-US",
      "viewport": { "width": 1920, "height": 1080, "devicePixelRatio": 2 },
      "browser": { "name": "Chrome", "version": "120" },
      "os": { "name": "macOS", "version": "14.0" }
    }
  },
  "data": {
    "name": "page_view",
    "navType": "navigate"
  }
}

web_vital

{
  "type": "web_vital",
  "ts": 1730540000456,
  "seq": 2,
  "context": { /* full context */ },
  "data": {
    "name": "LCP",
    "value": 1835.42,
    "rating": "good",
    "id": "metric-id",
    "delta": 50
  }
}

resource

{
  "type": "resource",
  "ts": 1730540000789,
  "seq": 3,
  "context": { /* full context */ },
  "data": {
    "name": "https://cdn.example.com/app.js",
    "initiator": "script",
    "duration": 342.1,
    "transferSize": 128764,
    "encodedBodySize": 120000,
    "decodedBodySize": 128764
  }
}

error

{
  "type": "error",
  "ts": 1730540000123,
  "seq": 4,
  "context": { /* full context */ },
  "data": {
    "name": "window_error",
    "message": "TypeError: Cannot read properties of undefined",
    "stack": "at MyComponent (App.jsx:42:10)\n  at ...",
    "source": "https://example.com/app.js",
    "filename": "app.js",
    "lineno": 42,
    "colno": 10,
    "component": "MyComponent",
    "props": { "userId": 123 }
  }
}

network

{
  "type": "network",
  "ts": 1730540000123,
  "seq": 5,
  "context": { /* full context */ },
  "data": {
    "method": "POST",
    "url": "https://api.example.com/users",
    "status": 200,
    "ok": true,
    "duration": 150,
    "requestSize": 1024,
    "responseSize": 2048,
    "transferSize": 2500,
    "timing": {
      "dns": 10,
      "tcp": 20,
      "request": 30,
      "response": 50,
      "total": 150
    }
  }
}

Note: You can intercept/modify any event in beforeSend. Return null to drop it entirely.

Configuration Reference

OptionTypeDefaultDescription
appstringrequiredApplication name shown in the dashboard.
apiKeystringrequiredCopy from RUM → Your RUM Credentials in the panel.
endpointstringrequiredCollector endpoint (cloud or self-hosted).
environmentstring"prod"Environment tag (e.g., prod/staging/dev).
releasestringnullRelease version (e.g., '1.0.0').
debugbooleanfalseEnable debug logging.
flushIntervalnumber10000Flush interval in milliseconds.
sampleRatenumber (0.0–1.0)1.0Fraction of sessions to record. Note: Maximum allowed value is 0.5 (50%) to prevent server overload. Values above 0.5 will be automatically capped.
networkSampleRatenumber (0.0–1.0)0.1Sampling for resource/fetch/xhr events. Recommended: 0.1 (10%) for production.
interactionSampleRatenumber (0.0–1.0)0.1Sampling for user interactions (clicks, scrolls). Recommended: 0.1 (10%) for production.
enableWebVitalsbooleantrueCapture LCP/INP/CLS/TTFB/FID (requires web-vitals package).
autoTrackInitialViewbooleantrueSend first page_view on mount.
captureLongTasksbooleantrueCapture long tasks (>50ms on main thread).
captureFetchbooleantrueInstrument window.fetch.
captureXHRbooleantrueInstrument XMLHttpRequest.
captureUserInteractionsbooleanfalseCapture user interactions (clicks, scrolls, forms).
captureBreadcrumbsbooleantrueCapture event breadcrumbs.
maxBreadcrumbsnumber100Maximum number of breadcrumbs to keep.
beforeSend(ev) => ev | nullundefinedMutate, enrich, or drop events before export.

Sample Rate Limits & Best Practices

Session Sample Rate (sampleRate)

To protect server resources and prevent overload, the maximum allowed sampleRate is 0.5 (50%). If you set a value higher than 0.5, it will be automatically capped to 0.5.

Recommended values:

  • Development/Testing: 0.5 (50%) - Full visibility for debugging
  • Production (Low Traffic): 0.3 (30%) - Good balance between data and performance
  • Production (High Traffic): 0.1 (10%) - Efficient data collection without server strain

Why limit sample rate? High sample rates can generate massive amounts of data, leading to:

  • Server overload and potential crashes
  • Increased storage costs
  • Slower query performance
  • Network bandwidth issues

Network Sample Rate (networkSampleRate)

Network requests can be very frequent. We recommend keeping this at 0.1 (10%) or lower for production environments.

Interaction Sample Rate (interactionSampleRate)

User interactions (clicks, scrolls) can be extremely frequent. We recommend 0.1 (10%) or lower for production.

Privacy & Redaction

Use beforeSend to drop sensitive events or redact PII. Consider:

  • Strip query strings or hashes from URLs
  • Remove email addresses, phone numbers, or form values from error messages
  • Lower sampling for high-traffic pages to reduce data volume
  • Filter out internal/admin routes

Troubleshooting

  • No data in the dashboard? Confirm endpoint, apiKey, and app match the panel values.
  • SPAs not tracking route changes? Ensure you're using useWatchlogRUM hook or manually tracking route changes.
  • Routes not normalizing correctly? Use createBrowserRouter from watchlog-react-rum/react-router-v6 for accurate route pattern extraction.
  • Web Vitals missing? Install web-vitals and keep enableWebVitals: true.
  • Browser blocks third-party scripts? Verify CORS and that your collector domain is reachable from clients.
  • Too much data? Lower sampleRate / networkSampleRate, or drop events via beforeSend.

Vue

Works with Vue 3 and Vue Router v4.

Installation

npm install @watchlog/rum-vue

Optional: For Web Vitals support (CLS, LCP, INP, TTFB, FID):

npm install web-vitals

Quick Start (Plugin-based - Recommended)

Use the createWatchlogRUMPlugin helper in your main.js to initialize tracking globally:

// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { createWatchlogRUMPlugin } from '@watchlog/rum-vue'

const app = createApp(App)

app.use(router)
app.use(createWatchlogRUMPlugin({
  router, // Vue Router instance (required for route tracking)
  apiKey: 'YOUR_API_KEY', // Copy from Watchlog panel
  endpoint: 'https://api.watchlog.io/rum', // or your self-hosted endpoint
  app: 'my-vue-app', // Your application name (as shown in the dashboard)
  environment: 'production', // optional: 'production' | 'staging' | 'development'
  release: '1.0.0', // optional: release version
  debug: false, // Enable debug logging
  flushInterval: 10000, // Flush interval in milliseconds
  sampleRate: 0.5, // 0.0 to 1.0 - session sampling (max: 0.5 to prevent server overload)
  networkSampleRate: 0.1, // 0.0 to 1.0 - network request sampling (recommended: 0.1)
  interactionSampleRate: 0.1, // 0.0 to 1.0 - user interaction sampling (recommended: 0.1)
  enableWebVitals: true, // Capture LCP/INP/CLS/TTFB (requires web-vitals package)
  captureLongTasks: true, // Capture long tasks (>50ms on main thread)
  captureFetch: true, // Instrument window.fetch
  captureXHR: true, // Instrument XMLHttpRequest
  captureUserInteractions: false, // Set to true to enable click/scroll tracking
  captureBreadcrumbs: true, // Capture event breadcrumbs
  maxBreadcrumbs: 100, // Maximum number of breadcrumbs to keep
  beforeSend: (event) => {
    // Optional: filter or modify events before sending
    // Return null to drop the event
    
    // Example: redact email-like strings from error messages
    if (event.type === 'error' && typeof event.data?.message === 'string') {
      event.data.message = event.data.message.replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+/gi, '[redacted]')
    }
    
    // Example: strip query strings from page URLs
    if (event.context?.page?.url) {
      const u = new URL(event.context.page.url)
      u.search = ''
      event.context.page.url = u.toString()
    }
    
    return event
  }
}))

app.mount('#app')

This automatically sends:

  1. session_start on first load (with normalized path and referrer)
  2. page_view on every route change
  3. session_end on unload
  4. error for uncaught JS errors, unhandled promise rejections, and Vue component errors
  5. performance metrics on each page load
  6. web_vital metrics (CLS, LCP, INP, TTFB, FID, FCP, FP)
  7. network requests (fetch/XHR) with detailed timing
  8. resource loads (images, scripts, stylesheets, etc.)
  9. longtask events when JavaScript blocks the main thread
  10. interaction events (if enabled)

Alternative: Composable Setup

If you prefer to initialize RUM manually in your root component:

// App.vue (script setup)
<script setup>
import { useWatchlogRUM } from '@watchlog/rum-vue'

const { rum, custom, captureError } = useWatchlogRUM({
  apiKey: 'YOUR_API_KEY',
  endpoint: 'https://api.watchlog.io/rum',
  app: 'my-vue-app',
  debug: true,
  flushInterval: 5000,
  captureUserInteractions: true,
})

// Manually capture errors
const handleError = () => {
  try {
    // your code
  } catch (error) {
    captureError(error, { component: 'MyComponent' })
  }
}

// Send custom events
const handleClick = () => {
  custom('button_clicked', 1, { buttonId: 'submit-btn' })
}
</script>

Manual SDK API (Advanced)

For more control, you can use the SDK directly:

import WatchlogRUM from '@watchlog/rum-vue'

// Initialize once at app startup
WatchlogRUM.init({
  apiKey: 'YOUR_API_KEY',
  endpoint: 'https://api.watchlog.io/rum',
  app: 'my-vue-app',
  debug: true,
  flushInterval: 10000,
})

// Send custom metric
WatchlogRUM.custom('button_clicked', 1, { extra: 'data' })

// Manually capture errors
WatchlogRUM.captureError(new Error('Something went wrong'), {
  component: 'MyComponent',
  props: { userId: 123 }
})

// Add breadcrumbs
WatchlogRUM.addBreadcrumb('user', 'User clicked button', 'info', {
  buttonId: 'submit'
})

// Flush buffered events (e.g., before manual unload)
WatchlogRUM.flush(true)

Event Types Collected

Same as React SDK. RUM automatically captures:

  • session_start — Emitted when a new session begins
  • page_view — Route/page navigations (SPA & traditional)
  • session_end — Emitted when session ends (on unload)
  • web_vital — LCP / INP / CLS / TTFB / FID values
  • performance — Navigation timing, paint metrics, and resource timing
  • resource — Browser resource timings (duration, size, initiator type)
  • network — Fetch/XHR requests with detailed timing and size information
  • error — JavaScript errors with message, stack, fingerprint and top paths
  • longtask — Long-running JavaScript tasks (>50ms on main thread)
  • interaction — User interactions (clicks, scrolls, form submissions) if enabled
  • custom — Optional, user-defined events (e.g., business actions)

What It Sends (Wire Format)

The Vue SDK batches events and POSTs a wrapper to /rum:

{
  "apiKey": "your-api-key",
  "app": "my-vue-app",
  "sdk": "watchlog-rum-vue",
  "version": "0.3.0",
  "sentAt": 1730540000000,
  "sessionId": "sess-abc123",
  "deviceId": "dev-xyz789",
  "environment": "production",
  "release": "1.0.0",
  "events": [
    {
      "type": "page_view",
      "ts": 1730540000123,
      "seq": 1,
      "context": {
        "page": {
          "url": "https://site.com/user/42",
          "path": "/user/42",
          "normalizedPath": "/user/:id",
          "referrer": ""
        },
        "client": {
          "userAgent": "Mozilla/5.0 …",
          "language": "en-US",
          "timezone": "Europe/Amsterdam"
        },
        "app": "my-vue-app",
        "environment": "production",
        "sessionId": "sess-abc123",
        "deviceId": "dev-xyz789"
      },
      "data": {
        "name": "page_view"
      }
    }
  ]
}

The server-side accepts apiKey/app from either headers or wrapper body. Events include context and data (used for Mongo/Influx mapping).

Vue Configuration Reference

Same as React where applicable. Additional notes:

  • app, apiKey, endpoint — required
  • router — required when using createWatchlogRUMPlugin for automatic route tracking
  • environment, release — optional labels shown in dashboards and used for filtering
  • beforeSend — mutate or drop events before export
  • Router detection uses normalized paths (e.g., /user/:id)

Sample Rate Limits & Best Practices

Same as React SDK. See the React section above for detailed recommendations.

Privacy & Redaction

Use beforeSend to drop sensitive events or redact PII. Consider:

  • Strip query strings or hashes from URLs
  • Remove email addresses, phone numbers, or form values from error messages
  • Lower sampling for high-traffic pages to reduce data volume
  • Filter out internal/admin routes

Troubleshooting (Vue)

  • 400 "Missing apiKey or app": Ensure both are present in the wrapper or X-Watchlog-Key header.
  • Empty context/data: You may be using an older version. Update to the latest @watchlog/rum-vue which wraps events and sets context/data per event.
  • No navigations captured: Confirm the router instance is passed to the plugin or useWatchlogRUM is invoked after router creation.
  • Routes not normalizing correctly: Ensure your Vue Router routes use dynamic segments (e.g., /user/:id instead of /user/:userId if your route definition uses :userId).
  • Web Vitals missing: Install web-vitals and keep enableWebVitals: true.

Next steps

  • Open the dashboard: https://app.watchlog.io/rum
  • Copy your Endpoint and API Key from Connect to RUM.
  • Deploy to your staging or production environment.
  • Explore: Overview → Web Vitals → Errors → Resources → Sessions.
Last Updated:: 11/21/25, 12:05 AM
Contributors: mohammad
Prev
APM
Next
Kubernetes Cluster Monitoring