Skip to content

Real User Monitoring

Real User Monitoring (RUM) records what real users experience in their browser or on their phone: how fast your pages load, how quickly they react to a click, which errors and crashes they hit, and which backend request each page or screen made. It lives under Frontend in the sidebar (/frontend/applications).

The rest of FuseScope sees requests from the server inward. RUM adds the other end: the time spent in the browser or the app, and the errors your servers never see.

Platform SDK Status Set up
Web @fusescope/browser (npm) Beta Set up a web application
Android io.fuseone.fusescope:android (Maven Central), Java and Kotlin Beta Set up an Android application
iOS Planned

An application has one platform, chosen when you create it. The screens are the same for both; the differences are noted where they matter. On Android, a view is a screen (an Activity, a Fragment or a Compose destination), errors include crashes and ANRs (the app not responding), and stack traces are retraced with R8 mappings instead of source maps. Native (NDK) crashes are not captured yet; they are planned.

Use RUM when users say “the site is slow” and the backend looks healthy, when you want to know whether a release made pages slower, or when you need the backend trace behind one user’s failing click.

  • Sessions. A session is one user’s visit, from the first page until 15 minutes without a click, key press, scroll or touch, or 4 hours at most. All tabs of one browser share it. On Android, a session ends after 15 minutes in the background, or at 4 hours. Every span and event carries its session.id, and a session that follows an expired one carries its id as session.previous_id. See Sessions.
  • Views. A view is one page or screen the user saw, named by its route template, such as /product/:id, so /product/1234 and /product/99 count as one route. The view the page was loaded on is a page load; every later view of a single-page app is a route change. Each view records how long the user stayed on it, the time on view. See Views.
  • Web Vitals. Google’s standard measures of page experience, explained below.
  • Errors. Uncaught JavaScript errors and unhandled promise rejections on the web; crashes and ANRs on Android; and errors you report yourself. Each has its type, message, stack trace and the route it happened on, grouped so one bug is one row. With your source maps or R8 mappings uploaded, stack traces show your original code. See Errors.
  • App start (Android). Cold, warm and hot start times, rated with Android vitals’ thresholds, shown as p75 on the overview.
  • Clicks. Each click records which element was clicked, as a short selector such as button#checkout, never its text.
  • Requests linked to backend traces. Each fetch or XMLHttpRequest call in the browser, and each OkHttp request on Android, is a span. The SDK adds the W3C traceparent header to requests to your backend, so the backend’s trace becomes a child of the browser’s request. From a session, a route or an error, one click opens the backend trace in Trace details.

RUM data is stored as ordinary traces and logs, so the Traces explorer, the Logs explorer, dashboards and alert rules work on it. Filter by fusescope.rum.application.id to see one application.

Web Vitals are measured on each page load. FuseScope shows the 75th percentile (p75): three loads in four were at least this good. Each value is rated good, needs improvement or poor with Google’s thresholds:

Vital What it measures Good Needs improvement Poor
LCP, Largest Contentful Paint How long until the largest image or block of text is on screen: when the page looks loaded. ≤ 2.5 s ≤ 4 s > 4 s
INP, Interaction to Next Paint How long the page takes to show a reaction after a click, tap or key press. The slowest interactions of the visit count. ≤ 200 ms ≤ 500 ms > 500 ms
CLS, Cumulative Layout Shift How much the content jumps around while it loads, such as a button moving as an image appears. It has no unit. ≤ 0.1 ≤ 0.25 > 0.25

The SDK also records FCP (First Contentful Paint, the first text or image) and TTFB (Time to First Byte, how long the server took to start answering). Each route’s detail shows their p75.

RUM is designed so that no personal data is collected unless you decide otherwise.

  • Query strings and fragments are dropped from URLs, span names and error messages: /search?q=boots#top becomes /search. Keep named parameters, such as page, with an allowlist. The SDK does this in the browser, and the FuseScope gateway does it again on the server for URL attributes and span names, so a client that skips it is still covered.
  • No form input or typed text is ever captured. Clicks record the element’s tag and a CSS selector, not its text.
  • The client IP is not stored. The gateway uses it in memory for the rate limit only.
  • Consent hook. With Wait for consent on, the SDK records nothing until your consent banner calls setConsent(true).
  • Users are anonymous unless you call setUser(). The id you pass is then sent in user.id as its SHA-256 hash, unless you turn hashing off. You can find a user’s sessions by hashing their id the same way, without FuseScope storing who they are.

The application’s settings say which of these the server enforces and which the SDK applies; see Set up a web application. The Android SDK applies the same defaults and sends no persistent device identifier; see Privacy defaults.

FuseScope publishes a browser SDK, @fusescope/browser, a thin distribution of OpenTelemetry JS, and an Android SDK, io.fuseone.fusescope:android, a thin distribution of OpenTelemetry Android. Both add sessions, views, privacy scrubbing and the attributes the RUM screens read. It is the tested and recommended path, but it is not required. FuseScope accepts any OpenTelemetry client that sends OTLP to the RUM endpoints with an application key, and never checks which SDK sent the data.

A client configured by hand only needs a few attributes for the screens to fill: session.id, fusescope.rum.view.route, spans named view, and exception and browser.web_vital events with an event.name attribute; an Android client also needs app.package.name. See Use another OpenTelemetry client.