feat(socket): integrate circuit breaker with Socket operations
- Add circuit breaker configuration options to SocketConfig type - Initialize circuit breakers for query, connection, and pre-key operations - Wrap query() with circuit breaker protection for all WhatsApp queries - Wrap sendRawMessage() with connection circuit breaker - Integrate pre-key circuit breaker with uploadPreKeys() - Add circuit breaker utilities to socket return object (stats, reset) - Clean up circuit breakers on connection close - Fix TypeScript errors in utility files (prometheus-metrics, logger-adapter, etc.) Circuit breakers provide: - Sliding window failure tracking - Automatic state transitions (closed -> open -> half-open) - Configurable thresholds and timeouts - Prometheus metrics integration - Event callbacks for monitoring
This commit is contained in:
@@ -321,7 +321,8 @@ export class BaileysEventStream extends EventEmitter {
|
||||
// Find correct position
|
||||
let insertIndex = this.buffer.length
|
||||
for (let i = 0; i < this.buffer.length; i++) {
|
||||
if (PRIORITY_VALUES[this.buffer[i].priority] > eventPriorityValue) {
|
||||
const bufferEvent = this.buffer[i]
|
||||
if (bufferEvent && PRIORITY_VALUES[bufferEvent.priority] > eventPriorityValue) {
|
||||
insertIndex = i
|
||||
break
|
||||
}
|
||||
|
||||
@@ -331,8 +331,29 @@ export class BaileysLogger implements ILogger {
|
||||
...(typeof sanitizedObj === 'object' && sanitizedObj !== null ? sanitizedObj : { value: sanitizedObj }),
|
||||
}
|
||||
|
||||
// Structured log
|
||||
this.structuredLogger[level](enrichedObj, msg)
|
||||
// Structured log (using type-safe method call)
|
||||
switch (level) {
|
||||
case 'trace':
|
||||
this.structuredLogger.trace(enrichedObj, msg)
|
||||
break
|
||||
case 'debug':
|
||||
this.structuredLogger.debug(enrichedObj, msg)
|
||||
break
|
||||
case 'info':
|
||||
this.structuredLogger.info(enrichedObj, msg)
|
||||
break
|
||||
case 'warn':
|
||||
this.structuredLogger.warn(enrichedObj, msg)
|
||||
break
|
||||
case 'error':
|
||||
this.structuredLogger.error(enrichedObj, msg)
|
||||
break
|
||||
case 'fatal':
|
||||
this.structuredLogger.fatal(enrichedObj, msg)
|
||||
break
|
||||
default:
|
||||
this.structuredLogger.info(enrichedObj, msg)
|
||||
}
|
||||
|
||||
// Event handler
|
||||
if (this.config.eventHandler) {
|
||||
@@ -429,8 +450,10 @@ export class BaileysLogger implements ILogger {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
// In production, mask part of the number
|
||||
const parts = jid.split('@')
|
||||
if (parts.length === 2 && parts[0].length > 4) {
|
||||
return `${parts[0].substring(0, 4)}****@${parts[1]}`
|
||||
const localPart = parts[0]
|
||||
const domainPart = parts[1]
|
||||
if (parts.length === 2 && localPart && domainPart && localPart.length > 4) {
|
||||
return `${localPart.substring(0, 4)}****@${domainPart}`
|
||||
}
|
||||
}
|
||||
return jid
|
||||
|
||||
@@ -500,7 +500,8 @@ export function withCache<T extends (...args: unknown[]) => unknown>(
|
||||
/**
|
||||
* Global singleton cache by namespace
|
||||
*/
|
||||
const globalCaches: Map<string, Cache<unknown>> = new Map()
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const globalCaches: Map<string, Cache<any>> = new Map()
|
||||
|
||||
export function getGlobalCache<V>(namespace: string, options?: CacheOptions<V>): Cache<V> {
|
||||
if (!globalCaches.has(namespace)) {
|
||||
|
||||
@@ -120,7 +120,7 @@ export class LoggerAdapter implements ILogger {
|
||||
}
|
||||
|
||||
if (this.config.levelMapping && level in this.config.levelMapping) {
|
||||
return this.config.levelMapping[level]
|
||||
return this.config.levelMapping[level] || 'info'
|
||||
}
|
||||
|
||||
return (level as LogLevel) || 'info'
|
||||
@@ -327,8 +327,12 @@ function createLogMethod(
|
||||
return (obj: unknown, msg?: string) => {
|
||||
if (typeof logger[level] === 'function') {
|
||||
;(logger[level] as (obj: unknown, msg?: string) => void)(obj, msg)
|
||||
} else if (typeof (console as Record<string, unknown>)[level] === 'function') {
|
||||
;(console as Record<string, (...args: unknown[]) => void>)[level](obj, msg)
|
||||
} else {
|
||||
// Fallback to console
|
||||
const consoleMethod = (console as unknown as Record<string, (...args: unknown[]) => void>)[level]
|
||||
if (typeof consoleMethod === 'function') {
|
||||
consoleMethod(obj, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,10 +612,10 @@ export class MetricsRegistry {
|
||||
/**
|
||||
* Return metrics in Prometheus format
|
||||
*/
|
||||
async metrics(): Promise<string> {
|
||||
async getMetricsOutput(): Promise<string> {
|
||||
const lines: string[] = []
|
||||
|
||||
for (const [name, metric] of this.metrics) {
|
||||
for (const [name, metric] of this.metrics.entries()) {
|
||||
lines.push(`# HELP ${name} ${metric.help}`)
|
||||
lines.push(`# TYPE ${name} ${metric.type}`)
|
||||
|
||||
@@ -776,7 +776,7 @@ export const metrics = {
|
||||
*/
|
||||
export function createMetricsHandler(registry: MetricsRegistry = baileysMetrics) {
|
||||
return async (_req: unknown, res: { setHeader: (name: string, value: string) => void; end: (body: string) => void }) => {
|
||||
const metricsOutput = await registry.metrics()
|
||||
const metricsOutput = await registry.getMetricsOutput()
|
||||
res.setHeader('Content-Type', registry.contentType())
|
||||
res.end(metricsOutput)
|
||||
}
|
||||
|
||||
@@ -554,9 +554,10 @@ export function extractTraceHeaders(headers: Record<string, string | undefined>)
|
||||
}
|
||||
|
||||
// Parse baggage
|
||||
if (headers[TRACE_HEADERS.BAGGAGE]) {
|
||||
const baggageHeader = headers[TRACE_HEADERS.BAGGAGE]
|
||||
if (baggageHeader) {
|
||||
options.baggage = {}
|
||||
const pairs = headers[TRACE_HEADERS.BAGGAGE].split(',')
|
||||
const pairs = baggageHeader.split(',')
|
||||
for (const pair of pairs) {
|
||||
const [key, value] = pair.split('=')
|
||||
if (key && value) {
|
||||
|
||||
Reference in New Issue
Block a user