Take the Prometheus User Survey (Edition 03.2026)  and help the community prioritize future development!

OpenMetrics 2.0 Migration Guide for Client Libraries

This guide covers the changes from OpenMetrics (OM) 1.0 to OpenMetrics 2.0 that affect client library (i.e. exposers) implementations. Sections are organized so you can implement changes incrementally, starting with version negotiation and working through metric types, syntax, and metadata. This guide may contain errors, and in any case where this document disagrees with the spec document, the spec document is the authoritative source of truth.

NOTE: OpenMetrics 2.0 is currently experimental (release candidate versions), so some details might change. Track progress at the OpenMetrics 2.0 work group issue .

This guide covers the changes most relevant to client library / exposer authors. For the complete specification including ABNF grammar, conformance requirements, and ingester rules, see the full OpenMetrics 2.0 specification. If you find errors or have questions, file an issue on the OpenMetrics repository .

How to use this guide

Each section below guides implementers through the changes. We use a Breaking or Non-breaking label. A change is Breaking if a line that was valid in OM 1.0 is now invalid in OM 2.0. A Non-breaking change adds new syntax or relaxes restrictions without invalidating any existing valid line.

There are also references to the relevant section of the specification if more detail is required.

Quick Reference

OpenMetrics 2.0 contains many changes. Some of those changes are a loosening of previously-strict requirements, like the way metric names are constructed or character limits. Some of these changes are in service of allowing OpenTelemetry metric data to be encoded in OpenMetrics without violating the specification. Other changes improve scraper (e.g. Prometheus) efficiency and reliablity on various cases. Changes introduce new syntaxes, mostly focused on allowing metric data to be encoded into a single line rather than requiring multiple lines to describe one cohesive piece of information. Lastly, some changes add new features and data types, like Native Histograms.

Change1.02.0Breaking?
Negotiation
Content-Type version headerversion=1.0.0version=2.0.0No
Negotiation defaultsOldest versionSame (default to 1.0)No
Naming
MetricFamily must match Metric NameImplicit suffix strippingExact match requiredYes
Counter _total suffix_total MUST_total SHOULDNo
Reserved suffixesNot specified_count/_sum/_bucket etc. SHOULD NOTNo
Metadata
UTF-8 Names
Metric and label name quoting[a-zA-Z0-9_:] onlyUTF-8 allowed; quoted when neededNo
Start Timestamps
st@ replaces _createdSeparate _created sampleInline st@ on sample lineYes
CompositeValues
Summary / Histogram / GaugeHistogramExpanded _bucket/_count/_sum linesSingle {key:value} CompositeValueYes
Sum and Count required_count/_sum optionalCount and Sum required in CompositeValueYes
Native Histograms
Native-only and combined histogramsN/AExponential buckets via schema/spans/buckets/etcNo
Exemplars
W3C Trace Context KeysN/Atrace_id and span_id recommendedNo
Mandatory timestamps / multiple exemplarsOptional timestamp; max 1Mandatory timestamp; multiple allowedMixed
Size limit / W3C keys / histogram placement128-char limit; bucket-levelSoft limit; W3C keys; sample-levelMixed
Unknown Type
CompositeValue allowedNumber onlyNumber or CompositeValueNo

Version Negotiation and Content-Type

Non-breaking: OM 1.0 Content-Type is still valid.

In OM 1.0, exposers used the following Content-Type header to identify their format:

OM 1.0:

application/openmetrics-text; version=1.0.0; charset=utf-8

OM 2.0:

application/openmetrics-text; version=2.0.0; charset=utf-8

Negotiation defaults

Exposers must default to the oldest version of the standard (1.0.0) when no specific version is requested. Use standard HTTP content-type negotiation. If the scraper does not request version 2.0.0, respond with 1.0.0.

This means your exposer should continue serving 1.0 format by default and only switch to 2.0 when the consumer asks for it.

Protobuf format removed

OM 2.0 no longer specifies an official protobuf format. You may continue to support the protobuf format for 1.0 (we d, but 2.0 does not contain a new or updated protobuf format.

The Prometheus protobuf wire format is still important and maintain, see the exposition formats documentation.

See: Protocol Negotiation in the OM 2.0 spec.

Naming Changes

OM 2.0 tightens the relationship between MetricFamily names and Metric Names, and changes the suffix rules for counters and info metrics. In OM 1.0, parsers implicitly stripped known suffixes to map sample names back to their MetricFamily. In OM 2.0, this implicit stripping is gone: the MetricFamily name must exactly match every Metric's Name.

Generally, the mandatory unit and _total suffix is also now recommended, but not required.

Specification Terminology Changes

Non-breaking: Does not affect exposition format, only the terms used in the spec. It's useful to understand this change for easier use of the OM 2.0 spec.

OM 2.0 restructures the data model hierarchy. In OM 2.0, MetricPoint is removed and Sample becomes a first-class data model object:

OM 1.0: MetricSet → MetricFamily → Metric → MetricPoint → Sample (text format) OM 2.0: MetricSet → MetricFamily → Metric → Sample (with Number or CompositeValue)

ScopeOM 1.0 TermOM 2.0 TermWhat changed
All typesMetricSetMetricSetUnchanged.
All typesMetricFamilyMetricFamilyName must now match every Metric's Name (no implicit suffix stripping).
All typesMetricMetricUnchanged.
All typesMetricPointSampleSample replaces MetricPoint. Complex types (except StateSet) use CompositeValue instead of multiple MetricPoints.
StateSetMetricMetricGroupThe set of all states sharing one label set is now called a MetricGroup.
StateSetMetricPointMetricAn individual state within that set is now called a Metric.

MetricFamily Name Must Match Metric Name

Breaking: The MetricFamily Name used in TYPE, HELP, and UNIT comments must match the name used on Metric lines.

In OM 1.0, a counter's TYPE line used a base name (e.g. http_requests) while its samples carried the _total suffix (e.g. http_requests_total). The parser knew to strip _total when matching samples back to their MetricFamily, so the MetricFamily name and sample Metric Name could differ.

In OM 2.0, the MetricFamily name must exactly match every Metric's Name. There is no implicit suffix stripping. For counters, this means the TYPE line must include _total if the samples use it.

OM 1.0:

# TYPE http_requests counter
# HELP http_requests Total HTTP requests.
http_requests_total 1027

OM 2.0:

# TYPE http_requests_total counter
# HELP http_requests_total Total HTTP requests.
http_requests_total 1027

Notice that the TYPE and HELP lines now use http_requests_total to match the sample name exactly. This is the most common change you will need to make: update TYPE and HELP metadata names to include the suffix that was previously only on samples.

See: MetricFamily in the OM 2.0 spec.

Counter Suffix Rules

Counter _total

Non-breaking: Counters with _total are still valid.

In OM 1.0, counter Metric names must end in _total.

In OM 2.0, counter Metric names should end in _total. It is no longer mandatory.

Tip: The change in rule to should is primarily intended to enable compatibility with OpenTelemetry, which does not require any particular format for counter metric names.

OM 1.0:

# TYPE http_requests counter
http_requests_total 1027

Valid OM 2.0:

# TYPE http_requests counter
http_requests 1027

Reserved Suffixes

Non-breaking: OM 1.0 did not allow these suffixes at all.

MetricFamily names should not end with any of these reserved suffixes:

  • _count
  • _sum
  • _bucket
  • _gcount
  • _gsum

These suffixes are reserved because older ingestors that convert histograms and summaries to classic representation expand them into samples with these suffixes. If you have a gauge called foo_bucket and a histogram called foo, older ingestors would expand the histogram into foo_bucket, foo_count, and foo_sum samples, creating a collision with your gauge.

This is a should not (not must not), so existing metrics with these suffixes will still parse. However, renaming them avoids collision bugs when your metrics are consumed by systems that expand compound types into classic representation.

See: MetricFamily in the OM 2.0 spec.

Naming Changes in Practice

Here is a complete OM 2.0 exposition for an HTTP server, showing how the naming rules work together:

# TYPE http_requests_total counter
# HELP http_requests_total Total HTTP requests received.
http_requests_total{method="GET",code="200"} 1027
http_requests_total{method="POST",code="201"} 53
# TYPE http_request_duration_seconds histogram
# HELP http_request_duration_seconds Duration of HTTP requests in seconds.
http_request_duration_seconds{method="GET"} {count:1027,sum:172.5,bucket:[0.1:800,0.5:950,+Inf:1027]}
# TYPE build_info info
# HELP build_info Build metadata.
build_info{version="1.4.2",branch="main",goversion="go1.22"} 1
# EOF

What each metric demonstrates:

  • http_requests_total (counter): TYPE line uses http_requests_total, matching the sample name exactly. The _total suffix is kept for readability and backward compatibility.
  • http_request_duration_seconds (histogram): MetricFamily name avoids reserved suffixes. The histogram uses CompositeValue syntax with all bucket data on a single line (described later).
  • build_info (info): TYPE line uses build_info, matching the sample name. The _info suffix is present on the MetricFamily name as required.

Metadata Changes

OM 2.0 relaxes several metadata requirements and renames a few conventions. This section covers all metadata-level changes that affect how MetricFamilies are described.

UTF-8 Names

Non-breaking: OM 1.0 syntax for traditional names is still valid.

OM 2.0 allows metric and label names to contain UTF-8 characters beyond the traditional [a-zA-Z0-9_:] set. This exists primarily for OpenTelemetry bridge scenarios, where metrics use dotted naming conventions like process.cpu.seconds. Dotted names pair well with the relaxed _total suffix rule described in Naming Changes (see Counter and Info Suffix Rules), since dropping _total gives you cleaner dotted metric names.

Be aware that not all Prometheus ecosystem tools support UTF-8 metric names yet.

Metric Name Quoting

Metric names that do not match ^[a-zA-Z_:][a-zA-Z0-9_:]*$ must be enclosed in double quotes. Any metric name may be enclosed in double quotes, but quoting is only required when the name contains characters outside the traditional set.

Within quoted strings, use \\ for a literal backslash, \" for a literal double quote, and \n for a newline.

# TYPE "process.cpu.seconds" counter
# HELP "process.cpu.seconds" Total user and system CPU time spent in seconds.
{"process.cpu.seconds"} 4.20072246e+06

See: UTF-8 Quoting in the OM 2.0 spec.

Label Name Quoting

Label names that do not match ^[a-zA-Z_][a-zA-Z0-9_]*$ must be enclosed in double quotes. Any label name may be enclosed in double quotes, but quoting is only required when the name contains characters outside the traditional set.

{"process.cpu.seconds","node.name"="my_node"} 4.20072246e+06

See: UTF-8 Quoting in the OM 2.0 spec.

Alternative Brace Syntax

When a metric name requires quoting, the quoted name moves inside the braces as the first element. This is required for quoted metric names: the metric name must appear inside the braces, not outside. In the ABNF grammar, this is the name-and-labels-in-braces production.

The following complete example shows TYPE, UNIT, and HELP metadata with a quoted metric name, followed by a sample line using the brace syntax with both a quoted metric name and a quoted label name:

# TYPE "process.cpu.seconds" counter
# UNIT "process.cpu.seconds" seconds
# HELP "process.cpu.seconds" Total user and system CPU time spent in seconds.
{"process.cpu.seconds","node.name"="my_node"} 4.20072246e+06

See: UTF-8 Quoting in the OM 2.0 spec.

Start Timestamps

Breaking: _created samples are no longer used to provide the Start Time.

OM 2.0 replaces separate _created samples with an inline Start Timestamp (st@) on the sample line itself, reducing exposition size and avoiding race conditions between the _created sample and the value sample. Counters, histograms, and summaries have creation semantics and support Start Timestamps; gauges do not.

So an OM 2.0 sample line has the following field ordering:

metric_name value [timestamp] [st@start_timestamp] [# exemplar...]

Counter

OM 1.0:

# TYPE http_requests counter
http_requests_total 1027
http_requests_created 1000000000

OM 2.0:

# TYPE http_requests_total counter
http_requests_total 1027 [email protected]

The OM 1.0 example uses http_requests as the TYPE name (implicit suffix stripping), while OM 2.0 uses http_requests_total (MetricFamily must match Metric Name, per Naming Changes). The http_requests_created sample becomes the inline st@ timestamp.

Histogram

OM 1.0:

# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{le="0.1"} 800
http_request_duration_seconds_bucket{le="0.5"} 950
http_request_duration_seconds_bucket{le="+Inf"} 1027
http_request_duration_seconds_sum 172.5
http_request_duration_seconds_count 1027
http_request_duration_seconds_created 1000000000

OM 2.0 histograms use CompositeValue syntax (covered in detail in CompositeValues). Here we focus on where st@ appears:

OM 2.0:

# TYPE http_request_duration_seconds histogram
http_request_duration_seconds {count:1027,sum:172.5,bucket:[0.1:800,0.5:950,+Inf:1027]} [email protected]

The _created sample disappears entirely. The st@ timestamp appears after the value on the single CompositeValue line.

Start Timestamps in Practice

This combined example shows counters and a histogram together with labels, timestamps, Start Timestamps, and an exemplar demonstrating the full field ordering:

# TYPE http_requests_total counter
http_requests_total{method="GET",code="200"} 1027 1710000000.123 [email protected] # {trace_id="abc123",span_id="def456"} 1.0 1709999999.456
http_requests_total{method="POST",code="201"} 53 1710000000.123 [email protected]
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds{method="GET"} {count:1027,sum:172.5,bucket:[0.1:800,0.5:950,+Inf:1027]} 1710000000.123 [email protected]
# EOF

See also: Exemplars

See: Counter and Histogram with Classic Buckets in the OM 2.0 spec.

CompositeValues

Breaking: Traditional multiline complex types are no longer allowed (except StateSet).

In OM 1.0, complex metric types (histograms, summaries, gaugehistograms) were represented as multiple expanded sample lines — one line per bucket, one for count, one for sum. In OM 2.0, these become a single sample line whose value is a CompositeValue: a structured {key:value,...} block containing all the fields at once.

All three types — histogram, summary, and gaugehistogram — must use CompositeValue syntax in OM 2.0.

Syntax overview

A CompositeValue is enclosed in curly braces and contains comma-separated key:value fields with no whitespace. Keys are fixed literal names; values are numbers, integers, or bracket-enclosed lists depending on the field. The full set of keys is:

KeyValue typeUsed byDescription
countnumberhistogram, summaryObservation count.
sumnumberhistogram, summarySum of observed values.
gcountnumbergaugehistogramGauge observation count.
gsumnumbergaugehistogramGauge sum of observed values.
quantile[q:v,...]summaryQuantile/value pairs, sorted by quantile.
bucket[le:v,...,+Inf:v]histogram, gaugehistogram (classic)Classic bucket upper-bound/count pairs. +Inf is required.
schemaintegerhistogram, gaugehistogram (native)Native histogram resolution schema.
zero_thresholdrealnumberhistogram, gaugehistogram (native)Width of the zero bucket.
zero_countnumberhistogram, gaugehistogram (native)Count of observations in the zero bucket.
negative_spans[off:len,...]histogram, gaugehistogram (native)Span definitions for negative buckets.
negative_buckets[v,...]histogram, gaugehistogram (native)Counts for negative buckets.
positive_spans[off:len,...]histogram, gaugehistogram (native)Span definitions for positive buckets.
positive_buckets[v,...]histogram, gaugehistogram (native)Counts for positive buckets.

Not every key appears in every CompositeValue. Which keys are required depends on the metric type:

  • Summary: count, sum, quantile
  • Classic histogram: count, sum, bucket
  • Classic gaugehistogram: gcount, gsum, bucket
  • Native histogram: count, sum, schema, zero_threshold, zero_count, and optionally negative_spans/negative_buckets and/or positive_spans/positive_buckets
  • Native gaugehistogram: gcount, gsum, schema, zero_threshold, zero_count, and optionally negative_spans/negative_buckets and/or positive_spans/positive_buckets
  • Combined (classic + native): count/gcount, sum/gsum, native fields, then bucket

An abstract example showing every key (a combined classic + native histogram):

{count:<n>,sum:<n>,schema:<i>,zero_threshold:<r>,zero_count:<n>,negative_spans:[<off>:<len>,...],negative_buckets:[<n>,...],positive_spans:[<off>:<len>,...],positive_buckets:[<n>,...],bucket:[<le>:<n>,...,+Inf:<n>]}

Where <n> is a number, <i> is an integer, <r> is a real number, <off> is an integer offset, <len> is a positive integer length, and <le> is a real-valued bucket boundary. Fields must appear in the order shown. There must not be any whitespace inside the braces.

See: CompositeValues in the OM 2.0 spec.

Summary

Rule: In OM 2.0, a Summary Sample must contain Count, Sum, and a set of quantiles. In OM 1.0 these were all may. Exposers that previously omitted count or sum must now include them.

OM 1.0:

# TYPE http_request_duration_seconds_summary summary
http_request_duration_seconds_summary{quantile="0.5"} 0.013
http_request_duration_seconds_summary{quantile="0.9"} 0.025
http_request_duration_seconds_summary{quantile="0.99"} 0.10
http_request_duration_seconds_summary_sum 172.5
http_request_duration_seconds_summary_count 1027

OM 2.0:

# TYPE http_request_duration_seconds_summary summary
http_request_duration_seconds_summary {count:1027,sum:172.5,quantile:[0.5:0.013,0.9:0.025,0.99:0.10]}

All five sample lines collapse to one. The quantile map moves inside the CompositeValue block. Count and sum are now mandatory fields. If there are no observations, use quantile:[] (empty quantile list).

See: Summary in the OM 2.0 spec.

Histogram

OM 1.0:

# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{le="0.1"} 800
http_request_duration_seconds_bucket{le="0.5"} 950
http_request_duration_seconds_bucket{le="+Inf"} 1027
http_request_duration_seconds_sum 172.5
http_request_duration_seconds_count 1027

OM 2.0:

# TYPE http_request_duration_seconds histogram
http_request_duration_seconds {count:1027,sum:172.5,bucket:[0.1:800,0.5:950,+Inf:1027]}

Every _bucket, _sum, and _count sample line merges into a single CompositeValue. The bucket list uses threshold:count pairs, with the +Inf bucket included. Fields must appear in this order: count, sum, bucket. The _sum and _count suffixes disappear entirely.

See: Histogram with Classic Buckets in the OM 2.0 spec.

GaugeHistogram

A GaugeHistogram measures current distributions (not reset-based). Common examples: queue depth, in-flight request size. Unlike a histogram, values are gauge-semantics — they can go up or down without a reset. The OM 1.0 expanded format used _gcount/_gsum suffixes; OM 2.0 uses CompositeValue with gcount and gsum field names (preserving the gauge-semantic prefix).

OM 1.0:

# TYPE queue_depth_bytes gaugehistogram
queue_depth_bytes_bucket{le="1024"} 5
queue_depth_bytes_bucket{le="65536"} 18
queue_depth_bytes_bucket{le="+Inf"} 23
queue_depth_bytes_gcount 23
queue_depth_bytes_gsum 1048576

OM 2.0:

# TYPE queue_depth_bytes gaugehistogram
queue_depth_bytes {gcount:23,gsum:1048576,bucket:[1024:5,65536:18,+Inf:23]}

The CompositeValue format is similar to a classic histogram, but uses gcount and gsum instead of count and sum. The TYPE line also distinguishes them. Fields must appear in this order: gcount, gsum, bucket. GaugeHistograms do not have Start Timestamps (no creation semantics).

See: GaugeHistogram with Classic Buckets in the OM 2.0 spec.

CompositeValues in Practice

A complete exposition showing all types together:

# TYPE http_requests_total counter
http_requests_total{method="GET",code="200"} 1027 1710000000 st@1000000000
# TYPE http_request_duration_seconds_summary summary
http_request_duration_seconds_summary{path="/api/v1"} {count:1027,sum:172.5,quantile:[0.5:0.013,0.9:0.025,0.99:0.10]} 1710000000 st@1000000000
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds{path="/api/v1"} {count:1027,sum:172.5,bucket:[0.1:800,0.5:950,+Inf:1027]} 1710000000 st@1000000000
# TYPE queue_depth_bytes gaugehistogram
queue_depth_bytes{queue="work"} {gcount:23,gsum:1048576,bucket:[1024:5,65536:18,+Inf:23]} 1710000000
# EOF

Note: The counter line uses a plain Number value, not a CompositeValue. The summary and histogram use CompositeValue blocks. The gaugehistogram has no Start Timestamp (no creation semantics).

Sum and Count Required

Breaking: OM 2.0 does not allow eliding of sum and count.

The Sum and Count are now required in OM 2.0 for histograms and gaugehistograms. In OM 1.0 these were optional.

Histogram

OM 1.0:

# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{le="0.1"} 800
http_request_duration_seconds_bucket{le="0.5"} 950
http_request_duration_seconds_bucket{le="+Inf"} 1027
http_request_duration_seconds_created 1000000000

OM 2.0:

# TYPE http_request_duration_seconds histogram
http_request_duration_seconds {count:1027,sum:172.5,bucket:[0.1:800,0.5:950,+Inf:1027]} st@1000000000

GaugeHistogram

OM 1.0:

# TYPE queue_depth_bytes gaugehistogram
queue_depth_bytes_bucket{le="1024"} 5
queue_depth_bytes_bucket{le="65536"} 18
queue_depth_bytes_bucket{le="+Inf"} 23

OM 2.0:

# TYPE queue_depth_bytes gaugehistogram
queue_depth_bytes {gcount:23,gsum:1048576,bucket:[1024:5,65536:18,+Inf:23]}

See: Histogram and GaugeHistogram in the OM 2.0 spec.

Native Histograms

Non-breaking: Native Histograms were not supported at all in OM 1.0.

Native histograms are new in OM 2.0. Instead of fixed bucket boundaries chosen at instrumentation time, native histograms use an exponential bucket schema that provides automatic resolution across all value ranges without any bucket configuration. The schema field controls bucket width granularity: higher values produce narrower (finer) buckets.

This section builds on the CompositeValue syntax covered in CompositeValues. Native histogram fields follow the same {key:value,...} format described above.

Native-Only Histogram

A native-only histogram CompositeValue contains these fields, in order:

  • count -- total number of observations (number).
  • sum -- sum of all observed values (number).
  • schema -- integer (-4 to 8) controlling exponential bucket width. Higher values mean finer granularity.
  • zero_threshold -- non-negative float defining the zero bucket boundary [-threshold, +threshold].
  • zero_count -- number of observations in the zero bucket.
  • positive_spans -- list of offset:length pairs mapping bucket indices (see below).
  • positive_buckets -- list of observation counts, one per bucket.

Spans are offset:length pairs that describe which exponential buckets are populated. The first span's offset is the starting bucket index (can be negative). Its length is the number of consecutive populated buckets starting from that index. Each subsequent span's offset is the number of empty buckets to skip before the next group. The sum of all span lengths equals the total number of values in the bucket list. See Native Buckets in the spec for the bucket boundary formula.

Negative spans and buckets (negative_spans, negative_buckets) use the same syntax for negative observations.

If no buckets are populated, the span and bucket fields can be omitted entirely.

# TYPE http_request_duration_seconds histogram
http_request_duration_seconds {count:59,sum:120.0,schema:3,zero_threshold:1e-4,zero_count:2,positive_spans:[0:3,2:2],positive_buckets:[10,15,12,8,12]} 1710000000 st@1000000000

The example has two spans: the first starts at bucket index 0 with 3 consecutive buckets, then skips 2 empty buckets, then 2 more consecutive buckets. The total bucket count is 3 + 2 = 5, matching the five values in positive_buckets.

See: Histogram with Native Buckets in the OM 2.0 spec.

Combined Classic and Native

Some consumers do not support native buckets yet. A combined CompositeValue includes both representations in a single line, providing backward compatibility. The field order is: count, sum, then all native fields (schema through positive_buckets), then the classic bucket field. This ordering lets parsers that prefer native buckets stop reading before the classic bucket list.

# TYPE http_request_duration_seconds histogram
http_request_duration_seconds {count:59,sum:120.0,schema:3,zero_threshold:1e-4,zero_count:2,positive_spans:[0:3,2:2],positive_buckets:[10,15,12,8,12],bucket:[0.01:5,0.1:25,1.0:47,10.0:57,+Inf:59]} st@1000000000

See: Histogram with both Classic and Native Buckets in the OM 2.0 spec.

GaugeHistogram with Native Buckets

GaugeHistogram Samples with native buckets follow the same syntax as histogram, except using gcount and gsum instead of count and sum. The TYPE line also distinguishes them.

See: GaugeHistogram with Native Buckets in the OM 2.0 spec.

Native Histograms in Practice

A realistic exposition showing native-only and combined classic+native histograms together:

# TYPE http_request_duration_seconds histogram
http_request_duration_seconds{method="GET"} {count:59,sum:120.0,schema:3,zero_threshold:1e-4,zero_count:2,positive_spans:[0:3,2:2],positive_buckets:[10,15,12,8,12]} 1710000000 st@1000000000
http_request_duration_seconds{method="POST"} {count:34,sum:68.5,schema:3,zero_threshold:1e-4,zero_count:1,positive_spans:[0:2,3:1],positive_buckets:[8,12,13],bucket:[0.01:3,0.1:14,1.0:28,10.0:33,+Inf:34]} 1710000000 st@1000000000
# EOF

The GET line uses native-only fields. The POST line includes both native and classic bucket fields for consumers that do not yet support native buckets. The next section covers exemplar syntax that can be attached to these lines (see Exemplars).

Exemplars

OM 2.0 changes several exemplar rules. Mandatory timestamps, multiple exemplars per sample, and relaxed size limits all affect how exposers attach trace context to metrics.

Mandatory Timestamps

Breaking: Exemplar timestamps are no longer optional.

In OM 1.0, exemplar timestamps were optional (may). In OM 2.0, every exemplar must include a timestamp. This alone is a breaking change for OM 1.0 parsers that do not expect a timestamp after the exemplar value.

http_requests_total 1027 1710000000 # {trace_id="abc123",span_id="def456"} 1.0 1709999999

The exemplar (everything after #) has labels, a value (1.0), and a mandatory timestamp (1709999999).

Multiple Exemplars

Non-breaking: Single Exemplar still allowed.

In OM 1.0, each sample could have at most one exemplar. OM 2.0 allows multiple exemplars on a single sample. Each exemplar starts with # followed by its own label set, value, and timestamp.

http_requests_total 1027 1710000000 # {trace_id="abc123",span_id="def456"} 1.0 1709999999 # {trace_id="xyz789",span_id="uvw012"} 1.0 1709999800

W3C Trace Context Keys

OM 2.0 recommends using trace_id and span_id as exemplar label keys, following W3C Trace Context conventions. The examples above already demonstrate this. Using consistent key names lets consumers correlate metrics with distributed traces without per-exporter configuration.

Size Limit Relaxation

In OM 1.0, exemplar label sets had a hard 128-character limit. OM 2.0 removes this hard cap and replaces it with soft guidance: exposers should not emit excessively large exemplar label sets. This shifts the burden of imposing limits on scraped exemplars to the scraper, and will allow for more flexibility when using exemplars.

Histogram Exemplar Placement

In OM 1.0, histogram exemplars appeared on individual _bucket lines. In OM 2.0 with CompositeValue syntax, exemplars attach to the single sample line after the CompositeValue. Exemplars without labels must use an empty LabelSet {}. When attaching multiple exemplars to a Sample, use consistent label names across all exemplars on that Sample.

See: Exemplars in the OM 2.0 spec.

Unknown Type

Non-breaking

In OM 1.0, a Sample in a Metric with the Unknown type could only have a Number value. In OM 2.0, a Sample in a Metric with the Unknown type may also have a CompositeValue.

Since the Unknown type should not be used in general (it exists for third-party metrics where the type is indeterminate), this is a minor change. It primarily affects ingestors and libraries that need to handle arbitrary Unknown-typed data.

See: Unknown in the OM 2.0 spec.

On this page