import { Registry, Counter, Gauge, Histogram, collectDefaultMetrics } from 'prom-client'; import { config } from '../config/index.js'; export class MetricsService { private static instance: MetricsService; private registry: Registry; // --- Metrics Definition --- // Operational Events public operationalEventsTotal: Counter; // Subsystem Metrics public subsystemDurationSeconds: Histogram; public subsystemLastRunTimestamp: Gauge; public subsystemAlive: Gauge; // Exchange API Metrics public exchangeApiLatencySeconds: Histogram; // Risk & Capital Metrics public capitalInvariantViolationsTotal: Counter; public profileUtilizationPercent: Gauge; // Reconciliation Metrics public reconciliationMismatchesTotal: Counter; public reconciliationMissingItemsCount: Gauge; private constructor() { this.registry = new Registry(); // Dynamic labels for every metric const env = process.env.NODE_ENV || 'development'; const mode = config.PAPER_TRADING ? 'paper' : 'live'; this.registry.setDefaultLabels({ app: 'bytelyst-trading-bot-service', env, mode }); collectDefaultMetrics({ register: this.registry }); // operational_events_total this.operationalEventsTotal = new Counter({ name: 'bytelyst_bot_operational_events_total', help: 'Cumulative count of operational events emitted by the system', labelNames: ['severity', 'type', 'profile_id', 'symbol'], registers: [this.registry] }); // subsystem_duration_seconds this.subsystemDurationSeconds = new Histogram({ name: 'bytelyst_bot_subsystem_duration_seconds', help: 'Duration of subsystem loop executions', labelNames: ['subsystem'], buckets: [0.1, 0.25, 0.5, 1, 2, 5, 10], registers: [this.registry] }); // subsystem_last_run_timestamp this.subsystemLastRunTimestamp = new Gauge({ name: 'bytelyst_bot_subsystem_last_run_timestamp', help: 'Unix timestamp of the last successful run of a subsystem', labelNames: ['subsystem'], registers: [this.registry] }); // subsystem_alive this.subsystemAlive = new Gauge({ name: 'bytelyst_bot_subsystem_alive', help: 'High-level availability flag for subsystems (1=alive, 0=stalled)', labelNames: ['subsystem'], registers: [this.registry] }); // exchange_api_latency_seconds this.exchangeApiLatencySeconds = new Histogram({ name: 'bytelyst_bot_exchange_api_latency_seconds', help: 'Latency of outgoing exchange API requests', labelNames: ['exchange', 'operation'], buckets: [0.05, 0.1, 0.25, 0.5, 1, 2, 5], registers: [this.registry] }); // capital_invariant_violations_total this.capitalInvariantViolationsTotal = new Counter({ name: 'bytelyst_bot_capital_invariant_violations_total', help: 'Cumulative count of capital invariant violations per profile', labelNames: ['profile_id'], registers: [this.registry] }); // profile_utilization_percent this.profileUtilizationPercent = new Gauge({ name: 'bytelyst_bot_profile_utilization_percent', help: 'Percentage of allocated capital currently tied up in positions/orders', labelNames: ['profile_id'], registers: [this.registry] }); // reconciliation_mismatches_total this.reconciliationMismatchesTotal = new Counter({ name: 'bytelyst_bot_reconciliation_mismatches_total', help: 'Cumulative count of detected state mismatches between DB and Exchange', registers: [this.registry] }); // reconciliation_missing_items_count this.reconciliationMissingItemsCount = new Gauge({ name: 'bytelyst_bot_reconciliation_missing_items_count', help: 'Count of missing items detected in the last reconciliation cycle', labelNames: ['source'], registers: [this.registry] }); } public static getInstance(): MetricsService { if (!MetricsService.instance) { MetricsService.instance = new MetricsService(); } return MetricsService.instance; } public async getMetrics(): Promise { return await this.registry.metrics(); } public getContentType(): string { return this.registry.contentType; } } export const metrics = MetricsService.getInstance();