Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ project adheres to [Semantic Versioning](http://semver.org/).

- Record cluster and worker thread scrape failures in internal histograms,
including timeouts, worker-reported errors, and failures with no known worker errors.
- Prometheus protobuf registries for classic metrics, with public
content-type constants and TypeScript support for binary output.

## [0.16.0] - 2026-08-24

Expand Down
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -397,11 +397,12 @@ enabled. They get a single object with the format
`{labels, value, exemplarLabels}`.

When using exemplars, the registry used for metrics should be set to OpenMetrics
type (including the global or default registry if no registries are specified).
or Prometheus protobuf (including the global or default registry if no registries
are specified).

### Registry type

The library supports both the old Prometheus format and the OpenMetrics format.
The library supports Prometheus text, OpenMetrics text, and Prometheus protobuf.
The format can be set per registry. For default metrics:

```js
Expand All @@ -419,9 +420,14 @@ this is currently the default registry type.
**OPENMETRICS_CONTENT_TYPE** - defaults to version 1.0.0 of the
[OpenMetrics standard](https://github.com/OpenObservability/OpenMetrics/blob/d99b705f611b75fec8f450b05e344e02eea6921d/specification/OpenMetrics.md).

**PROMETHEUS_PROTOBUF_CONTENT_TYPE** - length-delimited Prometheus protobuf.
`metrics()` returns a `Buffer` for this format; `getMetricsAsString()` and
`getSingleMetricAsString()` still return Prometheus text.

The HTTP Content-Type string for each registry type is exposed both at module
level (`prometheusContentType` and `openMetricsContentType`) and as static
properties on the `Registry` object.
level (`prometheusContentType`, `openMetricsContentType`, and
`prometheusProtobufContentType`) and as static properties on the `Registry`
object.

The `contentType` constant exposed by the module returns the default content
type when creating a new registry, currently defaults to Prometheus type.
Expand Down
113 changes: 63 additions & 50 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,32 @@ export type OpenMetricsContentType =
export type PrometheusContentType =
`${PrometheusMIME}; version=${PrometheusMetricsVersion}; charset=${Charset}`;

export type PrometheusProtobufContentType =
'application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited';

export type RegistryContentType =
PrometheusContentType | OpenMetricsContentType;
| PrometheusContentType
| OpenMetricsContentType
| PrometheusProtobufContentType;

export type RegistryMetrics<T extends RegistryContentType> =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Changing the constuctor seemed suspicious, I asked LLM and LLM says this and the constructor change is breaking the API, suggests this fix and text:

diff --git a/index.d.ts b/index.d.ts
@@ -36,14 +36,19 @@ export type RegistryContentType =
 	| OpenMetricsContentType
 	| PrometheusProtobufContentType;
 
-export type RegistryMetrics<T extends RegistryContentType> =
-	T extends PrometheusProtobufContentType ? Uint8Array : string;
+// Not distributive, so a Registry whose content type is not known statically
+// keeps returning a string, as it did before protobuf was supported.
+export type RegistryMetrics<T extends RegistryContentType> = [T] extends [
+	PrometheusProtobufContentType,
+]
+	? Uint8Array
+	: string;
 
 /**
  * Container for all registered metrics
  */
 export class Registry<
-	BoundRegistryContentType extends RegistryContentType = PrometheusContentType,
+	BoundRegistryContentType extends RegistryContentType = RegistryContentType,
 > {
diff --git a/test/protobufTypes.ts b/test/protobufTypes.ts
@@ -52,7 +52,11 @@ const openMetrics: Promise<string> = new Registry(
 	Registry.OPENMETRICS_CONTENT_TYPE,
 ).metrics();
 const unknownFormat: Registry<RegistryContentType> = registry;
-const unknownBody: Promise<string | Uint8Array> = unknownFormat.metrics();
+const unknownBody: Promise<string> = unknownFormat.metrics();
+const plainOpenMetrics: Registry = new Registry(
+	Registry.OPENMETRICS_CONTENT_TYPE,
+);
+const plainProtobuf: Registry = registry;
 void [
 	binary,
 	oneText,
@@ -63,6 +67,8 @@ void [
 	text,
 	openMetrics,
 	unknownBody,
+	plainOpenMetrics,
+	plainProtobuf,
 ];

T extends PrometheusProtobufContentType ? Uint8Array : string;

/**
* Container for all registered metrics
*/
export class Registry<
BoundRegistryContentType extends RegistryContentType = RegistryContentType,
BoundRegistryContentType extends RegistryContentType = PrometheusContentType,
> {
/**
* @param regContentType The content type of the registry
*/
constructor(regContentType?: RegistryContentType);
constructor(regContentType?: BoundRegistryContentType);

/**
* Get string representation for all metrics
* Get metrics as text, or a Buffer for a Prometheus protobuf registry
*/
metrics(): Promise<string>;
metrics(): Promise<RegistryMetrics<BoundRegistryContentType>>;

/**
* Remove all metrics from the registry
Expand Down Expand Up @@ -77,8 +85,8 @@ export class Registry<
): Promise<MetricObjectWithValues<MetricValue<string>>[]>;

/**
* Get string representation for a metric
* @param metric Metric to convert to a string
* Get a metric as text
* @param metric Metric to serialize
*/
getMetricsAsString<T extends string>(metric: Metric<T>): Promise<string>;

Expand Down Expand Up @@ -113,30 +121,30 @@ export class Registry<
setDefaultLabels(labels: object): void;

/**
* Get a string representation of a single metric by name
* Get a single metric as text
* @param name The name of the metric
*/
getSingleMetricAsString(name: string): Promise<string>;

/**
* Gets the Content-Type of the metrics for use in the response headers.
*/
readonly contentType: PrometheusContentType | OpenMetricsContentType;
readonly contentType: RegistryContentType;

/**
* Set the content type of a registry. Used to change between Prometheus and
* OpenMetrics versions.
* Set the content type of a registry. The returned registry has the new
* serialization return type; use it when switching between text and protobuf.
* @param contentType The type of the registry
*/
setContentType(
contentType: PrometheusContentType | OpenMetricsContentType,
): void;
setContentType<T extends RegistryContentType>(contentType: T): Registry<T>;

/**
* Merge registers
* @param registers The registers you want to merge together
*/
static merge(registers: Registry[]): Registry;
static merge<T extends RegistryContentType>(
registers: Registry<T>[],
): Registry<T>;

/**
* Creates a new Registry instance from an array of metrics that were
Expand All @@ -147,9 +155,10 @@ export class Registry<
* `registry.getMetricsAsJSON()`.
* @returns {Registry} aggregated registry.
*/
static aggregate<T extends RegistryContentType>(
static aggregate<T extends RegistryContentType = PrometheusContentType>(
metricsArr: Array<object>,
): Registry<T>; // TODO Promise?
registryType?: T,
): Registry<T>;

/**
* HTTP Prometheus Content-Type for metrics response headers.
Expand All @@ -160,6 +169,11 @@ export class Registry<
* HTTP OpenMetrics Content-Type for metrics response headers.
*/
static OPENMETRICS_CONTENT_TYPE: OpenMetricsContentType;

/**
* HTTP Content-Type for length-delimited Prometheus protobuf metrics.
*/
static PROMETHEUS_PROTOBUF_CONTENT_TYPE: PrometheusProtobufContentType;
}
export type Collector = () => void;

Expand All @@ -184,15 +198,23 @@ export const prometheusContentType: PrometheusContentType;
*/
export const openMetricsContentType: OpenMetricsContentType;

/**
* HTTP Content-Type for length-delimited Prometheus protobuf metrics.
*/
export const prometheusProtobufContentType: PrometheusProtobufContentType;

export class ClusterRegistry<
T extends RegistryContentType,
T extends RegistryContentType = PrometheusContentType,
> extends Registry<T> {
setContentType<U extends RegistryContentType>(
contentType: U,
): ClusterRegistry<U>;

/**
* Gets aggregated metrics for all workers.
* @returns {Promise<string>} Promise that resolves with the aggregated
* metrics.
* @returns Promise that resolves with text or protobuf bytes.
*/
clusterMetrics(): Promise<string>;
clusterMetrics(): Promise<RegistryMetrics<T>>;

/**
* Sets the registry or registries to be aggregated. Call from workers to
Expand All @@ -202,22 +224,22 @@ export class ClusterRegistry<
* @returns {void}
*/
static setRegistries(
regs:
| Array<
Registry<PrometheusContentType> | Registry<OpenMetricsContentType>
>
| Registry<PrometheusContentType>
| Registry<OpenMetricsContentType>,
regs: Registry<RegistryContentType>[] | Registry<RegistryContentType>,
): void;
}

export class WorkerRegistry<T extends RegistryContentType> extends Registry<T> {
export class WorkerRegistry<
T extends RegistryContentType = PrometheusContentType,
> extends Registry<T> {
setContentType<U extends RegistryContentType>(
contentType: U,
): WorkerRegistry<U>;

/**
* Gets aggregated metrics for all workers.
* @returns {Promise<string>} Promise that resolves with the aggregated
* metrics.
* @returns Promise that resolves with text or protobuf bytes.
*/
workerMetrics(): Promise<string>;
workerMetrics(): Promise<RegistryMetrics<T>>;

/**
* Orderly shutdown of the registry.
Expand All @@ -238,27 +260,25 @@ export class WorkerRegistry<T extends RegistryContentType> extends Registry<T> {
* @returns {void}
*/
static setRegistries(
regs:
| Array<
Registry<PrometheusContentType> | Registry<OpenMetricsContentType>
>
| Registry<PrometheusContentType>
| Registry<OpenMetricsContentType>,
regs: Registry<RegistryContentType>[] | Registry<RegistryContentType>,
): void;
}

/**
* @deprecated
*/
export class AggregatorRegistry<
T extends RegistryContentType,
T extends RegistryContentType = PrometheusContentType,
> extends Registry<T> {
setContentType<U extends RegistryContentType>(
contentType: U,
): AggregatorRegistry<U>;

/**
* Gets aggregated metrics for all workers.
* @returns {Promise<string>} Promise that resolves with the aggregated
* metrics.
* @returns Promise that resolves with text or protobuf bytes.
*/
clusterMetrics(): Promise<string>;
clusterMetrics(): Promise<RegistryMetrics<T>>;

/**
* Orderly shutdown of the registry.
Expand All @@ -279,12 +299,7 @@ export class AggregatorRegistry<
* @returns {void}
*/
static setRegistries(
regs:
| Array<
Registry<PrometheusContentType> | Registry<OpenMetricsContentType>
>
| Registry<PrometheusContentType>
| Registry<OpenMetricsContentType>,
regs: Registry<RegistryContentType>[] | Registry<RegistryContentType>,
): void;
}

Expand Down Expand Up @@ -340,9 +355,7 @@ interface MetricConfiguration<T extends string> {
name: string;
help: string;
labelNames?: T[] | readonly T[];
registers?: (
Registry<PrometheusContentType> | Registry<OpenMetricsContentType>
)[];
registers?: Registry<RegistryContentType>[];
aggregator?: Aggregator;
collect?: CollectFunction<any>;
enableExemplars?: boolean;
Expand Down
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ Object.defineProperty(exports, 'contentType', {
});
exports.prometheusContentType = exports.Registry.PROMETHEUS_CONTENT_TYPE;
exports.openMetricsContentType = exports.Registry.OPENMETRICS_CONTENT_TYPE;
exports.prometheusProtobufContentType =
exports.Registry.PROMETHEUS_PROTOBUF_CONTENT_TYPE;
exports.validateMetricName = require('./lib/validation').validateMetricName;

exports.Counter = require('./lib/counter');
Expand Down
15 changes: 7 additions & 8 deletions lib/cluster.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,11 @@ class AggregatorRegistry extends Registry {
/**
* Gets aggregated metrics for all workers. The optional callback and
* returned Promise resolve with the same value; either may be used.
* @returns {Promise<string>} Promise that resolves with the aggregated
* @returns {Promise<string | Buffer>} Promise that resolves with the aggregated
* metrics.
*/
async clusterMetrics() {
const contentType = this.contentType;
const requestId = requestCtr++;
const orderedWorkers = [...workers.values()]
.filter(worker => worker.isConnected())
Expand All @@ -102,7 +103,7 @@ class AggregatorRegistry extends Registry {
responseHandlers,
workerFailures: 0,
promise: waitFor(
this.#gather(requestId, metricSnapshot, responsePromises),
this.#gather(requestId, metricSnapshot, responsePromises, contentType),
5_000,
),
};
Expand Down Expand Up @@ -149,15 +150,13 @@ class AggregatorRegistry extends Registry {
* @param requestId {number}
* @param {any[]} historical - Previously collected values
* @param promises {Promise[]}
* @returns {Promise<string>}
* @param contentType {string}
* @returns {Promise<string | Buffer>}
*/
async #gather(requestId, historical, promises) {
async #gather(requestId, historical, promises, contentType) {
const responses = await Promise.all(promises);
const metrics = responses.flatMap(response => response.metrics);
return Registry.aggregate(
[historical, ...metrics],
this.contentType,
).metrics();
return Registry.aggregate([historical, ...metrics], contentType).metrics();
}

get contentType() {
Expand Down
2 changes: 1 addition & 1 deletion lib/metric.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class Metric {
register.contentType === Registry.PROMETHEUS_CONTENT_TYPE
) {
throw new TypeError(
'Exemplars are supported only on OpenMetrics registries',
'Exemplars are supported only on OpenMetrics or Prometheus protobuf registries',
);
}
register.registerMetric(this);
Expand Down
Loading
Loading