Skip to content

feat(develop-docs): Add timestamp to Metrics API#17791

Open
philprime wants to merge 1 commit into
masterfrom
philprime/metrics-api-timestamps
Open

feat(develop-docs): Add timestamp to Metrics API#17791
philprime wants to merge 1 commit into
masterfrom
philprime/metrics-api-timestamps

Conversation

@philprime
Copy link
Copy Markdown
Member

@philprime philprime commented May 18, 2026

DESCRIBE YOUR PR

Problem

The current Metrics API (Sentry.metrics.count, .gauge, .distribution) always timestamps each metric at the moment the SDK method is called.
There is no way for the caller to supply a timestamp.

This is fine when application code emits metrics inline (e.g. incrementing a counter inside a request handler), but it breaks down for an important class of use cases: scraper-style collection, where an external process reads a batch of metrics from a source and then forwards them to Sentry.

Concrete example: Prometheus scraping

A Prometheus-compatible application exposes a /metrics endpoint that returns all current metric values in a single response:

# HELP go_memstats_heap_alloc_bytes Number of heap bytes allocated and still in use.
# TYPE go_memstats_heap_alloc_bytes gauge
go_memstats_heap_alloc_bytes 7.261032e+06

# HELP go_goroutines Number of goroutines that currently exist.
# TYPE go_goroutines gauge
go_goroutines 43

# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.
# TYPE process_cpu_seconds_total counter
process_cpu_seconds_total 11.32

# HELP promhttp_metric_handler_requests_in_flight Current number of scrapes being served.
# TYPE promhttp_metric_handler_requests_in_flight gauge
promhttp_metric_handler_requests_in_flight 1

A scraper fetches this snapshot at time T, then iterates through the metrics and calls Sentry.metrics.gauge(...), Sentry.metrics.count(...), etc. for each one.

Without a user-supplied timestamp, each SDK call captures Date.now() independently.
Even on a fast machine, iterating through dozens of metrics introduces sub-millisecond to low-millisecond drift between calls.
This means metrics that were observed at the exact same instant end up with slightly different timestamps.

This change is necessary if we want to extend our sentry-kubernetes integration to support Prometheus-style scraping of metrics in a Kubernetes cluster.

Why this matters: bucket boundaries

Sentry aggregates metrics into time buckets.
When two metrics that logically belong to the same observation are recorded with timestamps close to the bucket boundary, they land in different buckets.
This causes:

Metrics from the same scrape can be close to a bucket boundary, splitting a single observation into two partial data points and causing artificial jitter or gaps on dashboards and correlations.

The probability of hitting a bucket boundary increases with the number of metrics per scrape and the scrape frequency.

Solution

Add an optional timestamp parameter to the options object of all three metrics methods (count, gauge, distribution).

const scrapedAt = new Date();
const metrics = scrapePrometheusEndpoint(url);

for (const m of metrics) {
  Sentry.metrics.gauge(m.name, m.value, {
    timestamp: scrapedAt,  // all metrics share the same observation time
    attributes: m.labels,
  });
}

When provided, the SDK uses the caller's timestamp for the metric payload instead of Date.now().
When omitted, behavior is unchanged (current time).

When a timestamp is set, we need to ensure the ordering of metrics as described in Metric Ordering.

Type

SDKs should accept the language's native date/time type for SDK user ergonomics:

Language Native type
JavaScript Date
Python datetime
Java Instant
Swift Date
PHP DateTimeInterface

The SDK converts to seconds-since-epoch for the wire format, which already has a required timestamp field.
This change should be aligned with the current implementation of the SDKs.

Alternative Approaches

Batching

The wire format already supports batching (the items array), but the public API is per-metric (count, gauge,distribution).
A batch API would be a much larger surface area change than necessary to resolve the issue and raises a couple of concerns:

  • A generic SentrySDK.metrics.capture(items: MetricItem[]) would introduce an object-based public API. At this point the users only need to call the API with the values and options, so this is a conceptual change.
  • Allowing multiple values, e.g. SentrySDK.metrics.gaugeBatch(values: number[], options) would capture all of the values with the same options, which is not always the right choice.
  • Allowing multiple values, e.g. SentrySDK.metrics.gaugeBatch(items: { value: number, options }[]) is the same as if we introduce an external item type.

All of these changes bring in additional complexity compared to just allowing the same timestamp to be set.

Use beforeSendMetric to override the timestamp

This callback receives the metric object before sending, so it could theoretically mutate the timestamp.
But it runs at flush time (potentially seconds later), not at capture time, and it doesn't have access to the caller's intended timestamp without out-of-band state.

IS YOUR CHANGE URGENT?

Help us prioritize incoming PRs by letting us know when the change needs to go live.

  • Urgent deadline (GA date, etc.):
  • Other deadline:
  • None: Not urgent, can wait up to 1 week+

SLA

  • Teamwork makes the dream work, so please add a reviewer to your PRs.
  • Please give the docs team up to 1 week to review your PR unless you've added an urgent due date to it.
    Thanks in advance for your help!

PRE-MERGE CHECKLIST

Make sure you've checked the following before merging your changes:

  • Checked Vercel preview for correctness, including links
  • PR was reviewed and approved by any necessary SMEs (subject matter experts)
  • PR was reviewed and approved by a member of the Sentry docs team

@vercel
Copy link
Copy Markdown

vercel Bot commented May 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
develop-docs Ready Ready Preview, Comment May 18, 2026 2:09pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
sentry-docs Ignored Ignored May 18, 2026 2:09pm

Request Review

@github-actions github-actions Bot added the sdk-develop-docs PRs touching develop-docs/sdk label May 18, 2026
@philprime philprime requested a review from giortzisg May 18, 2026 14:09
Copy link
Copy Markdown
Collaborator

@giortzisg giortzisg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice change @philprime. Though of adding a batch metric API, but this seems like the simplest way to solve the problem currently.

- `unit` **String, optional** — the unit of measurement (only used for `distribution` and `gauge`). SDKs **MUST NOT** restrict what can be sent in (e.g. by using an enum) but **SHOULD** offer constants or similar that help customers send in <Link to="/sdk/foundations/state-management/scopes/attributes/#units">units we support</Link> (since 2.4.0).
- `attributes` **Object, optional** — a dictionary of attributes (key-value pairs with type information).
- `scope` **Scope, optional** — the scope to capture the metric with.
- `timestamp` **Timestamp, optional** — indicates when the metric was recorded (since 2.9.0). SDKs **SHOULD** accept the language's native date/time type (e.g. `Date` in JavaScript/Swift, `datetime` in Python, `Instant` in Java, `DateTimeInterface` in PHP). When provided, the SDK **MUST** convert the value to seconds since epoch and use it as the `timestamp` field in the [metric payload](#metric-payload). When not provided, the SDK **MUST** default to the current time at the moment the metric is captured. If no such type exists, SDKs **MUST** accept a numeric Unix timestamp in seconds (epoch time) as a fallback instead.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When provided, the SDK MUST convert the value to seconds since epoch and use it as the timestamp

IIRC relay accepts both unix and RFC 3339 strings, so this shouldn't really be a MUST or at least mention that both are supported.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the section "Metric Payload" the timestamp is defined as a number in seconds (epoch time), without mentioning RFC 3339 strings.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a general misconception on our develop docs. Relay accepts both last time I checked.

@philprime philprime marked this pull request as ready for review May 20, 2026 07:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

sdk-develop-docs PRs touching develop-docs/sdk

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants