@sourceregistry/node-prometheus
Version:
A lightweight, zero-dependency TypeScript library for creating and exposing Prometheus metrics in standard exposition and OpenMetrics format.
307 lines (306 loc) • 8.7 kB
JavaScript
class n {
/**
* Creates a new Metric instance.
*
* @param type - The Prometheus metric type.
* @param name - The raw metric name (will be cleaned).
* @param description - Optional description for # HELP.
* @param labels - Optional default labels.
*/
constructor(t, e, r, s) {
this.type = t, this.name = n.cleanText(e) || "", this.description = r, this.labels = s;
}
/**
* Converts a label object to a Prometheus label string (e.g., `{foo="bar"}`).
*
* @param labels - The label key-value pairs.
* @returns The formatted label string.
*/
static labelString(t) {
return !t || Object.keys(t).length === 0 ? "" : `{${Object.entries(t).map(([e, r]) => `${e}="${String(r)}"`).join(",")}}`;
}
/**
* Sanitizes a metric name by removing or replacing invalid characters.
*
* Prometheus metric names must match [a-zA-Z_:][a-zA-Z0-9_:]*
* This removes hyphens, parens; replaces slashes and spaces with underscores.
*
* @param input - The raw metric name.
* @returns The cleaned metric name.
*/
static cleanText(t) {
return t?.replaceAll("-", "").replaceAll("/", "_").replaceAll(" ", "_").replaceAll("(", "").replaceAll(")", "");
}
/**
* Concatenates multiple metrics into a single exposition string.
*
* @param format - used to set the metric type
* @param metrics - The metrics to serialize.
* @returns A Promise resolving to the combined string.
*/
static async concat(t = "prometheus", ...e) {
let s = (await Promise.all(e.map((u) => u.stringify()))).join(`
`);
return t === "openmetrics" && (s = s.trimEnd() + `
# EOF`), s;
}
/**
* Generates the common header lines (# HELP, # TYPE) for this metric.
* Called by subclasses before serializing their specific values.
*
* @returns The header string.
*/
generateHeader() {
let t = "";
return this.name && this.description && (t += `# HELP ${this.name} ${this.description}
`), this.type !== "untyped" && (t += `# TYPE ${this.name} ${this.type}
`), t;
}
}
class a extends n {
/**
* Creates a new ValueMetric.
*
* @param type - The metric type.
* @param config - Configuration object.
* @param config.name - Metric name.
* @param config.description - Optional description.
* @param config.labels - Optional default labels.
* @param config.reader - Async or sync function returning values.
*/
constructor(t, e) {
super(t, e.name, e.description, e.labels), this._reader = async () => (await e.reader()).map((s) => {
if (typeof s == "number")
return [s, {}, Date.now()];
if (Array.isArray(s))
return s.length === 2 ? typeof s[1] == "number" ? [s[0], {}, s[1]] : [s[0], s[1], Date.now()] : s;
throw new Error(`Unexpected value format: ${JSON.stringify(s)}`);
});
}
/**
* Serializes the current values with labels and timestamps.
*
* @returns Promise resolving to value lines.
*/
async valueString() {
return (await this._reader()).map(
([e, r, s]) => `${this.name}${n.labelString({ ...this.labels, ...r })} ${e} ${s}
`
).join("");
}
/**
* @inheritdoc
*/
async stringify() {
return `${super.generateHeader()}${await this.valueString()}
`;
}
}
class l extends a {
/**
* Creates a new Gauge.
*
* @param config - Configuration object.
*/
constructor(t) {
super("gauge", t);
}
}
class c extends a {
/**
* Creates a new Counter.
*
* @param config - Configuration object.
*/
constructor(t) {
super("counter", t);
}
/**
* Increments the counter by a given value (convenience alias).
* Note: Since Counter uses a reader, this is just documentation — actual increment
* must be handled in the reader function or external state.
*
* @param delta - Amount to increment by (default: 1).
*/
inc(t = 1) {
console.warn(
`Counter.inc() called, but Counter uses reader function. Increment must be handled externally. Delta: ${t}`
);
}
}
class h extends n {
/**
* Creates a new Histogram.
*
* @param config - Configuration object.
* @param config.name - Metric name.
* @param config.description - Optional description.
* @param config.buckets - Optional bucket thresholds (default: []). +Inf always added.
* @param config.labels - Optional default labels.
*/
constructor(t) {
super("histogram", t.name, t.description, t.labels), this._bucketCounts = {}, this._sum = 0, this._count = 0;
const e = (t.buckets ?? []).sort((r, s) => r - s);
this._buckets = [...e, 1 / 0], this.reset();
}
/**
* Resets all bucket counts, sum, and total count to zero.
*/
reset() {
this._buckets.forEach((t) => this._bucketCounts[t] = 0), this._sum = 0, this._count = 0;
}
/**
* Observes a value, updating buckets, sum, and count.
*
* @param value - The observed value.
*/
observe(t) {
if (typeof t != "number" || isNaN(t))
throw new Error(`Invalid histogram value: ${t}`);
this.push(t);
}
/**
* Alias for observe().
*
* @param value - The observed value.
*/
push(t) {
this._sum += t, this._count++;
for (const e of this._buckets)
t <= e && this._bucketCounts[e]++;
}
/**
* Serializes bucket, sum, and count lines.
*
* @returns The serialized string.
*/
bucketString() {
return `${this._buckets.map((e) => {
const r = e === 1 / 0 ? "+Inf" : e;
return `${this.name}_bucket${n.labelString({ ...this.labels, le: r })} ${this._bucketCounts[e]}`;
}).join(`
`)}
${this.name}_sum ${this._sum}
${this.name}_count ${this._count}`;
}
/**
* @inheritdoc
*/
async stringify() {
return `${super.generateHeader()}${this.bucketString()}
`;
}
}
class o extends n {
/**
* Creates a new Summary.
*
* @param config - Configuration object.
* @param config.name - Metric name.
* @param config.description - Optional description.
* @param config.quantiles - Array of φ-quantiles (0 < φ < 1).
* @param config.calculate - Function to calculate quantile estimate given value and φ.
*/
constructor(t) {
if (super("summary", t.name, t.description), this._sum = 0, this._count = 0, !t.quantiles.length)
throw new Error("Summary must have at least one quantile");
this._quantiles = new Map(t.quantiles.map((e) => [e, 0])), this._calculate = t.calculate;
}
/**
* Observes a value, updating quantiles, sum, and count.
*
* @param value - The observed value.
*/
observe(t) {
if (typeof t != "number" || isNaN(t))
throw new Error(`Invalid summary value: ${t}`);
this.push(t);
}
/**
* Alias for observe().
*
* @param value - The observed value.
*/
push(t) {
this._sum += t, this._count++, this._quantiles.forEach((e, r) => {
const s = this._calculate(t, r);
this._quantiles.set(r, s);
});
}
/**
* Serializes quantile, sum, and count lines.
*
* @returns The serialized string.
*/
summaryString() {
return `${[...this._quantiles.entries()].sort(([e], [r]) => e - r).map(
([e, r]) => `${this.name}${n.labelString({ ...this.labels, quantile: e })} ${r}`
).join(`
`)}
${this.name}_sum ${this._sum}
${this.name}_count ${this._count}`;
}
/**
* @inheritdoc
*/
async stringify() {
return `${super.generateHeader()}${this.summaryString()}
`;
}
}
class m extends n {
// [value, timestamp]
/**
* Creates a new Untyped metric.
*
* @param config - Configuration object.
* @param config.name - Metric name.
* @param config.description - Optional description.
* @param config.labels - Optional default labels.
* @param config.value - Initial value or [value, timestamp] tuple.
*/
constructor(t) {
super("untyped", t.name, t.description, t.labels), this._value = Array.isArray(t.value) ? t.value : [t.value ?? 0, Date.now()];
}
/**
* Sets the current value and timestamp.
*
* @param value - New value or [value, timestamp] tuple.
*/
set(t) {
this._value = Array.isArray(t) ? t : [t, Date.now()];
}
/**
* Gets the current value and timestamp.
*
* @returns [value, timestamp] tuple.
*/
get() {
return this._value;
}
/**
* Serializes the current value.
*
* @returns The serialized string.
*/
valueString() {
return `${this.name}${n.labelString(this.labels)} ${this._value[0]} ${this._value[1]}
`;
}
/**
* @inheritdoc
*/
async stringify() {
return `${super.generateHeader()}${this.valueString()}
`;
}
}
export {
c as Counter,
l as Gauge,
h as Histogram,
n as Metric,
o as Summary,
m as Untyped
};
//# sourceMappingURL=index.es.js.map