@@ -38,6 +38,60 @@ import * as promClient from 'prom-client'
|
|||||||
// Create a custom registry to avoid conflicts with global registry
|
// Create a custom registry to avoid conflicts with global registry
|
||||||
const customRegistry = new promClient.Registry()
|
const customRegistry = new promClient.Registry()
|
||||||
|
|
||||||
|
// Flags to prevent multiple configuration calls (thread-safety and memory leak prevention)
|
||||||
|
let defaultMetricsCollected = false
|
||||||
|
let registryConfigured = false
|
||||||
|
let configuredPrefix = ''
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the custom registry with default labels
|
||||||
|
* Thread-safe: Only executes once, subsequent calls are no-ops
|
||||||
|
* To clear default labels, pass an empty object {} explicitly
|
||||||
|
*/
|
||||||
|
function configureRegistry(defaultLabels?: Labels): void {
|
||||||
|
if (registryConfigured) {
|
||||||
|
return // Already configured, ignore subsequent calls
|
||||||
|
}
|
||||||
|
registryConfigured = true
|
||||||
|
|
||||||
|
// Allow setting empty labels to clear previous (useful for testing/reconfiguration)
|
||||||
|
if (defaultLabels !== undefined) {
|
||||||
|
customRegistry.setDefaultLabels(defaultLabels)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the metric prefix - thread-safe, only first call takes effect
|
||||||
|
*/
|
||||||
|
function setMetricPrefix(prefix: string): void {
|
||||||
|
if (configuredPrefix === '') {
|
||||||
|
configuredPrefix = prefix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFullMetricName(name: string): string {
|
||||||
|
return configuredPrefix ? `${configuredPrefix}_${name}` : name
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset all configuration flags - FOR TESTING ONLY
|
||||||
|
* Allows reconfiguration in test environments
|
||||||
|
*/
|
||||||
|
export function resetMetricsConfiguration(): void {
|
||||||
|
registryConfigured = false
|
||||||
|
defaultMetricsCollected = false
|
||||||
|
configuredPrefix = ''
|
||||||
|
// Clear all metrics from custom registry
|
||||||
|
customRegistry.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current configured prefix - FOR TESTING/DEBUGGING
|
||||||
|
*/
|
||||||
|
export function getConfiguredPrefix(): string {
|
||||||
|
return configuredPrefix
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// Configuration
|
// Configuration
|
||||||
// ============================================
|
// ============================================
|
||||||
@@ -186,8 +240,9 @@ export class Counter implements BaseMetric {
|
|||||||
public help: string,
|
public help: string,
|
||||||
public labelNames: string[] = []
|
public labelNames: string[] = []
|
||||||
) {
|
) {
|
||||||
|
const fullName = getFullMetricName(name)
|
||||||
this.promCounter = new promClient.Counter({
|
this.promCounter = new promClient.Counter({
|
||||||
name,
|
name: fullName,
|
||||||
help,
|
help,
|
||||||
labelNames,
|
labelNames,
|
||||||
registers: [customRegistry], // Use custom registry, not global
|
registers: [customRegistry], // Use custom registry, not global
|
||||||
@@ -290,8 +345,9 @@ export class Gauge implements BaseMetric {
|
|||||||
public help: string,
|
public help: string,
|
||||||
public labelNames: string[] = []
|
public labelNames: string[] = []
|
||||||
) {
|
) {
|
||||||
|
const fullName = getFullMetricName(name)
|
||||||
this.promGauge = new promClient.Gauge({
|
this.promGauge = new promClient.Gauge({
|
||||||
name,
|
name: fullName,
|
||||||
help,
|
help,
|
||||||
labelNames,
|
labelNames,
|
||||||
registers: [customRegistry], // Use custom registry, not global
|
registers: [customRegistry], // Use custom registry, not global
|
||||||
@@ -460,8 +516,9 @@ export class Histogram implements BaseMetric {
|
|||||||
buckets: number[] = DEFAULT_BUCKETS
|
buckets: number[] = DEFAULT_BUCKETS
|
||||||
) {
|
) {
|
||||||
this.buckets = [...buckets].sort((a, b) => a - b)
|
this.buckets = [...buckets].sort((a, b) => a - b)
|
||||||
|
const fullName = getFullMetricName(name)
|
||||||
this.promHistogram = new promClient.Histogram({
|
this.promHistogram = new promClient.Histogram({
|
||||||
name,
|
name: fullName,
|
||||||
help,
|
help,
|
||||||
labelNames,
|
labelNames,
|
||||||
buckets: this.buckets,
|
buckets: this.buckets,
|
||||||
@@ -645,8 +702,9 @@ export class Summary implements BaseMetric {
|
|||||||
options: { percentiles?: number[]; maxAgeSeconds?: number; ageBuckets?: number } = {}
|
options: { percentiles?: number[]; maxAgeSeconds?: number; ageBuckets?: number } = {}
|
||||||
) {
|
) {
|
||||||
this.percentiles = options.percentiles ?? DEFAULT_PERCENTILES
|
this.percentiles = options.percentiles ?? DEFAULT_PERCENTILES
|
||||||
|
const fullName = getFullMetricName(name)
|
||||||
this.promSummary = new promClient.Summary({
|
this.promSummary = new promClient.Summary({
|
||||||
name,
|
name: fullName,
|
||||||
help,
|
help,
|
||||||
labelNames,
|
labelNames,
|
||||||
percentiles: this.percentiles,
|
percentiles: this.percentiles,
|
||||||
@@ -1116,13 +1174,21 @@ export class MetricsServer {
|
|||||||
return this.startPromise
|
return this.startPromise
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Configure prefix and registry BEFORE creating Promise to avoid race conditions
|
||||||
|
// These functions are thread-safe and only execute once
|
||||||
|
setMetricPrefix(this.config.prefix)
|
||||||
|
configureRegistry(this.config.defaultLabels)
|
||||||
|
|
||||||
// Cache the promise so concurrent calls get the same one
|
// Cache the promise so concurrent calls get the same one
|
||||||
this.startPromise = new Promise<void>((resolve, reject) => {
|
this.startPromise = new Promise<void>((resolve, reject) => {
|
||||||
// Enable prom-client's default Node.js metrics collection
|
|
||||||
if (this.config.collectDefaultMetrics) {
|
// Enable prom-client's default Node.js metrics collection (only once to prevent memory leak)
|
||||||
|
if (this.config.collectDefaultMetrics && !defaultMetricsCollected) {
|
||||||
|
defaultMetricsCollected = true
|
||||||
promClient.collectDefaultMetrics({
|
promClient.collectDefaultMetrics({
|
||||||
prefix: this.config.prefix ? `${this.config.prefix}_` : '',
|
prefix: this.config.prefix ? `${this.config.prefix}_` : '',
|
||||||
labels: this.config.defaultLabels,
|
labels: this.config.defaultLabels,
|
||||||
|
register: customRegistry, // Use custom registry, not global
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1228,6 +1294,13 @@ export class MetricsServer {
|
|||||||
*/
|
*/
|
||||||
const metricsConfig = loadMetricsConfig()
|
const metricsConfig = loadMetricsConfig()
|
||||||
|
|
||||||
|
// Initialize prefix and defaultLabels for all metrics created after this point
|
||||||
|
setMetricPrefix(metricsConfig.prefix)
|
||||||
|
configureRegistry({
|
||||||
|
prefix: metricsConfig.prefix,
|
||||||
|
defaultLabels: metricsConfig.defaultLabels,
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Global registry for Baileys metrics
|
* Global registry for Baileys metrics
|
||||||
* Uses prefix and default labels from environment variables
|
* Uses prefix and default labels from environment variables
|
||||||
|
|||||||
Reference in New Issue
Block a user