Metrics

Counter, gauge, and distribution metrics sent as batched trace_metric envelope items.

Statusstable
Version2.7.0(changelog)

This spec defines the format used by Sentry to ingest metrics, as well as the SDK API and behavior that create and send metrics to Sentry. Metrics are sent as trace_metric envelope items containing JSON-encoded payloads.

Related specs:


A metric has a type that determines how its values are interpreted:

  • Counter — tracks the number of times an event occurs. Only increases. Useful for requests, errors, or any countable event.
  • Gauge — tracks a value that can arbitrarily increase or decrease over time, such as memory usage, queue depth, or connection pool size.
  • Distribution — tracks the statistical distribution of a set of values. Useful for understanding percentiles, averages, and other statistical properties of measurements like response times or file sizes.

Metrics carry typed key-value pairs called attributes for dimensional filtering and grouping. Each attribute declares its type (string, boolean, integer, double). See Attributes for the full type specification.

Multiple metrics are batched into a single trace_metric envelope item for efficient transport. SDKs buffer metrics locally and flush them periodically or when size thresholds are reached.


Stablespecified since 2.0.0

Metric processing MUST follow this order:

  1. Capture metric via Public APIs (e.g. Sentry.metrics.count).
  2. Check if metrics are disabled as per enableMetrics/enable_metrics configuration — if so, skip the rest of the steps.
  3. Pass the metric to Hub/Scope via a generic method (e.g., captureMetric(metric)).
  4. Pass the metric to the Client via a generic method (e.g., captureMetric(metric)).
  5. Process captured metric (attach default attributes as per Default Attributes).
  6. Run beforeSendMetric/before_send_metric to filter or modify the metric.
  7. Add metric to buffer/batch processor as detailed in Buffering.
  8. At time of flushing buffer, send array of metrics to Sentry via trace_metric envelope, apply rate limiting as per Data Category and Rate Limiting.
Stablespecified since 2.0.0

SDKs MUST attach the following attributes to every metric by default:

  1. sentry.environment — the environment set in the SDK, if defined.
  2. sentry.release — the release set in the SDK, if defined.
  3. sentry.sdk.name — the name of the SDK that sent the metric.
  4. sentry.sdk.version — the version of the SDK that sent the metric.

SDKs SHOULD minimize the number of default attributes. Metrics cardinality can explode quickly with too many attributes. New default attributes SHOULD only be added after significant feedback from users and discussion internally with the SDK and ingest teams.

Stablespecified since 2.0.0

SDKs MAY attach user information as attributes, guarded by sendDefaultPii (unless manually set by user, since 2.5.0):

  1. user.id — the user ID. Maps to id in the User payload.
  2. user.name — the username. Maps to username in the User payload.
  3. user.email — the email address. Maps to email in the User payload.
Stablespecified since 2.0.0

By default, Relay parses the user agent attached to an incoming metric envelope to extract browser and os information. These attributes are attached by Relay, but client-side SDKs MAY attach them if they do not forward a user agent when sending metrics to Sentry.

  1. browser.name — display name of the browser application. Maps to name in the Browser Context.
  2. browser.version — version string of the browser. Maps to version in the Browser Context.
Stablespecified since 2.1.0

Backend SDKs (Node.js, Python, PHP, etc.) SHOULD attach the following attribute:

  1. server.address — the address of the server that sent the metric. Equivalent to server_name attached to errors and transactions.
Stablespecified since 2.0.0

SDKs MUST buffer metrics before sending them (since 2.2.0, tightened from SHOULD). SDKs SHOULD flush the buffer when specific conditions are met. For initial implementation, a simple strategy is fine, for example: flushing if the buffer length exceeds 100 items or if 5 seconds have passed.

To prevent data loss, the buffer SHOULD forward metrics to the transport in the scenarios outlined in the telemetry buffer data forwarding scenarios (since 2.2.0).

Furthermore:

  • The aggregation window SHOULD be time and size based.
  • SDKs SHOULD implement safeguards to prevent excessive memory usage from metric buffering.
Stablespecified since 2.0.0

Metrics SHOULD be associated with traces if possible. If a metric is recorded during an active span, the SDK SHOULD set the span_id property of the metric to the span ID of the span that was active when the metric was collected.

The trace_id field is REQUIRED on every metric payload and MUST be taken from the current propagation context.

Stablespecified since 2.3.0

Whenever possible, metrics SHOULD be linked to replays. If a metric is recorded while an active, sampled replay is in progress, the SDK SHOULD attach:

  1. sentry.replay_id — the ID of the replay that was active when the metric was collected. This MUST NOT be set if there was no active replay.
  2. sentry._internal.replay_is_buffering — indicates that the metric is associated with a replay that is currently buffering. This ensures correct handling of scenarios where a replay_id is present but the corresponding replay was never actually sent.

Some runtimes (notably Cloudflare Workers) freeze timer APIs (Date.now(), performance.now()) during synchronous execution, causing multiple metrics to share identical timestamps. SDKs that target runtimes where timestamps may be frozen or lack sub-millisecond precision MUST attach a sentry.timestamp.sequence integer attribute to every metric. SDKs that only target runtimes with reliable sub-millisecond timestamps MAY omit it.

Candidatespecified since 2.6.0

When sent, the sequence integer MUST:

  • Start at 0 when the SDK initializes.
  • Increment by 1 for each metric that is captured.
  • Reset to 0 when:
    • The SDK is re-initialized.
    • The current metric's integer millisecond differs from the previous metric's integer millisecond (i.e., floor(timestamp_seconds * 1000) changes).

The sequence provides deterministic ordering within a single SDK instance. It does not guarantee ordering across independent processes or workers, which have separate counters. The reset behavior ensures the sequence only increments for consecutive metrics that share the same timestamp.

Stablespecified since 2.0.0

The data category for metrics is trace_metric. Rate limiting applies as usual. SDKs SHOULD track client outcomes to monitor how often metrics are dropped. If the SDK receives a rate limit, it MUST NOT send any trace metrics until the limit expires (based on the communicated end timestamp).

Stablespecified since 2.0.0

If debug is set to true in SDK init, calls to the Sentry metrics API SHOULD also print to the console with appropriate details. This will help debugging metrics setups.

Candidatespecified since 2.7.0

Metrics emitted via the public API are opt-out. The enableMetrics/enable_metrics option MUST act as a general kill switch for all metrics sent to Sentry — when set to false, no metrics MUST be sent, regardless of whether they originate from the public API, auto-emitting integrations, or third-party bindings.

However, metrics that are automatically emitted by integrations or libraries without an explicit user call follow different rules because they can generate unexpected volume and cost.

Integrations that auto-emit metrics — An SDK integration that automatically captures metrics (e.g., recording HTTP request durations, database query counts, or runtime resource usage) MUST be opt-in. The integration MUST NOT emit any metrics unless the user explicitly enables it. The opt-in mechanism MAY be a dedicated integration-level option or requiring the user to explicitly add the integration.

Third-party metrics bindings — An SDK feature that binds to an external library's metrics API and mirrors those metrics as Sentry metrics in the background (e.g., syncing from a framework's built-in metrics or an OpenTelemetry metrics pipeline) MUST also be opt-in. Users MUST explicitly enable the binding before any metrics are forwarded to Sentry.

These rules exist because auto-emitted metrics can cause a high volume of telemetry that directly impacts a user's quota. Unlike spans or errors — where users have prior intuition about volume — silently emitting metrics on their behalf could lead to unexpected costs. Requiring opt-in ensures users remain in control of their metrics volume.


Stablespecified since 2.0.0

Metrics are sent as a trace_metric envelope item containing a JSON object with an items array. See the Examples section for a complete envelope.

An envelope MUST contain at most one trace_metric envelope item — all metrics for a flush are batched into a single item's items array. Metrics from different traces MAY be mixed into the same trace_metric item, but if they are, the envelope MUST NOT include a DSC (dynamic sampling context) header.

Item Headers:

FieldTypeRequiredDescription
typeStringREQUIREDMUST be "trace_metric".
item_countNumberREQUIREDNumber of metric items in the items array. MUST match the actual count.
content_typeStringREQUIREDMUST be "application/vnd.sentry.items.trace-metric+json".
Copied
{
	"type": "trace_metric",
	"item_count": 10,
	"content_type": "application/vnd.sentry.items.trace-metric+json"
}
{
	"items": [{..metric..}, {..metric..}, {..metric..}]
}
Stablespecified since 2.0.0

Each metric in the items array is a JSON object with the following fields:

Copied
{
  "timestamp": 1544719860.0,
  "trace_id": "5b8efff798038103d269b633813fc60c",
  "span_id": "b0e6f15b45c36b12",
  "name": "api.response_time",
  "value": 125.5,
  "unit": "millisecond",
  "type": "distribution",
  "attributes": {
    "endpoint": { "value": "api/users", "type": "string" },
    "method": { "value": "GET", "type": "string" },
    "status_code": { "value": 200, "type": "integer" }
  }
}
FieldTypeRequiredSinceDescription
timestampNumberREQUIRED2.0.0Timestamp in seconds (epoch time) indicating when the metric was recorded.
typeStringREQUIRED2.0.0The type of metric. One of counter, gauge, distribution.
nameStringREQUIRED2.0.0The name of the metric. SHOULD follow a hierarchical naming convention using dots as separators (e.g., api.response_time, db.query.duration).
valueNumberREQUIRED2.0.0The numeric value. For counter: the count to increment by (default 1). For gauge: the current value. For distribution: a single measured value. Integers MUST be 64-bit signed; doubles MUST be 64-bit floating point.
trace_idStringREQUIRED2.0.016 random bytes encoded as a hex string (32 characters). Taken from the current propagation context.
span_idStringOPTIONAL2.3.08 random bytes encoded as a hex string (16 characters). The span ID of the span that was active when the metric was emitted.
unitStringOPTIONAL2.0.0The unit of measurement for the metric value.
attributesObjectOPTIONAL2.0.0Typed key-value pairs. See Attributes for supported types and structure.

Stablespecified since 2.0.0

SDKs MUST expose the following configuration options:

  • enableMetrics/enable_metrics — a boolean flag to control if metric envelopes will be generated and sent to Sentry. If false, the SDK MUST NOT send metrics. Defaults to true.

  • beforeSendMetric/before_send_metric — an OPTIONAL function that takes a metric object and returns a metric object. Called before sending the metric to Sentry. Can be used to modify the metric or prevent it from being sent (by returning null/None).

While metrics functionality is in an experimental state, SDKs SHOULD put these options in an experimental namespace to avoid breaking changes:

Copied
Sentry.init({
  // stable
  enableMetrics: true,

  // experimental
  _experiments: { enableMetrics: true },
})
Stablespecified since 2.0.0

SDKs MUST expose metrics methods in a metrics module or namespace. At minimum, the SDK MUST implement the following methods:

  • Sentry.metrics.count(name, value, options) — increment a counter
  • Sentry.metrics.gauge(name, value, options) — set a gauge value
  • Sentry.metrics.distribution(name, value, options) — add a distribution value

Parameters:

  • name String, required — the name of the metric.
  • value Number, required — the value of the metric.
  • options Object, optional — containing:
    • 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 units we support (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.

The Hub/Scope and Client MUST exclusively provide a generic method for capturing metrics (e.g., captureMetric(metric)). They MUST NOT replicate the static Sentry.metrics.X API methods to avoid increasing our API surface. Users utilizing multiple Hub/Scope or Clients will need to rely on the generic method for capturing metrics.

The threading and concurrency considerations for metrics are the same as for logs. See the Threading and Concurrency Considerations section in the Logs documentation for more information.


Copied
{
  "name": "api.requests",
  "value": 1,
  "type": "counter",
  "attributes": {
    "endpoint": { "value": "/api/users", "type": "string" },
    "method": { "value": "POST", "type": "string" }
  }
}

Copied
{
  "name": "db.connection_pool.active",
  "value": 42,
  "unit": "connection",
  "type": "gauge",
  "attributes": {
    "pool_name": { "value": "main_db", "type": "string" },
    "max_size": { "value": 100, "type": "integer" },
    "database": { "value": "postgres", "type": "string" }
  }
}

Copied
{
  "name": "page.load_time",
  "value": 245.7,
  "unit": "millisecond",
  "type": "distribution",
  "attributes": {
    "page": { "value": "/dashboard", "type": "string" },
    "browser": { "value": "chrome", "type": "string" }
  }
}

Copied
// Increment a counter
Sentry.metrics.count("button.clicks", 1, {
  attributes: {
    button_id: "submit",
    page: "checkout",
  },
});

// Set a gauge value
Sentry.metrics.gauge("db.connection_pool.active", 42, {
  unit: "connection",
  attributes: {
    pool_name: "main_db",
    database: "postgres",
  },
});

// Record a distribution
Sentry.metrics.distribution("page.load_time", 245.7, {
  unit: "millisecond",
  attributes: {
    page: "/dashboard",
    browser: "chrome",
  },
});

Copied
{
  "attributes": {
    "sentry.environment": { "value": "production", "type": "string" },
    "sentry.release": { "value": "1.0.0", "type": "string" },
    "sentry.sdk.name": {
      "value": "sentry.javascript.browser",
      "type": "string"
    },
    "sentry.sdk.version": { "value": "10.17.0", "type": "string" },
    "sentry.replay_id": {
      "value": "36b75d9fa11f45459412a96c41bdf691",
      "type": "string"
    }
  }
}

Copied
{
  "attributes": {
    "user.id": { "value": "123", "type": "string" },
    "user.name": { "value": "john.doe", "type": "string" },
    "user.email": { "value": "john.doe@example.com", "type": "string" }
  }
}

Copied
{ "sdk": { "name": "sentry.javascript.browser", "version": "10.17.0" } }
{
  "type": "trace_metric",
  "item_count": 5,
  "content_type": "application/vnd.sentry.items.trace-metric+json"
}
{
  "items": [
    {
      "timestamp": 1746456149.019,
      "trace_id": "624f66e93a04469f9992c7e9f1485056",
      "span_id": "b0e6f15b45c36b12",
      "name": "api.requests",
      "value": 1,
      "type": "counter",
      "attributes": {
        "endpoint": { "value": "/api/users", "type": "string" },
        "method": { "value": "POST", "type": "string" },
        "status_code": { "value": 201, "type": "integer" },
        "sentry.sdk.name": { "value": "sentry.javascript.browser", "type": "string" },
        "sentry.sdk.version": { "value": "10.17.0", "type": "string" },
        "sentry.environment": { "value": "production", "type": "string" },
        "sentry.release": { "value": "1.0.0", "type": "string" },
        "sentry.timestamp.sequence": { "value": 0, "type": "integer" }
      }
    },
    {
      "timestamp": 1746456149.019,
      "trace_id": "624f66e93a04469f9992c7e9f1485056",
      "span_id": "b0e6f15b45c36b12",
      "name": "api.response_time",
      "value": 125.5,
      "unit": "millisecond",
      "type": "distribution",
      "attributes": {
        "endpoint": { "value": "/api/users", "type": "string" },
        "method": { "value": "POST", "type": "string" },
        "sentry.sdk.name": { "value": "sentry.javascript.browser", "type": "string" },
        "sentry.sdk.version": { "value": "10.17.0", "type": "string" },
        "sentry.timestamp.sequence": { "value": 1, "type": "integer" }
      }
    },
    {
      "timestamp": 1746456149.02,
      "trace_id": "624f66e93a04469f9992c7e9f1485056",
      "span_id": "b0e6f15b45c36b12",
      "name": "cache.hit_rate",
      "value": 0.95,
      "unit": "ratio",
      "type": "gauge",
      "attributes": {
        "cache_name": { "value": "user_sessions", "type": "string" },
        "region": { "value": "us-west-1", "type": "string" },
        "sentry.sdk.name": { "value": "sentry.javascript.browser", "type": "string" },
        "sentry.sdk.version": { "value": "10.17.0", "type": "string" },
        "sentry.timestamp.sequence": { "value": 0, "type": "integer" }
      }
    },
    {
      "timestamp": 1746456149.02,
      "trace_id": "624f66e93a04469f9992c7e9f1485056",
      "span_id": "b0e6f15b45c36b12",
      "name": "database.query.duration",
      "value": 42.3,
      "unit": "millisecond",
      "type": "distribution",
      "attributes": {
        "query_type": { "value": "SELECT", "type": "string" },
        "table": { "value": "users", "type": "string" },
        "sentry.sdk.name": { "value": "sentry.javascript.browser", "type": "string" },
        "sentry.sdk.version": { "value": "10.17.0", "type": "string" },
        "sentry.origin": { "value": "auto.db.graphql", "type": "string" },
        "sentry.timestamp.sequence": { "value": 1, "type": "integer" }
      }
    },
    {
      "timestamp": 1746456149.021,
      "trace_id": "624f66e93a04469f9992c7e9f1485056",
      "span_id": "b0e6f15b45c36b12",
      "name": "active.users",
      "value": 12345,
      "type": "gauge",
      "attributes": {
        "cohort": { "value": "beta", "type": "string" },
        "sentry.sdk.name": { "value": "sentry.javascript.browser", "type": "string" },
        "sentry.sdk.version": { "value": "10.17.0", "type": "string" },
        "sentry.replay_id": { "value": "36b75d9fa11f45459412a96c41bdf691", "type": "string" },
        "sentry.timestamp.sequence": { "value": 0, "type": "integer" }
      }
    }
  ]
}

VersionDateSummary
2.7.02026-03-10Auto-emitted metrics and third-party metrics bindings MUST be opt-in
2.6.02026-03-04Add sentry.timestamp.sequence attribute for deterministic metric ordering
2.5.02026-02-12Clarified sendDefaultPii gating for user attributes — allowed when user manually sets data
2.4.12026-01-19Fixed example — replaced unsupported set type with gauge
2.4.02026-01-14SDKs MUST NOT restrict unit values, SHOULD offer constants for supported units
2.3.12026-01-09Fixed Swift API examples (syntax correction)
2.3.02025-12-15Added span_id field, added replay association (sentry.replay_id, sentry._internal.replay_is_buffering)
2.2.02025-11-19Tightened buffering from SHOULD to MUST, added telemetry buffer forwarding scenarios
2.1.02025-11-17Added backend SDK default attributes (server.address)
2.0.02025-11-13Breaking — new trace_metric JSON envelope format, attribute system replacing tags
1.0.02023-12-07Initial metrics spec (statsd-based format, now superseded)
Was this helpful?
Help improve this content
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").