Client-Side Backgrounding Causes Inflated Span Durations

Last updated: June 24, 2026

If you're seeing spans from mobile or browser clients with durations far longer than expected (minutes or even hours instead of milliseconds), the most likely cause is app or tab backgrounding.

This affects all client-side tracing (React Native, browser JavaScript, and other mobile SDKs).

What's happening

When a mobile app goes to the background (the user switches apps or the device sleeps) or a browser tab becomes hidden, the JavaScript runtime is suspended. But the clocks used to measure span duration keep ticking.

If a span started before the app was backgrounded and ends after it comes back to the foreground, the recorded duration includes all the inactive time. The span wasn't actually "running" for 50 minutes. The clock just kept going while the app was frozen.

This is a general property of how clocks work on every client platform (iOS, Android, and web browsers), not something specific to Honeycomb or OpenTelemetry.

How to confirm this is the cause

You can add client lifecycle state as a span attribute, then check whether your long-duration spans correlate with backgrounding.

React Native: capture AppState at span start and end using a custom SpanProcessor:

import { AppState } from 'react-native';

// In a custom SpanProcessor
onStart(span) {
  span.setAttribute('app.state.start', AppState.currentState);
}
onEnd(span) {
  span.setAttribute('app.state.end', AppState.currentState);
}

Browser: capture document.visibilityState at span start and end:

// In a custom SpanProcessor
onStart(span) {
  span.setAttribute('page.visibility.start', document.visibilityState);
}
onEnd(span) {
  span.setAttribute('page.visibility.end', document.visibilityState);
}

Once you're sending these attributes, query in Honeycomb and group by app.state.end or page.visibility.end. Long-duration spans should correlate with background/inactive (mobile) or hidden (browser).

What you can do about it

  • Filter them out at query time: Add the lifecycle attributes above and exclude backgrounded spans in your queries, dashboards, and SLOs. For example, add a WHERE clause like page.visibility.end != hidden. This is the simplest approach and preserves all your data.

  • Exclude them from SLOs and triggers: If your SLO measures client-side latency (like P99 fetch duration), add a filter to exclude spans where the app was backgrounded. This prevents inflated durations from skewing your burn rate or triggering false alerts.

  • Drop or truncate them with a custom SpanProcessor: You can write a SpanProcessor that checks lifecycle state and either drops the span entirely or caps its duration. This gives you the cleanest data, but you lose visibility into what happened across background transitions.

Related docs