Skip to main content

Per-user Sampling

By default every Scout RUM SDK samples each session independently at sessionSampleRate, so one user ends up with some sessions recorded and others dropped. Per-user sampling lets your app make that decision for each user instead, typically from its own remote config or feature-flag service: pass true to record all of a user's sessions, false to stop recording them, or null to keep the default per-session roll.

Running this in production

Storing and querying these sessions at production volume is what base14 Scout does. Check out Scout RUM.

Requirements​

SDKMinimum version
Androidio.base14:scout-android:0.1.12
iOSios-0.1.12 (Swift Package)
Kotlin Multiplatformio.base14:scout-kmp:0.1.14
Flutterscout_flutter 0.4.0
React Native and React web@base-14/scout-react 0.1.22

What each value does​

ValueSpansLogs and metricsErrors, crashes, ANRs
trueSent for every sessionSentSent
falseDroppedStill sentStill sent while alwaysCaptureErrors is on (the default)
null (default)Decided by sessionSampleRate, as beforeFollow sessionSampleRateFollow alwaysCaptureErrors

false is not a full kill switch. A user set to false stops producing screen views, network calls, interactions, and other spans, but their logs, metrics, and error-class spans keep arriving, so you can still debug a failure for that user. Their logs carry session.sampled=false.

When a value takes effect​

The SDK reads the value once, when a session starts, and keeps that decision for the whole session. A new value never changes the session in progress; it applies at the next session start, which happens on:

  • an inactivity timeout (sessionTimeoutMinutes, 30 by default)
  • the maximum session length (maxSessionDurationMinutes, 60 by default)
  • the next app launch, when the previous session has expired
  • an account switch or logout (see Account switches and logout)

The latest value is persisted, so a launch that passes nothing at initialization still honors it. A value passed at initialization replaces the stored one, and a null passed at initialization means "no opinion" and keeps the stored value.

Set it up​

The pattern is the same on every platform:

  1. At initialization, pass the value you last fetched for the signed-in user. Remote config SDKs keep this cached locally, so it is available synchronously at startup.
  2. On login, fetch the user's value, call setUserSampling(value), and then call setUser(id). Calling them in this order makes an account switch start the new user's session with the new user's value.
  3. On logout, call clearUser(). It also clears the per-user value.
// Application.onCreate()
Scout.initialize(
this,
ScoutConfig(
serviceName = "my-app",
endpoint = "https://otel.example.com",
userSampling = remoteConfig.cachedRumEnabled(), // Boolean? (null = no opinion)
),
)

// After login
Scout.setUserSampling(remoteConfig.fetchRumEnabled(user.id))
Scout.setUser(user.id)

// On logout
Scout.clearUser()

Account switches and logout​

While per-user sampling is in use, an identity change starts a new session so that one user never inherits another user's decision:

  • setUser with an id different from the current one starts a new session.
  • clearUser() clears the per-user value and starts a new session, which falls back to sessionSampleRate.
  • Going from anonymous to the first setUser call keeps the current session, and so does calling setUser again with the same id.

Apps that never pass a per-user value see no extra sessions: identity changes only start a new session when the current session was decided by a per-user value, or a per-user value is pending.

Hybrid apps and WebViews​

  • Native host embedding Flutter. The native SDK owns the session, so it makes the decision. ScoutFlutter.setUserSampling, setUser, and clearUser are forwarded to it, and the Flutter layer follows its decision. On Android, a Flutter screen in a separate process follows the main process's decision; set the value in the main process. See Hybrid (Native + Flutter).
  • WebView pages. A page that adopts the host app's session in session-only mode also follows the host's decision, so a user set to false stops the page's spans too. The Flutter WebView bridge passes this automatically; other hosts pass sampled and samplingSource to Scout.setWebViewBridge(...). Pages need @base-14/scout-react 0.1.22 or later; older pages ignore the fields.

Verify it in Scout​

Every span and log carries session.sampling_source:

  • user means a per-user value decided the session.
  • fallback means sessionSampleRate decided it.

Open a session in RUM Sessions and read session.sampling_source next to session.sampled. For a user set to false, expect session.sampled=false and session.sampling_source=user, error spans and logs present, and no screen views or network calls.

FAQ​

Does setting a user to false stop all of their telemetry?​

Setting a user to false drops only their spans; logs, metrics, and error-class spans (errors, crashes, ANRs, app hangs) keep flowing, so failures stay visible. Set alwaysCaptureErrors to false if you also want to drop their error spans.

Why didn't a new value apply right away?​

The decision is fixed for the life of a session, so a new value applies from the next session start: after the inactivity timeout, the maximum session length, the next launch, or an account switch or logout.

What happens if my app never calls setUserSampling?​

If your app never calls setUserSampling, every session is sampled at sessionSampleRate as before, and identity changes do not start extra sessions.

Do users set to false still count in RUM dashboards?​

They count in metric-based charts, such as web vitals and other histograms, because their metrics are still sent. They do not appear in span-based views such as sessions, screens, and network calls.

Was this page helpful?