DEV Community

FrankFan
FrankFan

Posted on

The 114KB Span Attribute That Hid Our LCP Data

A React Native WebView debugging story about LCP, data URLs, and trace attributes

We recently hit a confusing Sentry performance issue in a React Native app:

The LCP transaction existed, but Trace Explorer could not find it by the attributes we attached to it.

The culprit was one span attribute:

lcpUrl = data:image/png;base64,...
Enter fullscreen mode Exit fullscreen mode

In one iOS sample, that value was roughly 114KB before Sentry normalized it.

The symptom

We measure WebView page performance by reporting custom Sentry transactions for FCP and LCP:

<page> (FCP)
<page> (LCP)
Enter fullscreen mode Exit fullscreen mode

Both use the same operation:

ui.web_page_load
Enter fullscreen mode Exit fullscreen mode

And both include attributes we use for grouping and filtering:

metricInfo
pageTitle
pageUrl
host
path
durationMs
Enter fullscreen mode Exit fullscreen mode

The strange part: Sentry Transaction Summary could show the LCP transaction, but Trace Explorer could not find the same data when filtering by attributes such as metricInfo, pageTitle, or path.

FCP worked. Android worked. iOS LCP did not.

The raw event told the story

After pulling the raw event from the Sentry API, the LCP transaction was clearly there. The transaction name was correct.

But the trace attributes were not complete. Some diagnostic fields were present:

durationMs
host
lcpElement
lcpUrl
Enter fullscreen mode Exit fullscreen mode

But several fields required for dashboard queries were missing:

metricInfo
pageTitle
pageUrl
path
Enter fullscreen mode Exit fullscreen mode

The suspicious field was lcpUrl. On iOS, it was not a normal URL. It was a base64 image data URI:

data:image/png;base64,...
Enter fullscreen mode Exit fullscreen mode

Sentry marked the oversized value as limited. After that, the event still existed, but the attributes we depended on for aggregation were not queryable in the way we expected.

That explains the apparent contradiction:

  • Transaction Summary could still find the transaction by name.
  • Trace Explorer could not find it by the missing attributes.

Why Android looked fine

This part was easy to misread. The Android data looked healthy, so it was tempting to assume the instrumentation was fine.

It was not.

In our production samples, the same lcpUrl field looked very different by platform:

Platform lcpUrl length Attribute query fields
iOS WebView about 114KB missing
Android WebView 100 characters present

To isolate the difference, we built a small WebView test page with a large base64 image as the LCP candidate. The full image data URI was about 1.6MB.

On Android, the DOM and bridge could still carry the full string, but the LCP entry itself exposed only a 100-character URL:

DOM img.src.length            = about 1.6MB
Android bridge received value = about 1.6MB
PerformanceObserver entry.url = 100 characters
Enter fullscreen mode Exit fullscreen mode

So Android was not safe because our telemetry model was good. It was safe because Chromium WebView had already returned a short value for entry.url before we sent it to Sentry.

iOS Safari WebView returned the full data URI. That may be a reasonable browser behavior, but it was operationally dangerous for telemetry.

I would treat the Android behavior as an implementation detail, not a contract. Application code should not rely on a browser silently shortening a dangerous value.

The fix

We removed lcpUrl and lcpElement from the Sentry span attributes.

Before:

reportWebSpan("LCP", lcpValue, {
  lcpElement,
  lcpUrl,
});
Enter fullscreen mode Exit fullscreen mode

After:

reportWebSpan("LCP", lcpValue);
Enter fullscreen mode Exit fullscreen mode

We kept only small, stable attributes that are useful for grouping:

metricInfo
pageTitle
pageUrl
host
path
durationMs
Enter fullscreen mode Exit fullscreen mode

After removing the oversized fields, LCP appeared correctly in the dashboard query again.

Lessons learned

Observability data needs stricter rules than ordinary application data.

A field is not safe just because the browser exposes it. Before sending it as a span attribute, ask:

  • Is it bounded in size?
  • Is it low-cardinality?
  • Does it help answer a real production question?
  • Could one bad value make the event harder to index or query?

For WebView performance telemetry, these are good attributes:

metricInfo = LCP
pageTitle = <stable page name>
host = example.com
path = /some/path
durationMs = 3020
Enter fullscreen mode Exit fullscreen mode

These are dangerous attributes:

lcpUrl = data:image/png;base64,...
outerHTML = <large DOM subtree>
requestBody = ...
Enter fullscreen mode Exit fullscreen mode

The dashboard was not wrong. The telemetry model was wrong.

And in this case, deleting two fields made the metric visible again.

Top comments (0)