Skip to content

Structured **log** timestamps are wrong on React Native — logs use unchecked performance.timeOrigin + performance.now() instead of Date.now() #6510

Description

@bgbahoue

What React Native libraries do you use?

Expo Router, Expo (mobile only)

Are you using sentry.io or on-premise?

sentry.io (SaS)

Are you using any other error monitoring solution alongside Sentry?

Yes

Other Error Monitoring Solution Name

Expo Observe

@sentry/react-native SDK Version

8.19.0 and test 8.20.0

How does your development environment look like?

  • @sentry/react-native 8.19.0 (observed) and 8.20.0 (confirmed still affected)
  • @sentry/core 10.65.010.67.0 (both affected; source identical)
  • React Native 0.86.0 (Hermes), Expo SDK 57, React 19.2.3
  • Device: iPhone, iOS 26.5.2 (device clock correct / set to automatic)

Sentry.init()

Sentry.init({
    dsn: 'https://...@sentry.io/...'
    sendDefaultPii: true,

    // Enable Logs
    enableLogs: true,
    beforeSendLog(log) {
      return log;
    },
    // Debug
    debug: true, //__DEV__, // true in dev builds

    // Configure Session Replay
    replaysSessionSampleRate: 0.1,
    replaysOnErrorSampleRate: 1,
    integrations: [
      Sentry.consoleLoggingIntegration({
        levels: ['debug', 'info', 'log', 'warn', 'error'],
      }),
      Sentry.mobileReplayIntegration({}),
      Sentry.feedbackIntegration({
        styles: {
          submitButton: {
            backgroundColor: '#6E4EA1',
            borderRadius: 5,
          },
        },
      }),
    ],
});

I know some options are not supported / useless in sentry/react-native (like Sentry.consoleLoggingIntegration) I was just testing all possible options to try to get the logs to work until I finally realized the ~4 days lag

Steps to Reproduce

Summary

Sentry.logger.* structured logs arrive in Sentry with a timestamp that is wrong by a large, constant offset (in our case ≈ 4.25 days, ≈ the device's uptime), while error/event timestamps are correct. The device's wall clock is correct — a Date.now() value we attach to each log as an attribute is spot-on; only the SDK-assigned log timestamp is off.

This is not an ingestion/Relay issue: the affected logs have no sentry._meta.fields.timestamp substitution, i.e. Relay accepted the SDK's timestamp verbatim. The wrong value is computed in the SDK.

Root cause

Logs stamp their timestamp with timestampInSeconds():

packages/core/src/logs/internal.ts

const timestamp = timestampInSeconds();

timestampInSeconds() trusts the Performance API unconditionally — no sanity check:

packages/core/src/utils/time.ts

function createUnixTimestampInSecondsFunc(): () => number {
  const { performance } = GLOBAL_OBJ as ...;
  if (!performance?.now || !performance.timeOrigin) {
    return dateTimestampInSeconds;
  }
  const timeOrigin = performance.timeOrigin;
  return () => {
    return (timeOrigin + withRandomSafeContext(() => performance.now())) / ONE_SECOND_IN_MS;
  };
}

On React Native / Hermes, performance.timeOrigin + performance.now() is not reliably wall-clock-anchored, so timeOrigin + now() is off by a constant. Notably, this same file already has a reliability check — getBrowserTimeOrigin() compares timeOrigin + performance.now() against Date.now() with a 5-minute threshold and falls back — but that guard is only used by browserPerformanceTimeOrigin() (tracing), not by timestampInSeconds(), and the log path calls timestampInSeconds() directly.

Errors are unaffected because event timestamps go through dateTimestampInSeconds() (= Date.now()).

Steps to reproduce

On an affected RN device (where the performance clock is skewed vs the wall clock):

console.log('Date.now()             =', Date.now());
console.log('performance.timeOrigin =', performance.timeOrigin);
console.log('performance.now()      =', performance.now());
// This is exactly what @sentry/core stamps onto a log:
console.log('SDK log clock          =', performance.timeOrigin + performance.now());

Sentry.logger.info('clock check', { deviceNow: Date.now() });

Observe in the Logs Explorer that the log's timestamp equals performance.timeOrigin + performance.now() and differs from the deviceNow attribute (and from reality) by a constant offset.

Image

Expected Result

Log timestamp should match Date.now() / real wall-clock time, the same as error events.

Actual Result

Log timestamp is performance.timeOrigin + performance.now(), wrong by a constant offset (≈ device uptime). Real data from one 2.3-minute session (38 logs):

value as UTC
SDK log timestamp 1784453353.186 2026-07-19T09:29:13.186Z (wrong)
our Date.now() attribute 1784820130576 2026-07-23T15:22:10.576Z (correct)

Offset ≈ 366,777 s (~4.25 days), constant across all 38 logs (drift ~0.1 s over the session) — the signature of a monotonic clock anchored to a bad origin, not a wall-clock error.

Metadata

Metadata

Assignees

Projects

Status
No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions