refactor(utils): translate all comments to English and enhance circuit breaker

- Translate all Portuguese comments to English in utility modules
- Enhance circuit-breaker.ts with sliding window failure tracking
- Add factory functions for WhatsApp-specific circuit breakers
  (createPreKeyCircuitBreaker, createConnectionCircuitBreaker, createMessageCircuitBreaker)
- Improve volume threshold support for circuit breaker decisions
- Standardize documentation across all utility files

Files updated:
- structured-logger.ts: English comments
- baileys-logger.ts: English comments
- trace-context.ts: English comments
- prometheus-metrics.ts: English comments
- cache-utils.ts: English comments
- circuit-breaker.ts: Enhanced with sliding window + English comments
- retry-utils.ts: English comments
- baileys-event-stream.ts: English comments
- index.ts: English comments
This commit is contained in:
Claude
2026-01-20 01:48:23 +00:00
parent 882a1f57cf
commit 64c7712da7
9 changed files with 865 additions and 614 deletions
+86 -85
View File
@@ -1,15 +1,16 @@
/** /**
* @fileoverview Gerenciamento de eventos do Baileys * Baileys Event Stream Management
* @module Utils/baileys-event-stream
* *
* Fornece: * Provides:
* - Event buffering com backpressure * - Event buffering with backpressure
* - Event transformation e filtering * - Event transformation and filtering
* - Priority queues para eventos * - Priority queues for events
* - Batch processing * - Batch processing
* - Dead letter queue para eventos falhos * - Dead letter queue for failed events
* - Replay de eventos * - Event replay
* - Integração com logging e métricas * - Logging and metrics integration
*
* @module Utils/baileys-event-stream
*/ */
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
@@ -17,7 +18,7 @@ import { metrics } from './prometheus-metrics.js'
import type { BaileysLogCategory } from './baileys-logger.js' import type { BaileysLogCategory } from './baileys-logger.js'
/** /**
* Tipos de eventos do Baileys * Baileys event types
*/ */
export type BaileysEventType = export type BaileysEventType =
| 'connection.update' | 'connection.update'
@@ -41,15 +42,15 @@ export type BaileysEventType =
| 'call' | 'call'
| 'blocklist.set' | 'blocklist.set'
| 'blocklist.update' | 'blocklist.update'
| string // Para eventos customizados | string // For custom events
/** /**
* Prioridade de eventos * Event priority
*/ */
export type EventPriority = 'critical' | 'high' | 'normal' | 'low' export type EventPriority = 'critical' | 'high' | 'normal' | 'low'
/** /**
* Valores numéricos de prioridade * Numeric priority values
*/ */
const PRIORITY_VALUES: Record<EventPriority, number> = { const PRIORITY_VALUES: Record<EventPriority, number> = {
critical: 0, critical: 0,
@@ -59,7 +60,7 @@ const PRIORITY_VALUES: Record<EventPriority, number> = {
} }
/** /**
* Evento do stream * Stream event
*/ */
export interface StreamEvent<T = unknown> { export interface StreamEvent<T = unknown> {
id: string id: string
@@ -74,48 +75,48 @@ export interface StreamEvent<T = unknown> {
} }
/** /**
* Opções do Event Stream * Event Stream options
*/ */
export interface EventStreamOptions { export interface EventStreamOptions {
/** Tamanho máximo do buffer (default: 10000) */ /** Maximum buffer size (default: 10000) */
maxBufferSize?: number maxBufferSize?: number
/** Se deve aplicar backpressure quando buffer cheio */ /** Whether to apply backpressure when buffer is full */
enableBackpressure?: boolean enableBackpressure?: boolean
/** Limite de highWaterMark para backpressure */ /** High water mark limit for backpressure */
highWaterMark?: number highWaterMark?: number
/** Limite de lowWaterMark para retomar */ /** Low water mark limit to resume */
lowWaterMark?: number lowWaterMark?: number
/** Tamanho do batch para processamento */ /** Batch size for processing */
batchSize?: number batchSize?: number
/** Intervalo de flush em ms (0 = desabilitado) */ /** Flush interval in ms (0 = disabled) */
flushInterval?: number flushInterval?: number
/** Máximo de retries para eventos falhos */ /** Maximum retries for failed events */
maxRetries?: number maxRetries?: number
/** Tamanho da dead letter queue */ /** Dead letter queue size */
deadLetterQueueSize?: number deadLetterQueueSize?: number
/** Se deve coletar métricas */ /** Whether to collect metrics */
collectMetrics?: boolean collectMetrics?: boolean
/** Nome do stream para métricas */ /** Stream name for metrics */
streamName?: string streamName?: string
} }
/** /**
* Handler de evento * Event handler
*/ */
export type EventHandler<T = unknown> = (event: StreamEvent<T>) => void | Promise<void> export type EventHandler<T = unknown> = (event: StreamEvent<T>) => void | Promise<void>
/** /**
* Filtro de evento * Event filter
*/ */
export type EventFilter<T = unknown> = (event: StreamEvent<T>) => boolean export type EventFilter<T = unknown> = (event: StreamEvent<T>) => boolean
/** /**
* Transformador de evento * Event transformer
*/ */
export type EventTransformer<T = unknown, R = unknown> = (event: StreamEvent<T>) => StreamEvent<R> export type EventTransformer<T = unknown, R = unknown> = (event: StreamEvent<T>) => StreamEvent<R>
/** /**
* Resultado de processamento de batch * Batch processing result
*/ */
export interface BatchResult { export interface BatchResult {
processed: number processed: number
@@ -124,7 +125,7 @@ export interface BatchResult {
} }
/** /**
* Estatísticas do stream * Stream statistics
*/ */
export interface EventStreamStats { export interface EventStreamStats {
bufferSize: number bufferSize: number
@@ -140,7 +141,7 @@ export interface EventStreamStats {
} }
/** /**
* Mapeamento de tipo de evento para categoria * Event type to category mapping
*/ */
const EVENT_CATEGORY_MAP: Record<string, BaileysLogCategory> = { const EVENT_CATEGORY_MAP: Record<string, BaileysLogCategory> = {
'connection.update': 'connection', 'connection.update': 'connection',
@@ -165,7 +166,7 @@ const EVENT_CATEGORY_MAP: Record<string, BaileysLogCategory> = {
} }
/** /**
* Prioridade padrão por tipo de evento * Default priority by event type
*/ */
const EVENT_PRIORITY_MAP: Partial<Record<BaileysEventType, EventPriority>> = { const EVENT_PRIORITY_MAP: Partial<Record<BaileysEventType, EventPriority>> = {
'connection.update': 'critical', 'connection.update': 'critical',
@@ -178,14 +179,14 @@ const EVENT_PRIORITY_MAP: Partial<Record<BaileysEventType, EventPriority>> = {
} }
/** /**
* Gera ID único para evento * Generate unique event ID
*/ */
function generateEventId(): string { function generateEventId(): string {
return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}` return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`
} }
/** /**
* Classe principal do Event Stream * Main Event Stream class
*/ */
export class BaileysEventStream extends EventEmitter { export class BaileysEventStream extends EventEmitter {
private buffer: StreamEvent[] = [] private buffer: StreamEvent[] = []
@@ -217,7 +218,7 @@ export class BaileysEventStream extends EventEmitter {
this.stats = this.createInitialStats() this.stats = this.createInitialStats()
// Iniciar flush periódico se configurado // Start periodic flush if configured
if (this.options.flushInterval > 0) { if (this.options.flushInterval > 0) {
this.flushTimer = setInterval(() => this.flush(), this.options.flushInterval) this.flushTimer = setInterval(() => this.flush(), this.options.flushInterval)
} }
@@ -243,10 +244,10 @@ export class BaileysEventStream extends EventEmitter {
} }
/** /**
* Adiciona evento ao stream * Add event to stream
*/ */
push<T>(type: BaileysEventType, data: T, options?: { priority?: EventPriority; metadata?: Record<string, unknown> }): boolean { push<T>(type: BaileysEventType, data: T, options?: { priority?: EventPriority; metadata?: Record<string, unknown> }): boolean {
// Verificar backpressure // Check backpressure
if (this.options.enableBackpressure && this.buffer.length >= this.options.highWaterMark) { if (this.options.enableBackpressure && this.buffer.length >= this.options.highWaterMark) {
this.stats.isBackpressured = true this.stats.isBackpressured = true
this.emit('backpressure', { bufferSize: this.buffer.length }) this.emit('backpressure', { bufferSize: this.buffer.length })
@@ -274,23 +275,23 @@ export class BaileysEventStream extends EventEmitter {
retryCount: 0, retryCount: 0,
} }
// Aplicar transformadores // Apply transformers
let transformedEvent: StreamEvent = event let transformedEvent: StreamEvent = event
for (const transformer of this.transformers) { for (const transformer of this.transformers) {
transformedEvent = transformer(transformedEvent) transformedEvent = transformer(transformedEvent)
} }
// Aplicar filtros // Apply filters
for (const filter of this.filters) { for (const filter of this.filters) {
if (!filter(transformedEvent)) { if (!filter(transformedEvent)) {
return false return false
} }
} }
// Adicionar ao buffer na posição correta (por prioridade) // Add to buffer at correct position (by priority)
this.insertByPriority(transformedEvent) this.insertByPriority(transformedEvent)
// Atualizar estatísticas // Update statistics
this.stats.totalReceived++ this.stats.totalReceived++
this.stats.bufferSize = this.buffer.length this.stats.bufferSize = this.buffer.length
this.stats.lastEventTimestamp = Date.now() this.stats.lastEventTimestamp = Date.now()
@@ -303,7 +304,7 @@ export class BaileysEventStream extends EventEmitter {
this.emit('event', transformedEvent) this.emit('event', transformedEvent)
// Processar se não estiver pausado // Process if not paused
if (!this.paused && !this.isProcessing) { if (!this.paused && !this.isProcessing) {
this.processNext() this.processNext()
} }
@@ -312,12 +313,12 @@ export class BaileysEventStream extends EventEmitter {
} }
/** /**
* Insere evento no buffer por prioridade * Insert event in buffer by priority
*/ */
private insertByPriority(event: StreamEvent): void { private insertByPriority(event: StreamEvent): void {
const eventPriorityValue = PRIORITY_VALUES[event.priority] const eventPriorityValue = PRIORITY_VALUES[event.priority]
// Encontrar posição correta // Find correct position
let insertIndex = this.buffer.length let insertIndex = this.buffer.length
for (let i = 0; i < this.buffer.length; i++) { for (let i = 0; i < this.buffer.length; i++) {
if (PRIORITY_VALUES[this.buffer[i].priority] > eventPriorityValue) { if (PRIORITY_VALUES[this.buffer[i].priority] > eventPriorityValue) {
@@ -330,7 +331,7 @@ export class BaileysEventStream extends EventEmitter {
} }
/** /**
* Registra handler para tipo de evento * Register handler for event type
*/ */
on<T = unknown>(event: BaileysEventType | '*', handler: EventHandler<T>): this { on<T = unknown>(event: BaileysEventType | '*', handler: EventHandler<T>): this {
if (!this.handlers.has(event)) { if (!this.handlers.has(event)) {
@@ -352,7 +353,7 @@ export class BaileysEventStream extends EventEmitter {
} }
/** /**
* Registra handler único * Register single-use handler
*/ */
once<T = unknown>(event: BaileysEventType, handler: EventHandler<T>): this { once<T = unknown>(event: BaileysEventType, handler: EventHandler<T>): this {
const wrappedHandler: EventHandler<T> = (e) => { const wrappedHandler: EventHandler<T> = (e) => {
@@ -363,7 +364,7 @@ export class BaileysEventStream extends EventEmitter {
} }
/** /**
* Adiciona filtro * Add filter
*/ */
addFilter(filter: EventFilter): this { addFilter(filter: EventFilter): this {
this.filters.push(filter) this.filters.push(filter)
@@ -371,7 +372,7 @@ export class BaileysEventStream extends EventEmitter {
} }
/** /**
* Remove filtro * Remove filter
*/ */
removeFilter(filter: EventFilter): this { removeFilter(filter: EventFilter): this {
const index = this.filters.indexOf(filter) const index = this.filters.indexOf(filter)
@@ -382,7 +383,7 @@ export class BaileysEventStream extends EventEmitter {
} }
/** /**
* Adiciona transformador * Add transformer
*/ */
addTransformer(transformer: EventTransformer): this { addTransformer(transformer: EventTransformer): this {
this.transformers.push(transformer) this.transformers.push(transformer)
@@ -390,7 +391,7 @@ export class BaileysEventStream extends EventEmitter {
} }
/** /**
* Processa próximos eventos * Process next events
*/ */
private async processNext(): Promise<void> { private async processNext(): Promise<void> {
if (this.isProcessing || this.paused || this.buffer.length === 0) { if (this.isProcessing || this.paused || this.buffer.length === 0) {
@@ -400,17 +401,17 @@ export class BaileysEventStream extends EventEmitter {
this.isProcessing = true this.isProcessing = true
try { try {
// Pegar batch de eventos // Get batch of events
const batch = this.buffer.splice(0, this.options.batchSize) const batch = this.buffer.splice(0, this.options.batchSize)
this.stats.bufferSize = this.buffer.length this.stats.bufferSize = this.buffer.length
// Verificar se saiu de backpressure // Check if exited backpressure
if (this.stats.isBackpressured && this.buffer.length <= this.options.lowWaterMark) { if (this.stats.isBackpressured && this.buffer.length <= this.options.lowWaterMark) {
this.stats.isBackpressured = false this.stats.isBackpressured = false
this.emit('drain') this.emit('drain')
} }
// Processar batch // Process batch
const startTime = Date.now() const startTime = Date.now()
let processed = 0 let processed = 0
let failed = 0 let failed = 0
@@ -430,7 +431,7 @@ export class BaileysEventStream extends EventEmitter {
const duration = Date.now() - startTime const duration = Date.now() - startTime
this.emit('batch-processed', { processed, failed, duration } as BatchResult) this.emit('batch-processed', { processed, failed, duration } as BatchResult)
// Continuar processando se houver mais // Continue processing if there are more
if (this.buffer.length > 0) { if (this.buffer.length > 0) {
setImmediate(() => this.processNext()) setImmediate(() => this.processNext())
} }
@@ -440,10 +441,10 @@ export class BaileysEventStream extends EventEmitter {
} }
/** /**
* Processa um evento * Process a single event
*/ */
private async processEvent(event: StreamEvent): Promise<void> { private async processEvent(event: StreamEvent): Promise<void> {
// Handlers específicos do tipo // Type-specific handlers
const typeHandlers = this.handlers.get(event.type) const typeHandlers = this.handlers.get(event.type)
if (typeHandlers) { if (typeHandlers) {
for (const handler of typeHandlers) { for (const handler of typeHandlers) {
@@ -451,7 +452,7 @@ export class BaileysEventStream extends EventEmitter {
} }
} }
// Handlers globais // Global handlers
const globalHandlers = this.handlers.get('*') const globalHandlers = this.handlers.get('*')
if (globalHandlers) { if (globalHandlers) {
for (const handler of globalHandlers) { for (const handler of globalHandlers) {
@@ -461,13 +462,13 @@ export class BaileysEventStream extends EventEmitter {
} }
/** /**
* Trata evento que falhou * Handle failed event
*/ */
private async handleFailedEvent(event: StreamEvent, error: Error): Promise<void> { private async handleFailedEvent(event: StreamEvent, error: Error): Promise<void> {
event.retryCount = (event.retryCount || 0) + 1 event.retryCount = (event.retryCount || 0) + 1
if (event.retryCount <= this.options.maxRetries) { if (event.retryCount <= this.options.maxRetries) {
// Re-adicionar ao buffer para retry // Re-add to buffer for retry
event.originalTimestamp = event.originalTimestamp || event.timestamp event.originalTimestamp = event.originalTimestamp || event.timestamp
event.timestamp = Date.now() event.timestamp = Date.now()
this.buffer.push(event) this.buffer.push(event)
@@ -475,7 +476,7 @@ export class BaileysEventStream extends EventEmitter {
this.emit('retry', { event, error, attempt: event.retryCount }) this.emit('retry', { event, error, attempt: event.retryCount })
} else { } else {
// Enviar para dead letter queue // Send to dead letter queue
this.addToDeadLetterQueue(event, error) this.addToDeadLetterQueue(event, error)
} }
@@ -485,7 +486,7 @@ export class BaileysEventStream extends EventEmitter {
} }
/** /**
* Adiciona evento à dead letter queue * Add event to dead letter queue
*/ */
private addToDeadLetterQueue(event: StreamEvent, error: Error): void { private addToDeadLetterQueue(event: StreamEvent, error: Error): void {
const dlqEvent = { const dlqEvent = {
@@ -500,7 +501,7 @@ export class BaileysEventStream extends EventEmitter {
this.deadLetterQueue.push(dlqEvent) this.deadLetterQueue.push(dlqEvent)
// Limitar tamanho da DLQ // Limit DLQ size
while (this.deadLetterQueue.length > this.options.deadLetterQueueSize) { while (this.deadLetterQueue.length > this.options.deadLetterQueueSize) {
this.deadLetterQueue.shift() this.deadLetterQueue.shift()
} }
@@ -510,7 +511,7 @@ export class BaileysEventStream extends EventEmitter {
} }
/** /**
* Força flush do buffer * Force flush the buffer
*/ */
async flush(): Promise<BatchResult> { async flush(): Promise<BatchResult> {
const startTime = Date.now() const startTime = Date.now()
@@ -543,7 +544,7 @@ export class BaileysEventStream extends EventEmitter {
} }
/** /**
* Pausa o processamento * Pause processing
*/ */
pause(): void { pause(): void {
this.paused = true this.paused = true
@@ -551,7 +552,7 @@ export class BaileysEventStream extends EventEmitter {
} }
/** /**
* Resume o processamento * Resume processing
*/ */
resume(): void { resume(): void {
this.paused = false this.paused = false
@@ -560,14 +561,14 @@ export class BaileysEventStream extends EventEmitter {
} }
/** /**
* Verifica se está pausado * Check if paused
*/ */
isPaused(): boolean { isPaused(): boolean {
return this.paused return this.paused
} }
/** /**
* Limpa o buffer * Clear the buffer
*/ */
clear(): void { clear(): void {
this.buffer = [] this.buffer = []
@@ -576,14 +577,14 @@ export class BaileysEventStream extends EventEmitter {
} }
/** /**
* Retorna eventos da dead letter queue * Return dead letter queue events
*/ */
getDeadLetterQueue(): StreamEvent[] { getDeadLetterQueue(): StreamEvent[] {
return [...this.deadLetterQueue] return [...this.deadLetterQueue]
} }
/** /**
* Limpa dead letter queue * Clear dead letter queue
*/ */
clearDeadLetterQueue(): void { clearDeadLetterQueue(): void {
this.deadLetterQueue = [] this.deadLetterQueue = []
@@ -591,7 +592,7 @@ export class BaileysEventStream extends EventEmitter {
} }
/** /**
* Replay eventos da dead letter queue * Replay dead letter queue events
*/ */
async replayDeadLetterQueue(): Promise<BatchResult> { async replayDeadLetterQueue(): Promise<BatchResult> {
const events = this.deadLetterQueue.splice(0) const events = this.deadLetterQueue.splice(0)
@@ -625,14 +626,14 @@ export class BaileysEventStream extends EventEmitter {
} }
/** /**
* Retorna estatísticas * Return statistics
*/ */
getStats(): EventStreamStats { getStats(): EventStreamStats {
return { ...this.stats } return { ...this.stats }
} }
/** /**
* Reseta estatísticas * Reset statistics
*/ */
resetStats(): void { resetStats(): void {
this.stats = this.createInitialStats() this.stats = this.createInitialStats()
@@ -641,7 +642,7 @@ export class BaileysEventStream extends EventEmitter {
} }
/** /**
* Destroy e limpa recursos * Destroy and clean up resources
*/ */
destroy(): void { destroy(): void {
if (this.flushTimer) { if (this.flushTimer) {
@@ -657,47 +658,47 @@ export class BaileysEventStream extends EventEmitter {
} }
/** /**
* Factory para criar event stream * Factory to create event stream
*/ */
export function createEventStream(options?: EventStreamOptions): BaileysEventStream { export function createEventStream(options?: EventStreamOptions): BaileysEventStream {
return new BaileysEventStream(options) return new BaileysEventStream(options)
} }
/** /**
* Filtros pré-definidos * Pre-defined filters
*/ */
export const eventFilters = { export const eventFilters = {
/** Filtra por tipo de evento */ /** Filter by event type */
byType: byType:
(...types: BaileysEventType[]): EventFilter => (...types: BaileysEventType[]): EventFilter =>
(event) => (event) =>
types.includes(event.type), types.includes(event.type),
/** Filtra por categoria */ /** Filter by category */
byCategory: byCategory:
(...categories: BaileysLogCategory[]): EventFilter => (...categories: BaileysLogCategory[]): EventFilter =>
(event) => (event) =>
categories.includes(event.category), categories.includes(event.category),
/** Filtra por prioridade mínima */ /** Filter by minimum priority */
byMinPriority: byMinPriority:
(minPriority: EventPriority): EventFilter => (minPriority: EventPriority): EventFilter =>
(event) => (event) =>
PRIORITY_VALUES[event.priority] <= PRIORITY_VALUES[minPriority], PRIORITY_VALUES[event.priority] <= PRIORITY_VALUES[minPriority],
/** Filtra eventos recentes (dentro de ms) */ /** Filter recent events (within ms) */
recentOnly: recentOnly:
(maxAgeMs: number): EventFilter => (maxAgeMs: number): EventFilter =>
(event) => (event) =>
Date.now() - event.timestamp <= maxAgeMs, Date.now() - event.timestamp <= maxAgeMs,
/** Combina filtros com AND */ /** Combine filters with AND */
and: and:
(...filters: EventFilter[]): EventFilter => (...filters: EventFilter[]): EventFilter =>
(event) => (event) =>
filters.every((f) => f(event)), filters.every((f) => f(event)),
/** Combina filtros com OR */ /** Combine filters with OR */
or: or:
(...filters: EventFilter[]): EventFilter => (...filters: EventFilter[]): EventFilter =>
(event) => (event) =>
@@ -705,10 +706,10 @@ export const eventFilters = {
} }
/** /**
* Transformadores pré-definidos * Pre-defined transformers
*/ */
export const eventTransformers = { export const eventTransformers = {
/** Adiciona timestamp de processamento */ /** Add processing timestamp */
addProcessingTimestamp: (): EventTransformer => (event) => ({ addProcessingTimestamp: (): EventTransformer => (event) => ({
...event, ...event,
metadata: { metadata: {
@@ -717,7 +718,7 @@ export const eventTransformers = {
}, },
}), }),
/** Adiciona ID de trace */ /** Add trace ID */
addTraceId: addTraceId:
(traceIdGenerator: () => string): EventTransformer => (traceIdGenerator: () => string): EventTransformer =>
(event) => ({ (event) => ({
@@ -728,7 +729,7 @@ export const eventTransformers = {
}, },
}), }),
/** Eleva prioridade baseado em condição */ /** Elevate priority based on condition */
elevatepriorityIf: elevatepriorityIf:
(condition: (event: StreamEvent) => boolean, newPriority: EventPriority): EventTransformer => (condition: (event: StreamEvent) => boolean, newPriority: EventPriority): EventTransformer =>
(event) => (event) =>
+73 -61
View File
@@ -1,60 +1,61 @@
/** /**
* @fileoverview Logger customizado específico para Baileys * Custom Logger for Baileys/WhatsApp
* @module Utils/baileys-logger
* *
* Fornece: * Provides:
* - Logger pré-configurado para contexto Baileys/WhatsApp * - Pre-configured logger for Baileys/WhatsApp context
* - Categorização de eventos por tipo (connection, message, media, etc.) * - Event categorization by type (connection, message, media, etc.)
* - Filtros específicos para reduzir ruído * - Specific filters to reduce noise
* - Métricas de eventos WhatsApp * - WhatsApp event metrics
* - Formatação otimizada para debugging * - Optimized formatting for debugging
*
* @module Utils/baileys-logger
*/ */
import type { ILogger } from './logger.js' import type { ILogger } from './logger.js'
import { StructuredLogger, createStructuredLogger, type LogLevel, type LogEntry } from './structured-logger.js' import { StructuredLogger, createStructuredLogger, type LogLevel, type LogEntry } from './structured-logger.js'
/** /**
* Categorias de log específicas do Baileys * Baileys-specific log categories
*/ */
export type BaileysLogCategory = export type BaileysLogCategory =
| 'connection' // Eventos de conexão WebSocket | 'connection' // WebSocket connection events
| 'auth' // Autenticação e QR code | 'auth' // Authentication and QR code
| 'message' // Envio/recebimento de mensagens | 'message' // Message send/receive
| 'media' // Upload/download de mídia | 'media' // Media upload/download
| 'group' // Operações de grupo | 'group' // Group operations
| 'presence' // Status de presença | 'presence' // Presence status
| 'call' // Chamadas de voz/vídeo | 'call' // Voice/video calls
| 'sync' // Sincronização de dados | 'sync' // Data synchronization
| 'encryption' // Operações de criptografia | 'encryption' // Encryption operations
| 'retry' // Retentativas de operações | 'retry' // Operation retries
| 'socket' // Eventos de socket baixo nível | 'socket' // Low-level socket events
| 'binary' // Codificação/decodificação binária | 'binary' // Binary encoding/decoding
| 'unknown' // Categoria desconhecida | 'unknown' // Unknown category
/** /**
* Configuração do Baileys Logger * Baileys Logger configuration
*/ */
export interface BaileysLoggerConfig { export interface BaileysLoggerConfig {
/** Nível de log padrão */ /** Default log level */
level: LogLevel level: LogLevel
/** Categorias a serem ignoradas */ /** Categories to ignore */
ignoredCategories?: BaileysLogCategory[] ignoredCategories?: BaileysLogCategory[]
/** Categorias com nível de log elevado (debug sempre) */ /** Categories with elevated log level (always debug) */
verboseCategories?: BaileysLogCategory[] verboseCategories?: BaileysLogCategory[]
/** Se deve logar payloads de mensagens (pode ser sensível) */ /** Whether to log message payloads (may be sensitive) */
logMessagePayloads?: boolean logMessagePayloads?: boolean
/** Se deve logar dados binários em hex */ /** Whether to log binary data in hex */
logBinaryData?: boolean logBinaryData?: boolean
/** Prefixo para identificar instância */ /** Instance identifier prefix */
instanceId?: string instanceId?: string
/** Handler para eventos específicos */ /** Handler for specific events */
eventHandler?: (category: BaileysLogCategory, entry: LogEntry) => void eventHandler?: (category: BaileysLogCategory, entry: LogEntry) => void
/** Limite de tamanho para payloads logados (bytes) */ /** Size limit for logged payloads (bytes) */
maxPayloadSize?: number maxPayloadSize?: number
} }
/** /**
* Métricas específicas do Baileys * Baileys-specific metrics
*/ */
export interface BaileysLoggerMetrics { export interface BaileysLoggerMetrics {
connectionAttempts: number connectionAttempts: number
@@ -72,7 +73,7 @@ export interface BaileysLoggerMetrics {
} }
/** /**
* Padrões para detectar categoria de log * Patterns to detect log category
*/ */
const CATEGORY_PATTERNS: Array<{ pattern: RegExp; category: BaileysLogCategory }> = [ const CATEGORY_PATTERNS: Array<{ pattern: RegExp; category: BaileysLogCategory }> = [
{ pattern: /connect|disconnect|socket|ws|websocket|open|close/i, category: 'connection' }, { pattern: /connect|disconnect|socket|ws|websocket|open|close/i, category: 'connection' },
@@ -89,7 +90,18 @@ const CATEGORY_PATTERNS: Array<{ pattern: RegExp; category: BaileysLogCategory }
] ]
/** /**
* Logger customizado para Baileys * Custom logger for Baileys
*
* @example
* ```typescript
* const logger = createBaileysLogger({
* level: 'debug',
* instanceId: 'session-1'
* })
*
* logger.logConnection('connected', { duration: 1500 })
* logger.logMessage('send', 'text', 'user@s.whatsapp.net')
* ```
*/ */
export class BaileysLogger implements ILogger { export class BaileysLogger implements ILogger {
private structuredLogger: StructuredLogger private structuredLogger: StructuredLogger
@@ -162,7 +174,7 @@ export class BaileysLogger implements ILogger {
} }
/** /**
* Cria logger filho com contexto adicional * Create child logger with additional context
*/ */
child(obj: Record<string, unknown>): BaileysLogger { child(obj: Record<string, unknown>): BaileysLogger {
const childLogger = new BaileysLogger(this.config) const childLogger = new BaileysLogger(this.config)
@@ -171,7 +183,7 @@ export class BaileysLogger implements ILogger {
} }
/** /**
* Detecta categoria do log baseado no conteúdo * Detect log category based on content
*/ */
private detectCategory(obj: unknown, msg?: string): BaileysLogCategory { private detectCategory(obj: unknown, msg?: string): BaileysLogCategory {
const searchText = [ const searchText = [
@@ -190,14 +202,14 @@ export class BaileysLogger implements ILogger {
} }
/** /**
* Verifica se categoria deve ser logada * Check if category should be logged
*/ */
private shouldLogCategory(category: BaileysLogCategory, level: LogLevel): boolean { private shouldLogCategory(category: BaileysLogCategory, _level: LogLevel): boolean {
if (this.config.ignoredCategories.includes(category)) { if (this.config.ignoredCategories.includes(category)) {
return false return false
} }
// Categorias verbose sempre logam em debug ou superior // Verbose categories always log at debug or higher
if (this.config.verboseCategories.includes(category)) { if (this.config.verboseCategories.includes(category)) {
return true return true
} }
@@ -206,14 +218,14 @@ export class BaileysLogger implements ILogger {
} }
/** /**
* Sanitiza payload de mensagem * Sanitize message payload
*/ */
private sanitizePayload(obj: unknown): unknown { private sanitizePayload(obj: unknown): unknown {
if (!this.config.logMessagePayloads) { if (!this.config.logMessagePayloads) {
if (typeof obj === 'object' && obj !== null) { if (typeof obj === 'object' && obj !== null) {
const sanitized = { ...(obj as Record<string, unknown>) } const sanitized = { ...(obj as Record<string, unknown>) }
// Remover campos sensíveis de mensagem // Remove sensitive message fields
const sensitiveFields = ['body', 'text', 'content', 'caption', 'payload', 'data'] const sensitiveFields = ['body', 'text', 'content', 'caption', 'payload', 'data']
for (const field of sensitiveFields) { for (const field of sensitiveFields) {
if (field in sanitized) { if (field in sanitized) {
@@ -230,7 +242,7 @@ export class BaileysLogger implements ILogger {
} }
} }
// Limitar tamanho do payload // Limit payload size
if (typeof obj === 'object' && obj !== null) { if (typeof obj === 'object' && obj !== null) {
const str = JSON.stringify(obj) const str = JSON.stringify(obj)
if (str.length > this.config.maxPayloadSize) { if (str.length > this.config.maxPayloadSize) {
@@ -246,7 +258,7 @@ export class BaileysLogger implements ILogger {
} }
/** /**
* Atualiza métricas baseado no log * Update metrics based on log
*/ */
private updateMetrics(category: BaileysLogCategory, level: LogLevel, obj: unknown): void { private updateMetrics(category: BaileysLogCategory, level: LogLevel, obj: unknown): void {
const objStr = typeof obj === 'object' ? JSON.stringify(obj) : String(obj) const objStr = typeof obj === 'object' ? JSON.stringify(obj) : String(obj)
@@ -296,7 +308,7 @@ export class BaileysLogger implements ILogger {
} }
/** /**
* Método principal de log * Main log method
*/ */
private log(level: LogLevel, obj: unknown, msg?: string): void { private log(level: LogLevel, obj: unknown, msg?: string): void {
const category = this.detectCategory(obj, msg) const category = this.detectCategory(obj, msg)
@@ -305,13 +317,13 @@ export class BaileysLogger implements ILogger {
return return
} }
// Atualizar métricas // Update metrics
this.updateMetrics(category, level, obj) this.updateMetrics(category, level, obj)
// Sanitizar payload // Sanitize payload
const sanitizedObj = this.sanitizePayload(obj) const sanitizedObj = this.sanitizePayload(obj)
// Adicionar contexto do Baileys // Add Baileys context
const enrichedObj = { const enrichedObj = {
category, category,
instanceId: this.config.instanceId, instanceId: this.config.instanceId,
@@ -319,10 +331,10 @@ export class BaileysLogger implements ILogger {
...(typeof sanitizedObj === 'object' && sanitizedObj !== null ? sanitizedObj : { value: sanitizedObj }), ...(typeof sanitizedObj === 'object' && sanitizedObj !== null ? sanitizedObj : { value: sanitizedObj }),
} }
// Log estruturado // Structured log
this.structuredLogger[level](enrichedObj, msg) this.structuredLogger[level](enrichedObj, msg)
// Handler de evento // Event handler
if (this.config.eventHandler) { if (this.config.eventHandler) {
const entry: LogEntry = { const entry: LogEntry = {
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
@@ -357,7 +369,7 @@ export class BaileysLogger implements ILogger {
} }
/** /**
* Log de conexão específico * Log connection-specific event
*/ */
logConnection(event: 'connecting' | 'connected' | 'disconnected' | 'error', details?: Record<string, unknown>): void { logConnection(event: 'connecting' | 'connected' | 'disconnected' | 'error', details?: Record<string, unknown>): void {
const level: LogLevel = event === 'error' ? 'error' : event === 'disconnected' ? 'warn' : 'info' const level: LogLevel = event === 'error' ? 'error' : event === 'disconnected' ? 'warn' : 'info'
@@ -365,7 +377,7 @@ export class BaileysLogger implements ILogger {
} }
/** /**
* Log de mensagem específico * Log message-specific event
*/ */
logMessage( logMessage(
direction: 'send' | 'receive', direction: 'send' | 'receive',
@@ -388,7 +400,7 @@ export class BaileysLogger implements ILogger {
} }
/** /**
* Log de mídia específico * Log media-specific event
*/ */
logMedia( logMedia(
operation: 'upload' | 'download', operation: 'upload' | 'download',
@@ -411,11 +423,11 @@ export class BaileysLogger implements ILogger {
} }
/** /**
* Sanitiza JID para log (remove parte do número) * Sanitize JID for logging (mask part of number)
*/ */
private sanitizeJid(jid: string): string { private sanitizeJid(jid: string): string {
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {
// Em produção, mascara parte do número // In production, mask part of the number
const parts = jid.split('@') const parts = jid.split('@')
if (parts.length === 2 && parts[0].length > 4) { if (parts.length === 2 && parts[0].length > 4) {
return `${parts[0].substring(0, 4)}****@${parts[1]}` return `${parts[0].substring(0, 4)}****@${parts[1]}`
@@ -425,7 +437,7 @@ export class BaileysLogger implements ILogger {
} }
/** /**
* Formata bytes para leitura humana * Format bytes for human readability
*/ */
private formatBytes(bytes: number): string { private formatBytes(bytes: number): string {
if (bytes === 0) return '0 B' if (bytes === 0) return '0 B'
@@ -436,21 +448,21 @@ export class BaileysLogger implements ILogger {
} }
/** /**
* Retorna métricas do logger * Get logger metrics
*/ */
getMetrics(): BaileysLoggerMetrics { getMetrics(): BaileysLoggerMetrics {
return { ...this.metrics } return { ...this.metrics }
} }
/** /**
* Retorna métricas do structured logger interno * Get internal structured logger metrics
*/ */
getStructuredMetrics() { getStructuredMetrics() {
return this.structuredLogger.getMetrics() return this.structuredLogger.getMetrics()
} }
/** /**
* Reseta métricas * Reset metrics
*/ */
resetMetrics(): void { resetMetrics(): void {
this.metrics = this.createInitialMetrics() this.metrics = this.createInitialMetrics()
@@ -458,7 +470,7 @@ export class BaileysLogger implements ILogger {
} }
/** /**
* Retorna instance ID * Get instance ID
*/ */
getInstanceId(): string { getInstanceId(): string {
return this.config.instanceId return this.config.instanceId
@@ -466,14 +478,14 @@ export class BaileysLogger implements ILogger {
} }
/** /**
* Factory para criar Baileys Logger * Factory to create Baileys Logger
*/ */
export function createBaileysLogger(config?: Partial<BaileysLoggerConfig>): BaileysLogger { export function createBaileysLogger(config?: Partial<BaileysLoggerConfig>): BaileysLogger {
return new BaileysLogger(config) return new BaileysLogger(config)
} }
/** /**
* Singleton para logger padrão do Baileys * Default Baileys logger singleton
*/ */
let defaultBaileysLogger: BaileysLogger | null = null let defaultBaileysLogger: BaileysLogger | null = null
+49 -48
View File
@@ -1,44 +1,45 @@
/** /**
* @fileoverview Sistema de cache inteligente * Smart Cache System
* @module Utils/cache-utils
* *
* Fornece: * Provides:
* - Cache em memória com TTL configurável * - In-memory cache with configurable TTL
* - Invalidação automática e manual * - Automatic and manual invalidation
* - Métricas de hit/miss * - Hit/miss metrics
* - Estratégia LRU (Least Recently Used) * - LRU (Least Recently Used) strategy
* - Cache distribuído (preparado para Redis) * - Distributed cache (prepared for Redis)
* - Namespace para isolamento * - Namespace for isolation
* - Serialização customizável * - Customizable serialization
*
* @module Utils/cache-utils
*/ */
import { LRUCache } from 'lru-cache' import { LRUCache } from 'lru-cache'
import { metrics } from './prometheus-metrics.js' import { metrics } from './prometheus-metrics.js'
/** /**
* Opções de configuração do cache * Cache configuration options
*/ */
export interface CacheOptions<V> { export interface CacheOptions<V> {
/** Tempo de vida em ms (default: 5 minutos) */ /** Time to live in ms (default: 5 minutes) */
ttl?: number ttl?: number
/** Tamanho máximo do cache (default: 1000) */ /** Maximum cache size (default: 1000) */
maxSize?: number maxSize?: number
/** Função para calcular tamanho de um item */ /** Function to calculate item size */
sizeCalculation?: (value: V) => number sizeCalculation?: (value: V) => number
/** Se deve atualizar TTL no acesso */ /** Whether to update TTL on access */
updateAgeOnGet?: boolean updateAgeOnGet?: boolean
/** Namespace para isolamento */ /** Namespace for isolation */
namespace?: string namespace?: string
/** Callback quando item expira */ /** Callback when item expires */
onExpire?: (key: string, value: V) => void onExpire?: (key: string, value: V) => void
/** Se deve coletar métricas */ /** Whether to collect metrics */
collectMetrics?: boolean collectMetrics?: boolean
/** Nome do cache para métricas */ /** Cache name for metrics */
metricName?: string metricName?: string
} }
/** /**
* Estatísticas do cache * Cache statistics
*/ */
export interface CacheStats { export interface CacheStats {
hits: number hits: number
@@ -49,7 +50,7 @@ export interface CacheStats {
} }
/** /**
* Item do cache com metadados * Cache item with metadata
*/ */
export interface CacheItem<V> { export interface CacheItem<V> {
value: V value: V
@@ -60,7 +61,7 @@ export interface CacheItem<V> {
} }
/** /**
* Resultado de operação de cache * Cache operation result
*/ */
export interface CacheResult<V> { export interface CacheResult<V> {
value: V | undefined value: V | undefined
@@ -70,7 +71,7 @@ export interface CacheResult<V> {
} }
/** /**
* Classe principal do Cache * Main Cache class
*/ */
export class Cache<V = unknown> { export class Cache<V = unknown> {
private cache: LRUCache<string, CacheItem<V>> private cache: LRUCache<string, CacheItem<V>>
@@ -105,7 +106,7 @@ export class Cache<V = unknown> {
} }
/** /**
* Obtém valor do cache * Get value from cache
*/ */
get(key: string): V | undefined { get(key: string): V | undefined {
const fullKey = this.getFullKey(key) const fullKey = this.getFullKey(key)
@@ -132,7 +133,7 @@ export class Cache<V = unknown> {
} }
/** /**
* Obtém valor com resultado detalhado * Get value with detailed result
*/ */
getWithResult(key: string): CacheResult<V> { getWithResult(key: string): CacheResult<V> {
const fullKey = this.getFullKey(key) const fullKey = this.getFullKey(key)
@@ -167,7 +168,7 @@ export class Cache<V = unknown> {
} }
/** /**
* Define valor no cache * Set value in cache
*/ */
set(key: string, value: V, ttl?: number): void { set(key: string, value: V, ttl?: number): void {
const fullKey = this.getFullKey(key) const fullKey = this.getFullKey(key)
@@ -190,7 +191,7 @@ export class Cache<V = unknown> {
} }
/** /**
* Verifica se chave existe * Check if key exists
*/ */
has(key: string): boolean { has(key: string): boolean {
const fullKey = this.getFullKey(key) const fullKey = this.getFullKey(key)
@@ -198,7 +199,7 @@ export class Cache<V = unknown> {
} }
/** /**
* Remove item do cache * Remove item from cache
*/ */
delete(key: string): boolean { delete(key: string): boolean {
const fullKey = this.getFullKey(key) const fullKey = this.getFullKey(key)
@@ -212,7 +213,7 @@ export class Cache<V = unknown> {
} }
/** /**
* Limpa todo o cache * Clear the entire cache
*/ */
clear(): void { clear(): void {
this.cache.clear() this.cache.clear()
@@ -224,7 +225,7 @@ export class Cache<V = unknown> {
} }
/** /**
* Obtém ou define valor (cache-aside pattern) * Get or set value (cache-aside pattern)
*/ */
async getOrSet(key: string, factory: () => V | Promise<V>, ttl?: number): Promise<V> { async getOrSet(key: string, factory: () => V | Promise<V>, ttl?: number): Promise<V> {
const existing = this.get(key) const existing = this.get(key)
@@ -238,7 +239,7 @@ export class Cache<V = unknown> {
} }
/** /**
* Obtém ou define valor síncronamente * Get or set value synchronously
*/ */
getOrSetSync(key: string, factory: () => V, ttl?: number): V { getOrSetSync(key: string, factory: () => V, ttl?: number): V {
const existing = this.get(key) const existing = this.get(key)
@@ -252,7 +253,7 @@ export class Cache<V = unknown> {
} }
/** /**
* Invalida itens por padrão * Invalidate items by pattern
*/ */
invalidateByPattern(pattern: RegExp): number { invalidateByPattern(pattern: RegExp): number {
let count = 0 let count = 0
@@ -274,14 +275,14 @@ export class Cache<V = unknown> {
} }
/** /**
* Invalida itens por prefixo * Invalidate items by prefix
*/ */
invalidateByPrefix(prefix: string): number { invalidateByPrefix(prefix: string): number {
return this.invalidateByPattern(new RegExp(`^${prefix}`)) return this.invalidateByPattern(new RegExp(`^${prefix}`))
} }
/** /**
* Retorna estatísticas do cache * Return cache statistics
*/ */
getStats(): CacheStats { getStats(): CacheStats {
const total = this.stats.hits + this.stats.misses const total = this.stats.hits + this.stats.misses
@@ -295,14 +296,14 @@ export class Cache<V = unknown> {
} }
/** /**
* Retorna tamanho atual * Return current size
*/ */
get size(): number { get size(): number {
return this.cache.size return this.cache.size
} }
/** /**
* Retorna todas as chaves * Return all keys
*/ */
keys(): string[] { keys(): string[] {
const prefix = `${this.namespace}:` const prefix = `${this.namespace}:`
@@ -310,14 +311,14 @@ export class Cache<V = unknown> {
} }
/** /**
* Retorna todos os valores * Return all values
*/ */
values(): V[] { values(): V[] {
return Array.from(this.cache.values()).map((item) => item.value) return Array.from(this.cache.values()).map((item) => item.value)
} }
/** /**
* Retorna todos os itens com metadados * Return all items with metadata
*/ */
entries(): Array<{ key: string; item: CacheItem<V> }> { entries(): Array<{ key: string; item: CacheItem<V> }> {
const prefix = `${this.namespace}:` const prefix = `${this.namespace}:`
@@ -328,7 +329,7 @@ export class Cache<V = unknown> {
} }
/** /**
* Atualiza TTL de um item * Update TTL of an item
*/ */
touch(key: string, ttl?: number): boolean { touch(key: string, ttl?: number): boolean {
const fullKey = this.getFullKey(key) const fullKey = this.getFullKey(key)
@@ -345,7 +346,7 @@ export class Cache<V = unknown> {
} }
/** /**
* Obtém item expirado (se ainda em memória) * Get expired item (if still in memory)
*/ */
peek(key: string): V | undefined { peek(key: string): V | undefined {
const fullKey = this.getFullKey(key) const fullKey = this.getFullKey(key)
@@ -359,14 +360,14 @@ export class Cache<V = unknown> {
} }
/** /**
* Factory para criar cache com tipo * Factory to create typed cache
*/ */
export function createCache<V>(options?: CacheOptions<V>): Cache<V> { export function createCache<V>(options?: CacheOptions<V>): Cache<V> {
return new Cache<V>(options) return new Cache<V>(options)
} }
/** /**
* Cache com múltiplos níveis (L1: memória, L2: externo) * Multi-level cache (L1: memory, L2: external)
*/ */
export class MultiLevelCache<V> { export class MultiLevelCache<V> {
private l1: Cache<V> private l1: Cache<V>
@@ -389,17 +390,17 @@ export class MultiLevelCache<V> {
} }
async get(key: string): Promise<V | undefined> { async get(key: string): Promise<V | undefined> {
// Tentar L1 primeiro // Try L1 first
const l1Value = this.l1.get(key) const l1Value = this.l1.get(key)
if (l1Value !== undefined) { if (l1Value !== undefined) {
return l1Value return l1Value
} }
// Tentar L2 se disponível // Try L2 if available
if (this.l2) { if (this.l2) {
const l2Value = await this.l2.get(key) const l2Value = await this.l2.get(key)
if (l2Value !== undefined) { if (l2Value !== undefined) {
// Promover para L1 // Promote to L1
this.l1.set(key, l2Value) this.l1.set(key, l2Value)
return l2Value return l2Value
} }
@@ -433,7 +434,7 @@ export class MultiLevelCache<V> {
} }
/** /**
* Decorator para cachear resultado de método * Decorator to cache method result
*/ */
export function cached<V>(options: CacheOptions<V> & { keyGenerator?: (...args: unknown[]) => string } = {}) { export function cached<V>(options: CacheOptions<V> & { keyGenerator?: (...args: unknown[]) => string } = {}) {
const cache = new Cache<V>(options) const cache = new Cache<V>(options)
@@ -465,7 +466,7 @@ export function cached<V>(options: CacheOptions<V> & { keyGenerator?: (...args:
} }
/** /**
* Wrapper para função com cache * Wrapper for function with cache
*/ */
export function withCache<T extends (...args: unknown[]) => unknown>( export function withCache<T extends (...args: unknown[]) => unknown>(
fn: T, fn: T,
@@ -497,7 +498,7 @@ export function withCache<T extends (...args: unknown[]) => unknown>(
} }
/** /**
* Cache global singleton por namespace * Global singleton cache by namespace
*/ */
const globalCaches: Map<string, Cache<unknown>> = new Map() const globalCaches: Map<string, Cache<unknown>> = new Map()
+332 -111
View File
@@ -1,66 +1,85 @@
/** /**
* @fileoverview Proteção contra falhas em cascata - Circuit Breaker * Circuit Breaker Pattern Implementation
* @module Utils/circuit-breaker
* *
* Fornece: * Provides protection against cascading failures by monitoring operation outcomes
* - Estados: Closed, Open, Half-Open * and temporarily blocking requests when failure thresholds are exceeded.
* - Thresholds configuráveis *
* - Recuperação automática * States:
* - Callbacks de estado * - CLOSED: Normal operation, requests pass through
* - Integração com métricas * - OPEN: Failures exceeded threshold, requests are blocked
* - Fallback handlers * - HALF_OPEN: Testing recovery, limited requests allowed
*
* @module Utils/circuit-breaker
*/ */
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
import { metrics } from './prometheus-metrics.js' import { metrics } from './prometheus-metrics.js'
/** /**
* Estados do Circuit Breaker * Circuit breaker operational states
*/ */
export type CircuitState = 'closed' | 'open' | 'half-open' export type CircuitState = 'closed' | 'open' | 'half-open'
/** /**
* Opções de configuração do Circuit Breaker * Failure record with timestamp for sliding window tracking
*/ */
export interface CircuitBreakerOptions { export interface FailureRecord {
/** Nome do circuit breaker (para métricas) */ timestamp: number
name: string error: Error
/** Número de falhas para abrir o circuito */
failureThreshold?: number
/** Número de sucessos para fechar o circuito (em half-open) */
successThreshold?: number
/** Tempo em ms para tentar half-open após open */
resetTimeout?: number
/** Timeout para operações em ms */
timeout?: number
/** Função para determinar se erro deve contar como falha */
isFailure?: (error: Error) => boolean
/** Coletar métricas */
collectMetrics?: boolean
/** Função de fallback quando circuito está aberto */
fallback?: <T>() => T | Promise<T>
/** Callback quando estado muda */
onStateChange?: (from: CircuitState, to: CircuitState) => void
/** Callback em falha */
onFailure?: (error: Error) => void
/** Callback em sucesso */
onSuccess?: () => void
/** Callback quando circuito abre */
onOpen?: () => void
/** Callback quando circuito fecha */
onClose?: () => void
} }
/** /**
* Estatísticas do Circuit Breaker * Circuit breaker configuration options
*/
export interface CircuitBreakerOptions {
/** Unique identifier for this circuit breaker (used in metrics/logging) */
name: string
/** Number of failures within the window to trigger OPEN state (default: 5) */
failureThreshold?: number
/** Time window in ms for counting failures (default: 60000) */
failureWindow?: number
/** Number of successes required in HALF_OPEN to return to CLOSED (default: 2) */
successThreshold?: number
/** Time in ms to wait before transitioning from OPEN to HALF_OPEN (default: 30000) */
resetTimeout?: number
/** Timeout for individual operations in ms (default: 10000) */
timeout?: number
/** Minimum number of requests before circuit can trip (default: 5) */
volumeThreshold?: number
/** Predicate to determine if an error should count as a failure */
shouldCountError?: (error: Error) => boolean
/** Whether to collect Prometheus metrics (default: true) */
collectMetrics?: boolean
/** Fallback function when circuit is OPEN */
fallback?: <T>() => T | Promise<T>
/** Callback when state changes */
onStateChange?: (from: CircuitState, to: CircuitState) => void
/** Callback on failure */
onFailure?: (error: Error) => void
/** Callback on success */
onSuccess?: () => void
/** Callback when circuit opens */
onOpen?: () => void
/** Callback when circuit closes */
onClose?: () => void
/** Callback when circuit enters half-open */
onHalfOpen?: () => void
}
/**
* Circuit breaker statistics
*/ */
export interface CircuitBreakerStats { export interface CircuitBreakerStats {
state: CircuitState state: CircuitState
failures: number failures: number
successes: number successes: number
consecutiveFailures: number
consecutiveSuccesses: number
totalCalls: number totalCalls: number
totalFailures: number totalFailures: number
totalSuccesses: number totalSuccesses: number
totalRejected: number
failureRate: number
lastFailureTime?: number lastFailureTime?: number
lastSuccessTime?: number lastSuccessTime?: number
lastStateChange?: number lastStateChange?: number
@@ -70,7 +89,7 @@ export interface CircuitBreakerStats {
} }
/** /**
* Erro lançado quando circuito está aberto * Error thrown when circuit is OPEN and request is rejected
*/ */
export class CircuitOpenError extends Error { export class CircuitOpenError extends Error {
constructor( constructor(
@@ -83,7 +102,7 @@ export class CircuitOpenError extends Error {
} }
/** /**
* Erro de timeout * Error thrown when operation exceeds timeout
*/ */
export class CircuitTimeoutError extends Error { export class CircuitTimeoutError extends Error {
constructor( constructor(
@@ -96,20 +115,39 @@ export class CircuitTimeoutError extends Error {
} }
/** /**
* Classe principal do Circuit Breaker * Circuit Breaker implementation with sliding window failure tracking
*
* @example
* ```typescript
* const breaker = new CircuitBreaker({
* name: 'whatsapp-api',
* failureThreshold: 5,
* resetTimeout: 30000
* })
*
* try {
* const result = await breaker.execute(() => sendMessage(msg))
* } catch (error) {
* if (error instanceof CircuitOpenError) {
* // Circuit is open, use fallback
* }
* }
* ```
*/ */
export class CircuitBreaker extends EventEmitter { export class CircuitBreaker extends EventEmitter {
private state: CircuitState = 'closed' private state: CircuitState = 'closed'
private failures = 0 private failureRecords: FailureRecord[] = []
private successes = 0 private consecutiveFailures = 0
private consecutiveSuccesses = 0
private totalCalls = 0 private totalCalls = 0
private totalFailures = 0 private totalFailures = 0
private totalSuccesses = 0 private totalSuccesses = 0
private totalRejected = 0
private lastFailureTime?: number private lastFailureTime?: number
private lastSuccessTime?: number private lastSuccessTime?: number
private lastStateChange?: number private lastStateChange: number
private resetTimer?: ReturnType<typeof setTimeout> private resetTimer?: ReturnType<typeof setTimeout>
private options: Required<CircuitBreakerOptions> private readonly options: Required<CircuitBreakerOptions>
constructor(options: CircuitBreakerOptions) { constructor(options: CircuitBreakerOptions) {
super() super()
@@ -117,10 +155,12 @@ export class CircuitBreaker extends EventEmitter {
this.options = { this.options = {
name: options.name, name: options.name,
failureThreshold: options.failureThreshold ?? 5, failureThreshold: options.failureThreshold ?? 5,
failureWindow: options.failureWindow ?? 60000,
successThreshold: options.successThreshold ?? 2, successThreshold: options.successThreshold ?? 2,
resetTimeout: options.resetTimeout ?? 30000, resetTimeout: options.resetTimeout ?? 30000,
timeout: options.timeout ?? 10000, timeout: options.timeout ?? 10000,
isFailure: options.isFailure ?? (() => true), volumeThreshold: options.volumeThreshold ?? 5,
shouldCountError: options.shouldCountError ?? (() => true),
collectMetrics: options.collectMetrics ?? true, collectMetrics: options.collectMetrics ?? true,
fallback: options.fallback ?? (() => { fallback: options.fallback ?? (() => {
throw new CircuitOpenError(this.options.name, this.state) throw new CircuitOpenError(this.options.name, this.state)
@@ -130,27 +170,46 @@ export class CircuitBreaker extends EventEmitter {
onSuccess: options.onSuccess ?? (() => {}), onSuccess: options.onSuccess ?? (() => {}),
onOpen: options.onOpen ?? (() => {}), onOpen: options.onOpen ?? (() => {}),
onClose: options.onClose ?? (() => {}), onClose: options.onClose ?? (() => {}),
onHalfOpen: options.onHalfOpen ?? (() => {}),
} }
this.lastStateChange = Date.now() this.lastStateChange = Date.now()
} }
/** /**
* Executa operação protegida pelo circuit breaker * Check if the circuit allows execution
*/
canExecute(): boolean {
if (this.state === 'closed') {
return true
}
if (this.state === 'open') {
return false
}
// HALF_OPEN: allow limited requests for testing
return true
}
/**
* Execute an async operation with circuit breaker protection
*/ */
async execute<T>(operation: () => T | Promise<T>): Promise<T> { async execute<T>(operation: () => T | Promise<T>): Promise<T> {
this.totalCalls++ this.totalCalls++
// Verificar estado // Check if circuit allows execution
if (this.state === 'open') { if (this.state === 'open') {
this.totalRejected++
if (this.options.collectMetrics) { if (this.options.collectMetrics) {
metrics.errors.inc({ category: 'circuit_breaker', code: 'open' }) metrics.errors.inc({ category: 'circuit_breaker', code: 'rejected' })
} }
return this.options.fallback() as T return this.options.fallback() as T
} }
// Executar com timeout // Execute with timeout protection
try { try {
const result = await this.executeWithTimeout(operation) const result = await this.executeWithTimeout(operation)
this.recordSuccess() this.recordSuccess()
@@ -162,14 +221,16 @@ export class CircuitBreaker extends EventEmitter {
} }
/** /**
* Executa operação síncrona protegida * Execute a synchronous operation with circuit breaker protection
*/ */
executeSync<T>(operation: () => T): T { executeSync<T>(operation: () => T): T {
this.totalCalls++ this.totalCalls++
if (this.state === 'open') { if (this.state === 'open') {
this.totalRejected++
if (this.options.collectMetrics) { if (this.options.collectMetrics) {
metrics.errors.inc({ category: 'circuit_breaker', code: 'open' }) metrics.errors.inc({ category: 'circuit_breaker', code: 'rejected' })
} }
return this.options.fallback() as T return this.options.fallback() as T
@@ -186,7 +247,7 @@ export class CircuitBreaker extends EventEmitter {
} }
/** /**
* Executa com timeout * Execute operation with timeout wrapper
*/ */
private async executeWithTimeout<T>(operation: () => T | Promise<T>): Promise<T> { private async executeWithTimeout<T>(operation: () => T | Promise<T>): Promise<T> {
return new Promise<T>((resolve, reject) => { return new Promise<T>((resolve, reject) => {
@@ -207,12 +268,13 @@ export class CircuitBreaker extends EventEmitter {
} }
/** /**
* Registra sucesso * Record a successful operation
*/ */
private recordSuccess(): void { private recordSuccess(): void {
this.totalSuccesses++ this.totalSuccesses++
this.lastSuccessTime = Date.now() this.lastSuccessTime = Date.now()
this.failures = 0 this.consecutiveSuccesses++
this.consecutiveFailures = 0
if (this.options.collectMetrics) { if (this.options.collectMetrics) {
metrics.socketEvents.inc({ event: 'circuit_success' }) metrics.socketEvents.inc({ event: 'circuit_success' })
@@ -221,28 +283,34 @@ export class CircuitBreaker extends EventEmitter {
this.options.onSuccess() this.options.onSuccess()
this.emit('success') this.emit('success')
// In HALF_OPEN state, check if we can close the circuit
if (this.state === 'half-open') { if (this.state === 'half-open') {
this.successes++ if (this.consecutiveSuccesses >= this.options.successThreshold) {
if (this.successes >= this.options.successThreshold) {
this.transitionTo('closed') this.transitionTo('closed')
} }
} }
} }
/** /**
* Registra falha * Record a failed operation
*/ */
private recordFailure(error: Error): void { private recordFailure(error: Error): void {
// Verificar se erro deve contar como falha // Check if this error should count as a failure
if (!this.options.isFailure(error)) { if (!this.options.shouldCountError(error)) {
return return
} }
const now = Date.now()
this.totalFailures++ this.totalFailures++
this.lastFailureTime = Date.now() this.lastFailureTime = now
this.failures++ this.consecutiveFailures++
this.successes = 0 this.consecutiveSuccesses = 0
// Add to failure records for sliding window
this.failureRecords.push({ timestamp: now, error })
// Clean old failures outside the window
this.cleanOldFailures()
if (this.options.collectMetrics) { if (this.options.collectMetrics) {
metrics.errors.inc({ category: 'circuit_breaker', code: 'failure' }) metrics.errors.inc({ category: 'circuit_breaker', code: 'failure' })
@@ -251,15 +319,35 @@ export class CircuitBreaker extends EventEmitter {
this.options.onFailure(error) this.options.onFailure(error)
this.emit('failure', error) this.emit('failure', error)
// State transition logic
if (this.state === 'half-open') { if (this.state === 'half-open') {
// Any failure in HALF_OPEN immediately reopens the circuit
this.transitionTo('open') this.transitionTo('open')
} else if (this.state === 'closed' && this.failures >= this.options.failureThreshold) { } else if (this.state === 'closed') {
this.transitionTo('open') // Check if we should trip the circuit
const recentFailures = this.failureRecords.length
if (
this.totalCalls >= this.options.volumeThreshold &&
recentFailures >= this.options.failureThreshold
) {
this.transitionTo('open')
}
} }
} }
/** /**
* Transiciona para novo estado * Remove failure records outside the sliding window
*/
private cleanOldFailures(): void {
const cutoff = Date.now() - this.options.failureWindow
this.failureRecords = this.failureRecords.filter(
(record) => record.timestamp > cutoff
)
}
/**
* Transition to a new state
*/ */
private transitionTo(newState: CircuitState): void { private transitionTo(newState: CircuitState): void {
const oldState = this.state const oldState = this.state
@@ -271,31 +359,39 @@ export class CircuitBreaker extends EventEmitter {
this.state = newState this.state = newState
this.lastStateChange = Date.now() this.lastStateChange = Date.now()
// Limpar timer existente // Clear existing reset timer
if (this.resetTimer) { if (this.resetTimer) {
clearTimeout(this.resetTimer) clearTimeout(this.resetTimer)
this.resetTimer = undefined this.resetTimer = undefined
} }
// Resetar contadores baseado no novo estado // State-specific actions
if (newState === 'closed') { switch (newState) {
this.failures = 0 case 'closed':
this.successes = 0 this.consecutiveFailures = 0
this.options.onClose() this.consecutiveSuccesses = 0
this.emit('close') this.failureRecords = []
} else if (newState === 'open') { this.options.onClose()
this.successes = 0 this.emit('close')
this.options.onOpen() break
this.emit('open')
// Agendar tentativa de half-open case 'open':
this.resetTimer = setTimeout(() => { this.consecutiveSuccesses = 0
this.transitionTo('half-open') this.options.onOpen()
}, this.options.resetTimeout) this.emit('open')
} else if (newState === 'half-open') {
this.successes = 0 // Schedule transition to HALF_OPEN
this.failures = 0 this.resetTimer = setTimeout(() => {
this.emit('half-open') this.transitionTo('half-open')
}, this.options.resetTimeout)
break
case 'half-open':
this.consecutiveSuccesses = 0
this.consecutiveFailures = 0
this.options.onHalfOpen()
this.emit('half-open')
break
} }
this.options.onStateChange(oldState, newState) this.options.onStateChange(oldState, newState)
@@ -303,58 +399,68 @@ export class CircuitBreaker extends EventEmitter {
} }
/** /**
* Força abertura do circuito * Manually trip the circuit to OPEN state
*/ */
trip(): void { trip(): void {
this.transitionTo('open') this.transitionTo('open')
} }
/** /**
* Força fechamento do circuito * Manually reset the circuit to CLOSED state
*/ */
reset(): void { reset(): void {
this.transitionTo('closed') this.transitionTo('closed')
} }
/** /**
* Retorna estado atual * Get current circuit state
*/ */
getState(): CircuitState { getState(): CircuitState {
return this.state return this.state
} }
/** /**
* Verifica se circuito está aberto * Check if circuit is OPEN
*/ */
isOpen(): boolean { isOpen(): boolean {
return this.state === 'open' return this.state === 'open'
} }
/** /**
* Verifica se circuito está fechado * Check if circuit is CLOSED
*/ */
isClosed(): boolean { isClosed(): boolean {
return this.state === 'closed' return this.state === 'closed'
} }
/** /**
* Verifica se circuito está half-open * Check if circuit is HALF_OPEN
*/ */
isHalfOpen(): boolean { isHalfOpen(): boolean {
return this.state === 'half-open' return this.state === 'half-open'
} }
/** /**
* Retorna estatísticas * Get circuit breaker statistics
*/ */
getStats(): CircuitBreakerStats { getStats(): CircuitBreakerStats {
this.cleanOldFailures()
const failureRate = this.totalCalls > 0
? (this.totalFailures / this.totalCalls) * 100
: 0
return { return {
state: this.state, state: this.state,
failures: this.failures, failures: this.failureRecords.length,
successes: this.successes, successes: this.consecutiveSuccesses,
consecutiveFailures: this.consecutiveFailures,
consecutiveSuccesses: this.consecutiveSuccesses,
totalCalls: this.totalCalls, totalCalls: this.totalCalls,
totalFailures: this.totalFailures, totalFailures: this.totalFailures,
totalSuccesses: this.totalSuccesses, totalSuccesses: this.totalSuccesses,
totalRejected: this.totalRejected,
failureRate,
lastFailureTime: this.lastFailureTime, lastFailureTime: this.lastFailureTime,
lastSuccessTime: this.lastSuccessTime, lastSuccessTime: this.lastSuccessTime,
lastStateChange: this.lastStateChange, lastStateChange: this.lastStateChange,
@@ -365,38 +471,39 @@ export class CircuitBreaker extends EventEmitter {
} }
/** /**
* Retorna nome do circuit breaker * Get circuit breaker name
*/ */
getName(): string { getName(): string {
return this.options.name return this.options.name
} }
/** /**
* Destroy e limpa recursos * Destroy circuit breaker and clean up resources
*/ */
destroy(): void { destroy(): void {
if (this.resetTimer) { if (this.resetTimer) {
clearTimeout(this.resetTimer) clearTimeout(this.resetTimer)
} }
this.failureRecords = []
this.removeAllListeners() this.removeAllListeners()
} }
} }
/** /**
* Factory para criar circuit breaker * Factory function to create a circuit breaker
*/ */
export function createCircuitBreaker(options: CircuitBreakerOptions): CircuitBreaker { export function createCircuitBreaker(options: CircuitBreakerOptions): CircuitBreaker {
return new CircuitBreaker(options) return new CircuitBreaker(options)
} }
/** /**
* Registry de circuit breakers * Registry for managing multiple circuit breakers
*/ */
export class CircuitBreakerRegistry { export class CircuitBreakerRegistry {
private breakers: Map<string, CircuitBreaker> = new Map() private breakers: Map<string, CircuitBreaker> = new Map()
/** /**
* Obtém ou cria circuit breaker * Get or create a circuit breaker by name
*/ */
get(name: string, options?: Omit<CircuitBreakerOptions, 'name'>): CircuitBreaker { get(name: string, options?: Omit<CircuitBreakerOptions, 'name'>): CircuitBreaker {
if (!this.breakers.has(name)) { if (!this.breakers.has(name)) {
@@ -407,14 +514,14 @@ export class CircuitBreakerRegistry {
} }
/** /**
* Verifica se circuit breaker existe * Check if a circuit breaker exists
*/ */
has(name: string): boolean { has(name: string): boolean {
return this.breakers.has(name) return this.breakers.has(name)
} }
/** /**
* Remove circuit breaker * Remove a circuit breaker
*/ */
remove(name: string): boolean { remove(name: string): boolean {
const breaker = this.breakers.get(name) const breaker = this.breakers.get(name)
@@ -426,14 +533,14 @@ export class CircuitBreakerRegistry {
} }
/** /**
* Retorna todos os circuit breakers * Get all circuit breakers
*/ */
getAll(): Map<string, CircuitBreaker> { getAll(): Map<string, CircuitBreaker> {
return new Map(this.breakers) return new Map(this.breakers)
} }
/** /**
* Retorna estatísticas de todos os circuit breakers * Get statistics for all circuit breakers
*/ */
getAllStats(): Record<string, CircuitBreakerStats> { getAllStats(): Record<string, CircuitBreakerStats> {
const stats: Record<string, CircuitBreakerStats> = {} const stats: Record<string, CircuitBreakerStats> = {}
@@ -444,7 +551,7 @@ export class CircuitBreakerRegistry {
} }
/** /**
* Reseta todos os circuit breakers * Reset all circuit breakers to CLOSED state
*/ */
resetAll(): void { resetAll(): void {
for (const breaker of this.breakers.values()) { for (const breaker of this.breakers.values()) {
@@ -453,7 +560,7 @@ export class CircuitBreakerRegistry {
} }
/** /**
* Destroy todos os circuit breakers * Destroy all circuit breakers
*/ */
destroyAll(): void { destroyAll(): void {
for (const breaker of this.breakers.values()) { for (const breaker of this.breakers.values()) {
@@ -464,12 +571,22 @@ export class CircuitBreakerRegistry {
} }
/** /**
* Registry global * Global circuit breaker registry instance
*/ */
export const globalCircuitRegistry = new CircuitBreakerRegistry() export const globalCircuitRegistry = new CircuitBreakerRegistry()
/** /**
* Decorator para proteger método com circuit breaker * Decorator to protect a method with circuit breaker
*
* @example
* ```typescript
* class MyService {
* @circuitBreaker({ failureThreshold: 3 })
* async fetchData() {
* return await api.getData()
* }
* }
* ```
*/ */
export function circuitBreaker(options: Omit<CircuitBreakerOptions, 'name'> & { name?: string } = {}) { export function circuitBreaker(options: Omit<CircuitBreakerOptions, 'name'> & { name?: string } = {}) {
return function ( return function (
@@ -492,7 +609,15 @@ export function circuitBreaker(options: Omit<CircuitBreakerOptions, 'name'> & {
} }
/** /**
* Wrapper para função com circuit breaker * Wrap a function with circuit breaker protection
*
* @example
* ```typescript
* const protectedFetch = withCircuitBreaker(
* fetchData,
* { name: 'api-fetch', failureThreshold: 5 }
* )
* ```
*/ */
export function withCircuitBreaker<T extends (...args: unknown[]) => unknown>( export function withCircuitBreaker<T extends (...args: unknown[]) => unknown>(
fn: T, fn: T,
@@ -506,7 +631,7 @@ export function withCircuitBreaker<T extends (...args: unknown[]) => unknown>(
} }
/** /**
* Verifica saúde de todos os circuit breakers * Get health status of all circuit breakers
*/ */
export function getCircuitHealth(): { export function getCircuitHealth(): {
healthy: boolean healthy: boolean
@@ -529,4 +654,100 @@ export function getCircuitHealth(): {
} }
} }
/**
* Create a pre-configured circuit breaker for WhatsApp PreKey operations
*
* This circuit breaker is optimized for handling encryption/session errors
* that commonly occur with WhatsApp's Signal protocol implementation.
*
* @example
* ```typescript
* const preKeyBreaker = createPreKeyCircuitBreaker()
*
* async function sendEncryptedMessage(msg) {
* return preKeyBreaker.execute(async () => {
* return await encryptAndSend(msg)
* })
* }
* ```
*/
export function createPreKeyCircuitBreaker(
customOptions?: Partial<CircuitBreakerOptions>
): CircuitBreaker {
const preKeyErrorPatterns = [
'prekey',
'pre-key',
'session',
'signal',
'encrypt',
'decrypt',
'cipher',
'key',
]
return new CircuitBreaker({
name: 'prekey-operations',
failureThreshold: 5,
failureWindow: 60000,
resetTimeout: 30000,
successThreshold: 2,
shouldCountError: (error: Error) => {
const message = error.message.toLowerCase()
return preKeyErrorPatterns.some((pattern) => message.includes(pattern))
},
...customOptions,
})
}
/**
* Create a circuit breaker for WebSocket connection operations
*/
export function createConnectionCircuitBreaker(
customOptions?: Partial<CircuitBreakerOptions>
): CircuitBreaker {
const connectionErrorPatterns = [
'econnrefused',
'econnreset',
'etimedout',
'enotfound',
'socket',
'websocket',
'connection',
'network',
]
return new CircuitBreaker({
name: 'connection-operations',
failureThreshold: 3,
failureWindow: 30000,
resetTimeout: 60000,
successThreshold: 1,
shouldCountError: (error: Error) => {
const message = error.message.toLowerCase()
const code = (error as NodeJS.ErrnoException).code?.toLowerCase() || ''
return connectionErrorPatterns.some(
(pattern) => message.includes(pattern) || code.includes(pattern)
)
},
...customOptions,
})
}
/**
* Create a circuit breaker for message sending operations
*/
export function createMessageCircuitBreaker(
customOptions?: Partial<CircuitBreakerOptions>
): CircuitBreaker {
return new CircuitBreaker({
name: 'message-operations',
failureThreshold: 5,
failureWindow: 60000,
resetTimeout: 15000,
successThreshold: 2,
timeout: 30000,
...customOptions,
})
}
export default CircuitBreaker export default CircuitBreaker
+4 -4
View File
@@ -17,18 +17,18 @@ export * from './process-message'
export * from './message-retry-manager' export * from './message-retry-manager'
export * from './browser-utils' export * from './browser-utils'
// === Novos Utilitários de Observabilidade e Resiliência === // === Observability and Resilience Utilities ===
// Logging estruturado // Structured logging
export * from './structured-logger' export * from './structured-logger'
export * from './logger-adapter' export * from './logger-adapter'
export * from './baileys-logger' export * from './baileys-logger'
// Observabilidade e rastreamento // Observability and tracing
export * from './trace-context' export * from './trace-context'
export * from './prometheus-metrics' export * from './prometheus-metrics'
// Resiliência e performance // Resilience and performance
export * from './cache-utils' export * from './cache-utils'
export * from './circuit-breaker' export * from './circuit-breaker'
export * from './retry-utils' export * from './retry-utils'
+90 -89
View File
@@ -1,42 +1,43 @@
/** /**
* @fileoverview Exposição de métricas no formato Prometheus * Prometheus Metrics Exposition
*
* Provides:
* - Counters for event counting
* - Gauges for instantaneous values
* - Histograms for value distribution
* - Summaries for percentiles
* - /metrics endpoint ready for scraping
* - Dynamic labels
* - Baileys events integration
*
* Note: Works standalone without prom-client,
* but can be integrated with prom-client if available.
*
* @module Utils/prometheus-metrics * @module Utils/prometheus-metrics
*
* Fornece:
* - Counters para contagem de eventos
* - Gauges para valores instantâneos
* - Histograms para distribuição de valores
* - Summaries para percentis
* - Endpoint /metrics pronto para scraping
* - Labels dinâmicas
* - Integração com Baileys events
*
* Nota: Funciona de forma standalone sem prom-client,
* mas pode ser integrado com prom-client se disponível.
*/ */
/** /**
* Tipo de métrica * Metric type
*/ */
export type MetricType = 'counter' | 'gauge' | 'histogram' | 'summary' export type MetricType = 'counter' | 'gauge' | 'histogram' | 'summary'
/** /**
* Labels para métricas * Metric labels
*/ */
export type Labels = Record<string, string> export type Labels = Record<string, string>
/** /**
* Buckets padrão para histogramas (em ms) * Default histogram buckets (in ms)
*/ */
export const DEFAULT_BUCKETS = [5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000] export const DEFAULT_BUCKETS = [5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000]
/** /**
* Percentis padrão para summaries * Default summary percentiles
*/ */
export const DEFAULT_PERCENTILES = [0.5, 0.9, 0.95, 0.99] export const DEFAULT_PERCENTILES = [0.5, 0.9, 0.95, 0.99]
/** /**
* Interface base para métricas * Base metric interface
*/ */
export interface BaseMetric { export interface BaseMetric {
name: string name: string
@@ -46,7 +47,7 @@ export interface BaseMetric {
} }
/** /**
* Valor de uma métrica com labels * Metric value with labels
*/ */
export interface MetricValue { export interface MetricValue {
labels: Labels labels: Labels
@@ -55,7 +56,7 @@ export interface MetricValue {
} }
/** /**
* Valores de histograma * Histogram values
*/ */
export interface HistogramValue { export interface HistogramValue {
labels: Labels labels: Labels
@@ -65,7 +66,7 @@ export interface HistogramValue {
} }
/** /**
* Valores de summary * Summary values
*/ */
export interface SummaryValue { export interface SummaryValue {
labels: Labels labels: Labels
@@ -75,7 +76,7 @@ export interface SummaryValue {
} }
/** /**
* Classe Counter - incrementa monotonicamente * Counter class - monotonically increasing
*/ */
export class Counter implements BaseMetric { export class Counter implements BaseMetric {
readonly type = 'counter' as const readonly type = 'counter' as const
@@ -88,7 +89,7 @@ export class Counter implements BaseMetric {
) {} ) {}
/** /**
* Incrementa o counter * Increment the counter
*/ */
inc(labelsOrValue?: Labels | number, value?: number): void { inc(labelsOrValue?: Labels | number, value?: number): void {
let labels: Labels = {} let labels: Labels = {}
@@ -117,7 +118,7 @@ export class Counter implements BaseMetric {
} }
/** /**
* Retorna valor atual * Get current value
*/ */
get(labels: Labels = {}): number { get(labels: Labels = {}): number {
const key = this.labelsToKey(labels) const key = this.labelsToKey(labels)
@@ -125,7 +126,7 @@ export class Counter implements BaseMetric {
} }
/** /**
* Reseta o counter * Reset the counter
*/ */
reset(labels?: Labels): void { reset(labels?: Labels): void {
if (labels) { if (labels) {
@@ -137,7 +138,7 @@ export class Counter implements BaseMetric {
} }
/** /**
* Retorna todos os valores * Get all values
*/ */
getValues(): MetricValue[] { getValues(): MetricValue[] {
return Array.from(this.values.values()) return Array.from(this.values.values())
@@ -148,7 +149,7 @@ export class Counter implements BaseMetric {
} }
/** /**
* Cria versão com labels pré-definidas * Create version with pre-defined labels
*/ */
labels(labels: Labels): { inc: (value?: number) => void } { labels(labels: Labels): { inc: (value?: number) => void } {
return { return {
@@ -158,7 +159,7 @@ export class Counter implements BaseMetric {
} }
/** /**
* Classe Gauge - valor que pode aumentar e diminuir * Gauge class - value that can increase and decrease
*/ */
export class Gauge implements BaseMetric { export class Gauge implements BaseMetric {
readonly type = 'gauge' as const readonly type = 'gauge' as const
@@ -171,7 +172,7 @@ export class Gauge implements BaseMetric {
) {} ) {}
/** /**
* Define valor * Set value
*/ */
set(labelsOrValue: Labels | number, value?: number): void { set(labelsOrValue: Labels | number, value?: number): void {
let labels: Labels = {} let labels: Labels = {}
@@ -193,7 +194,7 @@ export class Gauge implements BaseMetric {
} }
/** /**
* Incrementa valor * Increment value
*/ */
inc(labelsOrValue?: Labels | number, value?: number): void { inc(labelsOrValue?: Labels | number, value?: number): void {
let labels: Labels = {} let labels: Labels = {}
@@ -214,7 +215,7 @@ export class Gauge implements BaseMetric {
} }
/** /**
* Decrementa valor * Decrement value
*/ */
dec(labelsOrValue?: Labels | number, value?: number): void { dec(labelsOrValue?: Labels | number, value?: number): void {
let labels: Labels = {} let labels: Labels = {}
@@ -235,14 +236,14 @@ export class Gauge implements BaseMetric {
} }
/** /**
* Define para timestamp atual * Set to current timestamp
*/ */
setToCurrentTime(labels: Labels = {}): void { setToCurrentTime(labels: Labels = {}): void {
this.set(labels, Date.now() / 1000) this.set(labels, Date.now() / 1000)
} }
/** /**
* Retorna valor atual * Get current value
*/ */
get(labels: Labels = {}): number { get(labels: Labels = {}): number {
const key = this.labelsToKey(labels) const key = this.labelsToKey(labels)
@@ -250,7 +251,7 @@ export class Gauge implements BaseMetric {
} }
/** /**
* Reseta o gauge * Reset the gauge
*/ */
reset(labels?: Labels): void { reset(labels?: Labels): void {
if (labels) { if (labels) {
@@ -262,7 +263,7 @@ export class Gauge implements BaseMetric {
} }
/** /**
* Retorna todos os valores * Get all values
*/ */
getValues(): MetricValue[] { getValues(): MetricValue[] {
return Array.from(this.values.values()) return Array.from(this.values.values())
@@ -273,7 +274,7 @@ export class Gauge implements BaseMetric {
} }
/** /**
* Cria versão com labels pré-definidas * Create version with pre-defined labels
*/ */
labels(labels: Labels): { labels(labels: Labels): {
set: (value: number) => void set: (value: number) => void
@@ -288,12 +289,12 @@ export class Gauge implements BaseMetric {
} }
/** /**
* Timer helper - retorna função para parar e registrar duração * Timer helper - returns function to stop and record duration
*/ */
startTimer(labels: Labels = {}): () => number { startTimer(labels: Labels = {}): () => number {
const start = process.hrtime.bigint() const start = process.hrtime.bigint()
return () => { return () => {
const duration = Number(process.hrtime.bigint() - start) / 1_000_000_000 // segundos const duration = Number(process.hrtime.bigint() - start) / 1_000_000_000 // seconds
this.set(labels, duration) this.set(labels, duration)
return duration return duration
} }
@@ -301,7 +302,7 @@ export class Gauge implements BaseMetric {
} }
/** /**
* Classe Histogram - distribuição de valores em buckets * Histogram class - distribution of values in buckets
*/ */
export class Histogram implements BaseMetric { export class Histogram implements BaseMetric {
readonly type = 'histogram' as const readonly type = 'histogram' as const
@@ -318,7 +319,7 @@ export class Histogram implements BaseMetric {
} }
/** /**
* Observa um valor * Observe a value
*/ */
observe(labelsOrValue: Labels | number, value?: number): void { observe(labelsOrValue: Labels | number, value?: number): void {
let labels: Labels = {} let labels: Labels = {}
@@ -344,7 +345,7 @@ export class Histogram implements BaseMetric {
this.values.set(key, histValue) this.values.set(key, histValue)
} }
// Incrementar buckets apropriados // Increment appropriate buckets
for (const bucket of this.buckets) { for (const bucket of this.buckets) {
if (observeValue <= bucket) { if (observeValue <= bucket) {
histValue.buckets.set(bucket, (histValue.buckets.get(bucket) ?? 0) + 1) histValue.buckets.set(bucket, (histValue.buckets.get(bucket) ?? 0) + 1)
@@ -368,7 +369,7 @@ export class Histogram implements BaseMetric {
} }
/** /**
* Retorna valores do histogram * Get histogram values
*/ */
get(labels: Labels = {}): HistogramValue | undefined { get(labels: Labels = {}): HistogramValue | undefined {
const key = this.labelsToKey(labels) const key = this.labelsToKey(labels)
@@ -376,7 +377,7 @@ export class Histogram implements BaseMetric {
} }
/** /**
* Reseta o histogram * Reset the histogram
*/ */
reset(labels?: Labels): void { reset(labels?: Labels): void {
if (labels) { if (labels) {
@@ -388,14 +389,14 @@ export class Histogram implements BaseMetric {
} }
/** /**
* Retorna todos os valores * Get all values
*/ */
getValues(): HistogramValue[] { getValues(): HistogramValue[] {
return Array.from(this.values.values()) return Array.from(this.values.values())
} }
/** /**
* Retorna buckets configurados * Get configured buckets
*/ */
getBuckets(): number[] { getBuckets(): number[] {
return [...this.buckets] return [...this.buckets]
@@ -406,7 +407,7 @@ export class Histogram implements BaseMetric {
} }
/** /**
* Cria versão com labels pré-definidas * Create version with pre-defined labels
*/ */
labels(labels: Labels): { labels(labels: Labels): {
observe: (value: number) => void observe: (value: number) => void
@@ -420,7 +421,7 @@ export class Histogram implements BaseMetric {
} }
/** /**
* Classe Summary - percentis de valores * Summary class - value percentiles
*/ */
export class Summary implements BaseMetric { export class Summary implements BaseMetric {
readonly type = 'summary' as const readonly type = 'summary' as const
@@ -441,7 +442,7 @@ export class Summary implements BaseMetric {
} }
/** /**
* Observa um valor * Observe a value
*/ */
observe(labelsOrValue: Labels | number, value?: number): void { observe(labelsOrValue: Labels | number, value?: number): void {
let labels: Labels = {} let labels: Labels = {}
@@ -471,7 +472,7 @@ export class Summary implements BaseMetric {
summaryValue.sum += observeValue summaryValue.sum += observeValue
summaryValue.count++ summaryValue.count++
// Limitar tamanho // Limit size
if (summaryValue.values.length > this.maxSize) { if (summaryValue.values.length > this.maxSize) {
const removed = summaryValue.values.shift() const removed = summaryValue.values.shift()
if (removed !== undefined) { if (removed !== undefined) {
@@ -494,7 +495,7 @@ export class Summary implements BaseMetric {
} }
/** /**
* Calcula percentil * Calculate percentile
*/ */
getPercentile(labels: Labels, percentile: number): number | undefined { getPercentile(labels: Labels, percentile: number): number | undefined {
const key = this.labelsToKey(labels) const key = this.labelsToKey(labels)
@@ -510,7 +511,7 @@ export class Summary implements BaseMetric {
} }
/** /**
* Retorna valores do summary * Get summary values
*/ */
get(labels: Labels = {}): SummaryValue | undefined { get(labels: Labels = {}): SummaryValue | undefined {
const key = this.labelsToKey(labels) const key = this.labelsToKey(labels)
@@ -518,7 +519,7 @@ export class Summary implements BaseMetric {
} }
/** /**
* Reseta o summary * Reset the summary
*/ */
reset(labels?: Labels): void { reset(labels?: Labels): void {
if (labels) { if (labels) {
@@ -530,14 +531,14 @@ export class Summary implements BaseMetric {
} }
/** /**
* Retorna todos os valores * Get all values
*/ */
getValues(): SummaryValue[] { getValues(): SummaryValue[] {
return Array.from(this.values.values()) return Array.from(this.values.values())
} }
/** /**
* Retorna percentis configurados * Get configured percentiles
*/ */
getPercentiles(): number[] { getPercentiles(): number[] {
return [...this.percentiles] return [...this.percentiles]
@@ -548,7 +549,7 @@ export class Summary implements BaseMetric {
} }
/** /**
* Cria versão com labels pré-definidas * Create version with pre-defined labels
*/ */
labels(labels: Labels): { labels(labels: Labels): {
observe: (value: number) => void observe: (value: number) => void
@@ -562,7 +563,7 @@ export class Summary implements BaseMetric {
} }
/** /**
* Registry de métricas * Metrics registry
*/ */
export class MetricsRegistry { export class MetricsRegistry {
private metrics: Map<string, Counter | Gauge | Histogram | Summary> = new Map() private metrics: Map<string, Counter | Gauge | Histogram | Summary> = new Map()
@@ -575,7 +576,7 @@ export class MetricsRegistry {
} }
/** /**
* Registra uma métrica * Register a metric
*/ */
register<T extends Counter | Gauge | Histogram | Summary>(metric: T): T { register<T extends Counter | Gauge | Histogram | Summary>(metric: T): T {
const fullName = this.prefix ? `${this.prefix}_${metric.name}` : metric.name const fullName = this.prefix ? `${this.prefix}_${metric.name}` : metric.name
@@ -584,7 +585,7 @@ export class MetricsRegistry {
} }
/** /**
* Obtém uma métrica * Get a metric
*/ */
get(name: string): Counter | Gauge | Histogram | Summary | undefined { get(name: string): Counter | Gauge | Histogram | Summary | undefined {
const fullName = this.prefix ? `${this.prefix}_${name}` : name const fullName = this.prefix ? `${this.prefix}_${name}` : name
@@ -592,7 +593,7 @@ export class MetricsRegistry {
} }
/** /**
* Remove uma métrica * Remove a metric
*/ */
remove(name: string): boolean { remove(name: string): boolean {
const fullName = this.prefix ? `${this.prefix}_${name}` : name const fullName = this.prefix ? `${this.prefix}_${name}` : name
@@ -600,7 +601,7 @@ export class MetricsRegistry {
} }
/** /**
* Reseta todas as métricas * Reset all metrics
*/ */
resetAll(): void { resetAll(): void {
for (const metric of this.metrics.values()) { for (const metric of this.metrics.values()) {
@@ -609,7 +610,7 @@ export class MetricsRegistry {
} }
/** /**
* Retorna métricas no formato Prometheus * Return metrics in Prometheus format
*/ */
async metrics(): Promise<string> { async metrics(): Promise<string> {
const lines: string[] = [] const lines: string[] = []
@@ -675,7 +676,7 @@ export class MetricsRegistry {
} }
/** /**
* Retorna content type para Prometheus * Return content type for Prometheus
*/ */
contentType(): string { contentType(): string {
return 'text/plain; version=0.0.4; charset=utf-8' return 'text/plain; version=0.0.4; charset=utf-8'
@@ -694,84 +695,84 @@ export class MetricsRegistry {
} }
} }
// === Métricas Padrão do Baileys === // === Default Baileys Metrics ===
/** /**
* Registry global para métricas do Baileys * Global registry for Baileys metrics
*/ */
export const baileysMetrics = new MetricsRegistry({ prefix: 'baileys' }) export const baileysMetrics = new MetricsRegistry({ prefix: 'baileys' })
/** /**
* Métricas pré-definidas para Baileys * Pre-defined metrics for Baileys
*/ */
export const metrics = { export const metrics = {
// Conexão // Connection
connectionAttempts: baileysMetrics.register( connectionAttempts: baileysMetrics.register(
new Counter('connection_attempts_total', 'Total de tentativas de conexão', ['status']) new Counter('connection_attempts_total', 'Total connection attempts', ['status'])
), ),
connectionState: baileysMetrics.register( connectionState: baileysMetrics.register(
new Gauge('connection_state', 'Estado atual da conexão (0=desconectado, 1=conectado)', ['instance']) new Gauge('connection_state', 'Current connection state (0=disconnected, 1=connected)', ['instance'])
), ),
connectionDuration: baileysMetrics.register( connectionDuration: baileysMetrics.register(
new Gauge('connection_duration_seconds', 'Duração da conexão atual em segundos', ['instance']) new Gauge('connection_duration_seconds', 'Current connection duration in seconds', ['instance'])
), ),
// Mensagens // Messages
messagesSent: baileysMetrics.register( messagesSent: baileysMetrics.register(
new Counter('messages_sent_total', 'Total de mensagens enviadas', ['type']) new Counter('messages_sent_total', 'Total messages sent', ['type'])
), ),
messagesReceived: baileysMetrics.register( messagesReceived: baileysMetrics.register(
new Counter('messages_received_total', 'Total de mensagens recebidas', ['type']) new Counter('messages_received_total', 'Total messages received', ['type'])
), ),
messageLatency: baileysMetrics.register( messageLatency: baileysMetrics.register(
new Histogram('message_latency_ms', 'Latência de envio de mensagem em ms', ['type'], [10, 50, 100, 250, 500, 1000, 2500, 5000]) new Histogram('message_latency_ms', 'Message send latency in ms', ['type'], [10, 50, 100, 250, 500, 1000, 2500, 5000])
), ),
// Mídia // Media
mediaUploads: baileysMetrics.register( mediaUploads: baileysMetrics.register(
new Counter('media_uploads_total', 'Total de uploads de mídia', ['type', 'status']) new Counter('media_uploads_total', 'Total media uploads', ['type', 'status'])
), ),
mediaDownloads: baileysMetrics.register( mediaDownloads: baileysMetrics.register(
new Counter('media_downloads_total', 'Total de downloads de mídia', ['type', 'status']) new Counter('media_downloads_total', 'Total media downloads', ['type', 'status'])
), ),
mediaSize: baileysMetrics.register( mediaSize: baileysMetrics.register(
new Histogram('media_size_bytes', 'Tamanho de mídia em bytes', ['type', 'direction'], [1024, 10240, 102400, 1048576, 10485760]) new Histogram('media_size_bytes', 'Media size in bytes', ['type', 'direction'], [1024, 10240, 102400, 1048576, 10485760])
), ),
// Erros // Errors
errors: baileysMetrics.register( errors: baileysMetrics.register(
new Counter('errors_total', 'Total de erros', ['category', 'code']) new Counter('errors_total', 'Total errors', ['category', 'code'])
), ),
// Retries // Retries
retries: baileysMetrics.register( retries: baileysMetrics.register(
new Counter('retries_total', 'Total de retentativas', ['operation']) new Counter('retries_total', 'Total retries', ['operation'])
), ),
retryLatency: baileysMetrics.register( retryLatency: baileysMetrics.register(
new Histogram('retry_latency_ms', 'Latência de retentativas em ms', ['operation']) new Histogram('retry_latency_ms', 'Retry latency in ms', ['operation'])
), ),
// Socket // Socket
socketEvents: baileysMetrics.register( socketEvents: baileysMetrics.register(
new Counter('socket_events_total', 'Total de eventos de socket', ['event']) new Counter('socket_events_total', 'Total socket events', ['event'])
), ),
socketLatency: baileysMetrics.register( socketLatency: baileysMetrics.register(
new Histogram('socket_latency_ms', 'Latência de operações de socket em ms', ['operation']) new Histogram('socket_latency_ms', 'Socket operation latency in ms', ['operation'])
), ),
// Criptografia // Encryption
encryptionOperations: baileysMetrics.register( encryptionOperations: baileysMetrics.register(
new Counter('encryption_operations_total', 'Total de operações de criptografia', ['operation']) new Counter('encryption_operations_total', 'Total encryption operations', ['operation'])
), ),
// Cache // Cache
cacheHits: baileysMetrics.register(new Counter('cache_hits_total', 'Total de cache hits', ['cache'])), cacheHits: baileysMetrics.register(new Counter('cache_hits_total', 'Total cache hits', ['cache'])),
cacheMisses: baileysMetrics.register(new Counter('cache_misses_total', 'Total de cache misses', ['cache'])), cacheMisses: baileysMetrics.register(new Counter('cache_misses_total', 'Total cache misses', ['cache'])),
cacheSize: baileysMetrics.register(new Gauge('cache_size', 'Tamanho atual do cache', ['cache'])), cacheSize: baileysMetrics.register(new Gauge('cache_size', 'Current cache size', ['cache'])),
} }
/** /**
* Helper para criar endpoint HTTP de métricas * Helper to create HTTP metrics endpoint
*/ */
export function createMetricsHandler(registry: MetricsRegistry = baileysMetrics) { export function createMetricsHandler(registry: MetricsRegistry = baileysMetrics) {
return async (_req: unknown, res: { setHeader: (name: string, value: string) => void; end: (body: string) => void }) => { return async (_req: unknown, res: { setHeader: (name: string, value: string) => void; end: (body: string) => void }) => {
+75 -74
View File
@@ -1,15 +1,16 @@
/** /**
* @fileoverview Lógica de retry inteligente * Smart Retry Logic
* @module Utils/retry-utils
* *
* Fornece: * Provides:
* - Exponential backoff * - Exponential backoff
* - Jitter para evitar thundering herd * - Jitter to avoid thundering herd
* - Max attempts configurável * - Configurable max attempts
* - Predicates de retry customizáveis * - Customizable retry predicates
* - Integração com circuit breaker * - Circuit breaker integration
* - Hooks de eventos * - Event hooks
* - Cancelamento * - Cancellation support
*
* @module Utils/retry-utils
*/ */
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
@@ -17,48 +18,48 @@ import { metrics } from './prometheus-metrics.js'
import type { CircuitBreaker } from './circuit-breaker.js' import type { CircuitBreaker } from './circuit-breaker.js'
/** /**
* Estratégias de backoff * Backoff strategies
*/ */
export type BackoffStrategy = 'exponential' | 'linear' | 'constant' | 'fibonacci' export type BackoffStrategy = 'exponential' | 'linear' | 'constant' | 'fibonacci'
/** /**
* Opções de configuração de retry * Retry configuration options
*/ */
export interface RetryOptions { export interface RetryOptions {
/** Número máximo de tentativas (default: 3) */ /** Maximum number of attempts (default: 3) */
maxAttempts?: number maxAttempts?: number
/** Delay base em ms (default: 1000) */ /** Base delay in ms (default: 1000) */
baseDelay?: number baseDelay?: number
/** Delay máximo em ms (default: 30000) */ /** Maximum delay in ms (default: 30000) */
maxDelay?: number maxDelay?: number
/** Estratégia de backoff (default: exponential) */ /** Backoff strategy (default: exponential) */
backoffStrategy?: BackoffStrategy backoffStrategy?: BackoffStrategy
/** Multiplicador para exponential backoff (default: 2) */ /** Multiplier for exponential backoff (default: 2) */
backoffMultiplier?: number backoffMultiplier?: number
/** Percentual de jitter (0-1, default: 0.1) */ /** Jitter percentage (0-1, default: 0.1) */
jitter?: number jitter?: number
/** Função para determinar se deve retry */ /** Function to determine if should retry */
shouldRetry?: (error: Error, attempt: number) => boolean | Promise<boolean> shouldRetry?: (error: Error, attempt: number) => boolean | Promise<boolean>
/** Timeout por tentativa em ms */ /** Timeout per attempt in ms */
timeout?: number timeout?: number
/** Nome da operação para métricas */ /** Operation name for metrics */
operationName?: string operationName?: string
/** Coletar métricas */ /** Collect metrics */
collectMetrics?: boolean collectMetrics?: boolean
/** Circuit breaker para integração */ /** Circuit breaker for integration */
circuitBreaker?: CircuitBreaker circuitBreaker?: CircuitBreaker
/** Callback antes de cada retry */ /** Callback before each retry */
onRetry?: (error: Error, attempt: number, delay: number) => void | Promise<void> onRetry?: (error: Error, attempt: number, delay: number) => void | Promise<void>
/** Callback em sucesso */ /** Callback on success */
onSuccess?: (result: unknown, attempt: number) => void onSuccess?: (result: unknown, attempt: number) => void
/** Callback em falha final */ /** Callback on final failure */
onFailure?: (error: Error, attempts: number) => void onFailure?: (error: Error, attempts: number) => void
/** Signal para cancelamento */ /** Signal for cancellation */
abortSignal?: AbortSignal abortSignal?: AbortSignal
} }
/** /**
* Resultado de operação com retry * Result of operation with retry
*/ */
export interface RetryResult<T> { export interface RetryResult<T> {
success: boolean success: boolean
@@ -70,7 +71,7 @@ export interface RetryResult<T> {
} }
/** /**
* Contexto de retry * Retry context
*/ */
export interface RetryContext { export interface RetryContext {
attempt: number attempt: number
@@ -81,7 +82,7 @@ export interface RetryContext {
} }
/** /**
* Erro de retry esgotado * Retry exhausted error
*/ */
export class RetryExhaustedError extends Error { export class RetryExhaustedError extends Error {
constructor( constructor(
@@ -97,7 +98,7 @@ export class RetryExhaustedError extends Error {
} }
/** /**
* Erro de abort * Abort error
*/ */
export class RetryAbortedError extends Error { export class RetryAbortedError extends Error {
constructor(public readonly attempt: number) { constructor(public readonly attempt: number) {
@@ -107,7 +108,7 @@ export class RetryAbortedError extends Error {
} }
/** /**
* Calcula delay com base na estratégia * Calculate delay based on strategy
*/ */
export function calculateDelay( export function calculateDelay(
attempt: number, attempt: number,
@@ -142,10 +143,10 @@ export function calculateDelay(
delay = baseDelay delay = baseDelay
} }
// Aplicar cap de delay máximo // Apply max delay cap
delay = Math.min(delay, maxDelay) delay = Math.min(delay, maxDelay)
// Aplicar jitter // Apply jitter
if (jitter > 0) { if (jitter > 0) {
const jitterAmount = delay * jitter const jitterAmount = delay * jitter
delay = delay + (Math.random() * 2 - 1) * jitterAmount delay = delay + (Math.random() * 2 - 1) * jitterAmount
@@ -155,7 +156,7 @@ export function calculateDelay(
} }
/** /**
* Calcula número de Fibonacci * Calculate Fibonacci number
*/ */
function fibonacciNumber(n: number): number { function fibonacciNumber(n: number): number {
if (n <= 1) return 1 if (n <= 1) return 1
@@ -170,7 +171,7 @@ function fibonacciNumber(n: number): number {
} }
/** /**
* Sleep com suporte a abort * Sleep with abort support
*/ */
async function sleep(ms: number, signal?: AbortSignal): Promise<void> { async function sleep(ms: number, signal?: AbortSignal): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@@ -194,7 +195,7 @@ async function sleep(ms: number, signal?: AbortSignal): Promise<void> {
} }
/** /**
* Executa operação com timeout * Execute operation with timeout
*/ */
async function executeWithTimeout<T>( async function executeWithTimeout<T>(
operation: () => Promise<T>, operation: () => Promise<T>,
@@ -236,7 +237,7 @@ async function executeWithTimeout<T>(
} }
/** /**
* Função principal de retry * Main retry function
*/ */
export async function retry<T>( export async function retry<T>(
operation: (context: RetryContext) => T | Promise<T>, operation: (context: RetryContext) => T | Promise<T>,
@@ -269,7 +270,7 @@ export async function retry<T>(
let lastError: Error | undefined let lastError: Error | undefined
// Verificar abort inicial // Check initial abort
if (config.abortSignal?.aborted) { if (config.abortSignal?.aborted) {
throw new RetryAbortedError(0) throw new RetryAbortedError(0)
} }
@@ -277,19 +278,19 @@ export async function retry<T>(
for (let attempt = 1; attempt <= config.maxAttempts; attempt++) { for (let attempt = 1; attempt <= config.maxAttempts; attempt++) {
context.attempt = attempt context.attempt = attempt
// Verificar abort // Check abort
if (config.abortSignal?.aborted) { if (config.abortSignal?.aborted) {
context.aborted = true context.aborted = true
throw new RetryAbortedError(attempt) throw new RetryAbortedError(attempt)
} }
// Verificar circuit breaker // Check circuit breaker
if (config.circuitBreaker?.isOpen()) { if (config.circuitBreaker?.isOpen()) {
throw new Error(`Circuit breaker "${config.circuitBreaker.getName()}" is open`) throw new Error(`Circuit breaker "${config.circuitBreaker.getName()}" is open`)
} }
try { try {
// Executar operação // Execute operation
let result: T let result: T
if (config.timeout) { if (config.timeout) {
@@ -302,7 +303,7 @@ export async function retry<T>(
result = await operation(context) result = await operation(context)
} }
// Sucesso // Success
if (config.collectMetrics) { if (config.collectMetrics) {
metrics.retries.inc({ operation: config.operationName }) metrics.retries.inc({ operation: config.operationName })
} }
@@ -313,11 +314,11 @@ export async function retry<T>(
lastError = error as Error lastError = error as Error
context.lastError = lastError context.lastError = lastError
// Verificar se deve retry // Check if should retry
const shouldRetry = await config.shouldRetry(lastError, attempt) const shouldRetry = await config.shouldRetry(lastError, attempt)
if (!shouldRetry || attempt >= config.maxAttempts) { if (!shouldRetry || attempt >= config.maxAttempts) {
// Falha final // Final failure
if (config.collectMetrics) { if (config.collectMetrics) {
metrics.errors.inc({ category: 'retry', code: 'exhausted' }) metrics.errors.inc({ category: 'retry', code: 'exhausted' })
} }
@@ -327,7 +328,7 @@ export async function retry<T>(
throw new RetryExhaustedError(lastError, attempt, config.operationName) throw new RetryExhaustedError(lastError, attempt, config.operationName)
} }
// Calcular delay // Calculate delay
const delay = calculateDelay( const delay = calculateDelay(
attempt, attempt,
config.baseDelay, config.baseDelay,
@@ -337,24 +338,24 @@ export async function retry<T>(
config.jitter config.jitter
) )
// Callback de retry // Retry callback
await config.onRetry(lastError, attempt, delay) await config.onRetry(lastError, attempt, delay)
if (config.collectMetrics) { if (config.collectMetrics) {
metrics.retryLatency.observe({ operation: config.operationName }, delay) metrics.retryLatency.observe({ operation: config.operationName }, delay)
} }
// Aguardar delay // Wait for delay
await sleep(delay, config.abortSignal) await sleep(delay, config.abortSignal)
} }
} }
// Nunca deve chegar aqui, mas TypeScript precisa // Should never reach here, but TypeScript needs this
throw new RetryExhaustedError(lastError || new Error('Unknown error'), config.maxAttempts, config.operationName) throw new RetryExhaustedError(lastError || new Error('Unknown error'), config.maxAttempts, config.operationName)
} }
/** /**
* Retry com resultado detalhado * Retry with detailed result
*/ */
export async function retryWithResult<T>( export async function retryWithResult<T>(
operation: (context: RetryContext) => T | Promise<T>, operation: (context: RetryContext) => T | Promise<T>,
@@ -393,7 +394,7 @@ export async function retryWithResult<T>(
} }
/** /**
* Factory para criar função de retry configurada * Factory to create configured retry function
*/ */
export function createRetrier(defaultOptions: RetryOptions = {}) { export function createRetrier(defaultOptions: RetryOptions = {}) {
return <T>( return <T>(
@@ -405,7 +406,7 @@ export function createRetrier(defaultOptions: RetryOptions = {}) {
} }
/** /**
* Decorator para adicionar retry a método * Decorator to add retry to method
*/ */
export function withRetry(options: RetryOptions = {}) { export function withRetry(options: RetryOptions = {}) {
return function ( return function (
@@ -428,7 +429,7 @@ export function withRetry(options: RetryOptions = {}) {
} }
/** /**
* Wrapper para função com retry * Wrapper for function with retry
*/ */
export function retryable<T extends (...args: unknown[]) => unknown>( export function retryable<T extends (...args: unknown[]) => unknown>(
fn: T, fn: T,
@@ -440,7 +441,7 @@ export function retryable<T extends (...args: unknown[]) => unknown>(
} }
/** /**
* Classe para gerenciar retries com estado * Class to manage retries with state
*/ */
export class RetryManager extends EventEmitter { export class RetryManager extends EventEmitter {
private activeRetries: Map<string, { cancel: () => void; context: RetryContext }> = new Map() private activeRetries: Map<string, { cancel: () => void; context: RetryContext }> = new Map()
@@ -452,14 +453,14 @@ export class RetryManager extends EventEmitter {
} }
/** /**
* Executa operação com retry * Execute operation with retry
*/ */
async execute<T>( async execute<T>(
id: string, id: string,
operation: (context: RetryContext) => T | Promise<T>, operation: (context: RetryContext) => T | Promise<T>,
options?: RetryOptions options?: RetryOptions
): Promise<T> { ): Promise<T> {
// Cancelar retry anterior com mesmo ID // Cancel previous retry with same ID
this.cancel(id) this.cancel(id)
const abortController = new AbortController() const abortController = new AbortController()
@@ -487,7 +488,7 @@ export class RetryManager extends EventEmitter {
} }
/** /**
* Cancela retry em andamento * Cancel in-progress retry
*/ */
cancel(id: string): boolean { cancel(id: string): boolean {
const active = this.activeRetries.get(id) const active = this.activeRetries.get(id)
@@ -501,7 +502,7 @@ export class RetryManager extends EventEmitter {
} }
/** /**
* Cancela todos os retries * Cancel all retries
*/ */
cancelAll(): void { cancelAll(): void {
for (const [id, active] of this.activeRetries) { for (const [id, active] of this.activeRetries) {
@@ -512,21 +513,21 @@ export class RetryManager extends EventEmitter {
} }
/** /**
* Verifica se há retry ativo * Check if there is an active retry
*/ */
isActive(id: string): boolean { isActive(id: string): boolean {
return this.activeRetries.has(id) return this.activeRetries.has(id)
} }
/** /**
* Retorna contexto de retry ativo * Return active retry context
*/ */
getContext(id: string): RetryContext | undefined { getContext(id: string): RetryContext | undefined {
return this.activeRetries.get(id)?.context return this.activeRetries.get(id)?.context
} }
/** /**
* Retorna IDs de retries ativos * Return active retry IDs
*/ */
getActiveIds(): string[] { getActiveIds(): string[] {
return Array.from(this.activeRetries.keys()) return Array.from(this.activeRetries.keys())
@@ -534,36 +535,36 @@ export class RetryManager extends EventEmitter {
} }
/** /**
* Predicates comuns para shouldRetry * Common predicates for shouldRetry
*/ */
export const retryPredicates = { export const retryPredicates = {
/** Sempre retry (até max attempts) */ /** Always retry (up to max attempts) */
always: () => true, always: () => true,
/** Nunca retry */ /** Never retry */
never: () => false, never: () => false,
/** Retry apenas em erros de rede */ /** Retry only on network errors */
onNetworkError: (error: Error) => { onNetworkError: (error: Error) => {
const networkErrors = ['ECONNREFUSED', 'ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND', 'EAI_AGAIN'] const networkErrors = ['ECONNREFUSED', 'ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND', 'EAI_AGAIN']
return networkErrors.some((code) => error.message.includes(code) || (error as NodeJS.ErrnoException).code === code) return networkErrors.some((code) => error.message.includes(code) || (error as NodeJS.ErrnoException).code === code)
}, },
/** Retry apenas em erros específicos */ /** Retry only on specific errors */
onErrorCodes: onErrorCodes:
(codes: string[]) => (codes: string[]) =>
(error: Error): boolean => { (error: Error): boolean => {
return codes.some((code) => error.message.includes(code) || (error as NodeJS.ErrnoException).code === code) return codes.some((code) => error.message.includes(code) || (error as NodeJS.ErrnoException).code === code)
}, },
/** Retry exceto em erros específicos */ /** Retry except on specific errors */
exceptErrorCodes: exceptErrorCodes:
(codes: string[]) => (codes: string[]) =>
(error: Error): boolean => { (error: Error): boolean => {
return !codes.some((code) => error.message.includes(code) || (error as NodeJS.ErrnoException).code === code) return !codes.some((code) => error.message.includes(code) || (error as NodeJS.ErrnoException).code === code)
}, },
/** Retry em erros HTTP 5xx ou timeout */ /** Retry on HTTP 5xx errors or timeout */
onServerError: (error: Error) => { onServerError: (error: Error) => {
const message = error.message.toLowerCase() const message = error.message.toLowerCase()
return ( return (
@@ -575,14 +576,14 @@ export const retryPredicates = {
) )
}, },
/** Combina múltiplos predicates com OR */ /** Combine multiple predicates with OR */
or: or:
(...predicates: Array<(error: Error, attempt: number) => boolean>) => (...predicates: Array<(error: Error, attempt: number) => boolean>) =>
(error: Error, attempt: number): boolean => { (error: Error, attempt: number): boolean => {
return predicates.some((p) => p(error, attempt)) return predicates.some((p) => p(error, attempt))
}, },
/** Combina múltiplos predicates com AND */ /** Combine multiple predicates with AND */
and: and:
(...predicates: Array<(error: Error, attempt: number) => boolean>) => (...predicates: Array<(error: Error, attempt: number) => boolean>) =>
(error: Error, attempt: number): boolean => { (error: Error, attempt: number): boolean => {
@@ -591,10 +592,10 @@ export const retryPredicates = {
} }
/** /**
* Configurações pré-definidas de retry * Pre-defined retry configurations
*/ */
export const retryConfigs = { export const retryConfigs = {
/** Retry agressivo (muitas tentativas, delays curtos) */ /** Aggressive retry (many attempts, short delays) */
aggressive: { aggressive: {
maxAttempts: 10, maxAttempts: 10,
baseDelay: 100, baseDelay: 100,
@@ -603,7 +604,7 @@ export const retryConfigs = {
jitter: 0.2, jitter: 0.2,
}, },
/** Retry conservador (poucas tentativas, delays longos) */ /** Conservative retry (few attempts, long delays) */
conservative: { conservative: {
maxAttempts: 3, maxAttempts: 3,
baseDelay: 2000, baseDelay: 2000,
@@ -612,7 +613,7 @@ export const retryConfigs = {
jitter: 0.1, jitter: 0.1,
}, },
/** Retry rápido (para operações curtas) */ /** Fast retry (for short operations) */
fast: { fast: {
maxAttempts: 5, maxAttempts: 5,
baseDelay: 50, baseDelay: 50,
@@ -621,7 +622,7 @@ export const retryConfigs = {
jitter: 0.05, jitter: 0.05,
}, },
/** Retry para operações de rede */ /** Retry for network operations */
network: { network: {
maxAttempts: 5, maxAttempts: 5,
baseDelay: 1000, baseDelay: 1000,
+70 -57
View File
@@ -1,25 +1,26 @@
/** /**
* @fileoverview Sistema de logging estruturado para InfiniteAPI * Structured Logging System for InfiniteAPI
* @module Utils/structured-logger
* *
* Fornece: * Provides:
* - Níveis de log configuráveis (trace, debug, info, warn, error, fatal) * - Configurable log levels (trace, debug, info, warn, error, fatal)
* - Formatação JSON para análise * - JSON formatting for log analysis
* - Contexto hierárquico com child loggers * - Hierarchical context with child loggers
* - Integração com sistemas externos (hooks) * - External system integration via hooks
* - Suporte a métricas de logging * - Logging metrics support
* - Sanitização de dados sensíveis * - Sensitive data sanitization
*
* @module Utils/structured-logger
*/ */
import type { ILogger } from './logger.js' import type { ILogger } from './logger.js'
/** /**
* Níveis de log disponíveis (ordenados por severidade) * Available log levels (ordered by severity)
*/ */
export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' | 'silent' export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' | 'silent'
/** /**
* Valores numéricos para cada nível de log * Numeric values for each log level
*/ */
export const LOG_LEVEL_VALUES: Record<LogLevel, number> = { export const LOG_LEVEL_VALUES: Record<LogLevel, number> = {
trace: 10, trace: 10,
@@ -32,55 +33,55 @@ export const LOG_LEVEL_VALUES: Record<LogLevel, number> = {
} }
/** /**
* Configuração do logger estruturado * Structured logger configuration
*/ */
export interface StructuredLoggerConfig { export interface StructuredLoggerConfig {
/** Nível mínimo de log a ser registrado */ /** Minimum log level to record */
level: LogLevel level: LogLevel
/** Nome do serviço/componente */ /** Service/component name */
name?: string name?: string
/** Contexto adicional a ser incluído em todos os logs */ /** Additional context to include in all logs */
context?: Record<string, unknown> context?: Record<string, unknown>
/** Se deve formatar como JSON (true) ou texto legível (false) */ /** Format as JSON (true) or human-readable text (false) */
jsonFormat?: boolean jsonFormat?: boolean
/** Campos a serem sanitizados (senhas, tokens, etc.) */ /** Fields to sanitize (passwords, tokens, etc.) */
redactFields?: string[] redactFields?: string[]
/** Hook para enviar logs para sistemas externos */ /** Hook for sending logs to external systems */
externalHook?: (entry: LogEntry) => void | Promise<void> externalHook?: (entry: LogEntry) => void | Promise<void>
/** Se deve incluir stack trace em erros */ /** Include stack trace in errors */
includeStackTrace?: boolean includeStackTrace?: boolean
/** Timezone para timestamps (default: UTC) */ /** Timezone for timestamps (default: UTC) */
timezone?: string timezone?: string
} }
/** /**
* Entrada de log estruturada * Structured log entry
*/ */
export interface LogEntry { export interface LogEntry {
/** Timestamp ISO 8601 */ /** ISO 8601 timestamp */
timestamp: string timestamp: string
/** Nível do log */ /** Log level */
level: LogLevel level: LogLevel
/** Valor numérico do nível */ /** Numeric level value */
levelValue: number levelValue: number
/** Mensagem principal */ /** Main message */
message: string message: string
/** Nome do logger/componente */ /** Logger/component name */
name?: string name?: string
/** Contexto adicional */ /** Additional context */
context?: Record<string, unknown> context?: Record<string, unknown>
/** Dados do objeto logado */ /** Logged object data */
data?: Record<string, unknown> data?: Record<string, unknown>
/** Stack trace (para erros) */ /** Stack trace (for errors) */
stack?: string stack?: string
/** ID de correlação para rastreamento */ /** Correlation ID for tracing */
correlationId?: string correlationId?: string
/** Duração de operação em ms (se aplicável) */ /** Operation duration in ms (if applicable) */
durationMs?: number durationMs?: number
} }
/** /**
* Métricas de logging * Logger metrics
*/ */
export interface LoggerMetrics { export interface LoggerMetrics {
totalLogs: number totalLogs: number
@@ -90,7 +91,7 @@ export interface LoggerMetrics {
} }
/** /**
* Campos padrão a serem sanitizados * Default fields to sanitize
*/ */
const DEFAULT_REDACT_FIELDS = [ const DEFAULT_REDACT_FIELDS = [
'password', 'password',
@@ -109,7 +110,19 @@ const DEFAULT_REDACT_FIELDS = [
] ]
/** /**
* Classe principal do Logger Estruturado * Structured Logger main class
*
* @example
* ```typescript
* const logger = createStructuredLogger({
* level: 'info',
* name: 'my-service',
* jsonFormat: true
* })
*
* logger.info({ userId: '123' }, 'User logged in')
* logger.error(new Error('Connection failed'))
* ```
*/ */
export class StructuredLogger implements ILogger { export class StructuredLogger implements ILogger {
private config: Required<StructuredLoggerConfig> private config: Required<StructuredLoggerConfig>
@@ -144,14 +157,14 @@ export class StructuredLogger implements ILogger {
} }
/** /**
* Getter para o nível atual do logger (compatibilidade com ILogger) * Get current log level (ILogger compatibility)
*/ */
get level(): string { get level(): string {
return this.config.level return this.config.level
} }
/** /**
* Setter para o nível do logger * Set log level
*/ */
set level(newLevel: string) { set level(newLevel: string) {
if (newLevel in LOG_LEVEL_VALUES) { if (newLevel in LOG_LEVEL_VALUES) {
@@ -160,7 +173,7 @@ export class StructuredLogger implements ILogger {
} }
/** /**
* Cria um logger filho com contexto adicional * Create a child logger with additional context
*/ */
child(obj: Record<string, unknown>): StructuredLogger { child(obj: Record<string, unknown>): StructuredLogger {
const childLogger = new StructuredLogger({ const childLogger = new StructuredLogger({
@@ -172,14 +185,14 @@ export class StructuredLogger implements ILogger {
} }
/** /**
* Verifica se o nível de log está habilitado * Check if log level is enabled
*/ */
isLevelEnabled(level: LogLevel): boolean { isLevelEnabled(level: LogLevel): boolean {
return LOG_LEVEL_VALUES[level] >= LOG_LEVEL_VALUES[this.config.level] return LOG_LEVEL_VALUES[level] >= LOG_LEVEL_VALUES[this.config.level]
} }
/** /**
* Método principal de logging * Main logging method
*/ */
private log(level: LogLevel, obj: unknown, msg?: string): void { private log(level: LogLevel, obj: unknown, msg?: string): void {
if (!this.isLevelEnabled(level)) { if (!this.isLevelEnabled(level)) {
@@ -188,13 +201,13 @@ export class StructuredLogger implements ILogger {
const entry = this.createLogEntry(level, obj, msg) const entry = this.createLogEntry(level, obj, msg)
// Atualizar métricas // Update metrics
this.updateMetrics(level) this.updateMetrics(level)
// Output // Output
this.output(entry) this.output(entry)
// Hook externo (async, não bloqueia) // External hook (async, non-blocking)
if (this.config.externalHook) { if (this.config.externalHook) {
Promise.resolve(this.config.externalHook(entry)).catch(() => { Promise.resolve(this.config.externalHook(entry)).catch(() => {
// Silently ignore hook errors // Silently ignore hook errors
@@ -203,7 +216,7 @@ export class StructuredLogger implements ILogger {
} }
/** /**
* Cria uma entrada de log estruturada * Create a structured log entry
*/ */
private createLogEntry(level: LogLevel, obj: unknown, msg?: string): LogEntry { private createLogEntry(level: LogLevel, obj: unknown, msg?: string): LogEntry {
const timestamp = new Date().toISOString() const timestamp = new Date().toISOString()
@@ -211,7 +224,7 @@ export class StructuredLogger implements ILogger {
let data: Record<string, unknown> | undefined let data: Record<string, unknown> | undefined
let stack: string | undefined let stack: string | undefined
// Processar objeto // Process object
if (obj instanceof Error) { if (obj instanceof Error) {
message = message || obj.message message = message || obj.message
if (this.config.includeStackTrace && obj.stack) { if (this.config.includeStackTrace && obj.stack) {
@@ -231,7 +244,7 @@ export class StructuredLogger implements ILogger {
message = message || obj message = message || obj
} }
// Extrair correlationId e durationMs se presentes // Extract correlationId and durationMs if present
const correlationId = data?.correlationId as string | undefined const correlationId = data?.correlationId as string | undefined
const durationMs = data?.durationMs as number | undefined const durationMs = data?.durationMs as number | undefined
@@ -250,7 +263,7 @@ export class StructuredLogger implements ILogger {
} }
/** /**
* Sanitiza dados sensíveis * Sanitize sensitive data
*/ */
private sanitize(obj: Record<string, unknown>): Record<string, unknown> { private sanitize(obj: Record<string, unknown>): Record<string, unknown> {
const sanitized: Record<string, unknown> = {} const sanitized: Record<string, unknown> = {}
@@ -271,7 +284,7 @@ export class StructuredLogger implements ILogger {
} }
/** /**
* Atualiza métricas internas * Update internal metrics
*/ */
private updateMetrics(level: LogLevel): void { private updateMetrics(level: LogLevel): void {
this.metrics.totalLogs++ this.metrics.totalLogs++
@@ -284,7 +297,7 @@ export class StructuredLogger implements ILogger {
} }
/** /**
* Output do log * Output log entry
*/ */
private output(entry: LogEntry): void { private output(entry: LogEntry): void {
const output = this.config.jsonFormat ? JSON.stringify(entry) : this.formatText(entry) const output = this.config.jsonFormat ? JSON.stringify(entry) : this.formatText(entry)
@@ -308,7 +321,7 @@ export class StructuredLogger implements ILogger {
} }
/** /**
* Formata log como texto legível * Format log as human-readable text
*/ */
private formatText(entry: LogEntry): string { private formatText(entry: LogEntry): string {
const parts = [ const parts = [
@@ -333,7 +346,7 @@ export class StructuredLogger implements ILogger {
return text return text
} }
// Métodos de conveniência para cada nível de log // Convenience methods for each log level
trace(obj: unknown, msg?: string): void { trace(obj: unknown, msg?: string): void {
this.log('trace', obj, msg) this.log('trace', obj, msg)
@@ -360,21 +373,21 @@ export class StructuredLogger implements ILogger {
} }
/** /**
* Log com contexto temporário * Log with temporary context
*/ */
withContext(context: Record<string, unknown>): StructuredLogger { withContext(context: Record<string, unknown>): StructuredLogger {
return this.child(context) return this.child(context)
} }
/** /**
* Log com correlation ID * Log with correlation ID
*/ */
withCorrelationId(correlationId: string): StructuredLogger { withCorrelationId(correlationId: string): StructuredLogger {
return this.child({ correlationId }) return this.child({ correlationId })
} }
/** /**
* Log de operação com duração * Log operation with duration tracking
*/ */
logOperation<T>( logOperation<T>(
operationName: string, operationName: string,
@@ -412,14 +425,14 @@ export class StructuredLogger implements ILogger {
} }
/** /**
* Retorna métricas do logger * Get logger metrics
*/ */
getMetrics(): LoggerMetrics { getMetrics(): LoggerMetrics {
return { ...this.metrics } return { ...this.metrics }
} }
/** /**
* Reseta métricas * Reset metrics
*/ */
resetMetrics(): void { resetMetrics(): void {
this.metrics = { this.metrics = {
@@ -439,7 +452,7 @@ export class StructuredLogger implements ILogger {
} }
/** /**
* Factory para criar logger estruturado * Factory to create structured logger
*/ */
export function createStructuredLogger(config: Partial<StructuredLoggerConfig> = {}): StructuredLogger { export function createStructuredLogger(config: Partial<StructuredLoggerConfig> = {}): StructuredLogger {
return new StructuredLogger({ return new StructuredLogger({
@@ -449,7 +462,7 @@ export function createStructuredLogger(config: Partial<StructuredLoggerConfig> =
} }
/** /**
* Logger padrão singleton * Default singleton logger
*/ */
let defaultLogger: StructuredLogger | null = null let defaultLogger: StructuredLogger | null = null
@@ -469,7 +482,7 @@ export function setDefaultLogger(logger: StructuredLogger): void {
} }
/** /**
* Utilitário para medir tempo de execução * Utility to measure execution time
*/ */
export function createTimer(): { elapsed: () => number; elapsedMs: () => string } { export function createTimer(): { elapsed: () => number; elapsedMs: () => string } {
const start = process.hrtime.bigint() const start = process.hrtime.bigint()
+86 -85
View File
@@ -1,153 +1,154 @@
/** /**
* @fileoverview Contexto de rastreamento para requests * Request Tracing Context
* @module Utils/trace-context
* *
* Fornece: * Provides:
* - Geração de trace IDs únicos * - Unique trace ID generation
* - Context propagation entre operações * - Context propagation between operations
* - Correlation IDs para rastrear requests * - Correlation IDs for request tracking
* - Performance timing * - Performance timing
* - Span tracking para operações aninhadas * - Span tracking for nested operations
* - Baggage para dados contextuais * - Baggage for contextual data
*
* @module Utils/trace-context
*/ */
import { randomBytes } from 'crypto' import { randomBytes } from 'crypto'
import { AsyncLocalStorage } from 'async_hooks' import { AsyncLocalStorage } from 'async_hooks'
/** /**
* Identificadores de trace * Trace identifiers
*/ */
export interface TraceIds { export interface TraceIds {
/** ID único do trace (16 bytes hex) */ /** Unique trace ID (16 bytes hex) */
traceId: string traceId: string
/** ID do span atual (8 bytes hex) */ /** Current span ID (8 bytes hex) */
spanId: string spanId: string
/** ID do span pai (opcional) */ /** Parent span ID (optional) */
parentSpanId?: string parentSpanId?: string
/** ID de correlação para logging */ /** Correlation ID for logging */
correlationId: string correlationId: string
} }
/** /**
* Dados de baggage (contexto propagado) * Baggage data (propagated context)
*/ */
export type Baggage = Record<string, string | number | boolean> export type Baggage = Record<string, string | number | boolean>
/** /**
* Status de um span * Span status
*/ */
export type SpanStatus = 'unset' | 'ok' | 'error' export type SpanStatus = 'unset' | 'ok' | 'error'
/** /**
* Span representa uma unidade de trabalho * Span represents a unit of work
*/ */
export interface Span { export interface Span {
/** Nome da operação */ /** Operation name */
name: string name: string
/** IDs de rastreamento */ /** Trace identifiers */
traceIds: TraceIds traceIds: TraceIds
/** Timestamp de início (ms) */ /** Start timestamp (ms) */
startTime: number startTime: number
/** Timestamp de fim (ms) */ /** End timestamp (ms) */
endTime?: number endTime?: number
/** Duração em ms */ /** Duration in ms */
duration?: number duration?: number
/** Status do span */ /** Span status */
status: SpanStatus status: SpanStatus
/** Atributos do span */ /** Span attributes */
attributes: Record<string, unknown> attributes: Record<string, unknown>
/** Eventos ocorridos durante o span */ /** Events occurred during the span */
events: SpanEvent[] events: SpanEvent[]
/** Se o span está finalizado */ /** Whether the span has ended */
ended: boolean ended: boolean
} }
/** /**
* Evento dentro de um span * Event within a span
*/ */
export interface SpanEvent { export interface SpanEvent {
/** Nome do evento */ /** Event name */
name: string name: string
/** Timestamp do evento */ /** Event timestamp */
timestamp: number timestamp: number
/** Atributos do evento */ /** Event attributes */
attributes?: Record<string, unknown> attributes?: Record<string, unknown>
} }
/** /**
* Contexto completo de trace * Complete trace context
*/ */
export interface TraceContext { export interface TraceContext {
/** IDs de rastreamento */ /** Trace identifiers */
traceIds: TraceIds traceIds: TraceIds
/** Baggage (dados propagados) */ /** Baggage (propagated data) */
baggage: Baggage baggage: Baggage
/** Span atual */ /** Current span */
currentSpan?: Span currentSpan?: Span
/** Stack de spans (para spans aninhados) */ /** Span stack (for nested spans) */
spanStack: Span[] spanStack: Span[]
/** Timestamp de criação do contexto */ /** Context creation timestamp */
createdAt: number createdAt: number
/** Metadados adicionais */ /** Additional metadata */
metadata: Record<string, unknown> metadata: Record<string, unknown>
} }
/** /**
* Opções para criar um novo contexto * Options for creating a new context
*/ */
export interface CreateContextOptions { export interface CreateContextOptions {
/** Trace ID existente (para propagação) */ /** Existing trace ID (for propagation) */
traceId?: string traceId?: string
/** Parent span ID */ /** Parent span ID */
parentSpanId?: string parentSpanId?: string
/** Correlation ID existente */ /** Existing correlation ID */
correlationId?: string correlationId?: string
/** Baggage inicial */ /** Initial baggage */
baggage?: Baggage baggage?: Baggage
/** Metadados iniciais */ /** Initial metadata */
metadata?: Record<string, unknown> metadata?: Record<string, unknown>
} }
/** /**
* Opções para criar um span * Options for creating a span
*/ */
export interface CreateSpanOptions { export interface CreateSpanOptions {
/** Nome do span */ /** Span name */
name: string name: string
/** Atributos iniciais */ /** Initial attributes */
attributes?: Record<string, unknown> attributes?: Record<string, unknown>
/** Se deve ser filho do span atual */ /** Whether to be a child of the current span */
asChild?: boolean asChild?: boolean
} }
/** /**
* Storage assíncrono para contexto de trace * Async storage for trace context
*/ */
const traceStorage = new AsyncLocalStorage<TraceContext>() const traceStorage = new AsyncLocalStorage<TraceContext>()
/** /**
* Gera um ID hexadecimal aleatório * Generate a random hexadecimal ID
*/ */
function generateId(bytes: number): string { function generateId(bytes: number): string {
return randomBytes(bytes).toString('hex') return randomBytes(bytes).toString('hex')
} }
/** /**
* Gera um trace ID (16 bytes = 32 chars hex) * Generate a trace ID (16 bytes = 32 chars hex)
*/ */
export function generateTraceId(): string { export function generateTraceId(): string {
return generateId(16) return generateId(16)
} }
/** /**
* Gera um span ID (8 bytes = 16 chars hex) * Generate a span ID (8 bytes = 16 chars hex)
*/ */
export function generateSpanId(): string { export function generateSpanId(): string {
return generateId(8) return generateId(8)
} }
/** /**
* Gera um correlation ID mais legível * Generate a more readable correlation ID
*/ */
export function generateCorrelationId(): string { export function generateCorrelationId(): string {
const timestamp = Date.now().toString(36) const timestamp = Date.now().toString(36)
@@ -156,7 +157,7 @@ export function generateCorrelationId(): string {
} }
/** /**
* Cria um novo contexto de trace * Create a new trace context
*/ */
export function createTraceContext(options: CreateContextOptions = {}): TraceContext { export function createTraceContext(options: CreateContextOptions = {}): TraceContext {
const traceId = options.traceId || generateTraceId() const traceId = options.traceId || generateTraceId()
@@ -178,14 +179,14 @@ export function createTraceContext(options: CreateContextOptions = {}): TraceCon
} }
/** /**
* Obtém o contexto de trace atual * Get the current trace context
*/ */
export function getCurrentContext(): TraceContext | undefined { export function getCurrentContext(): TraceContext | undefined {
return traceStorage.getStore() return traceStorage.getStore()
} }
/** /**
* Obtém o contexto de trace atual ou cria um novo * Get the current trace context or create a new one
*/ */
export function getOrCreateContext(): TraceContext { export function getOrCreateContext(): TraceContext {
const existing = getCurrentContext() const existing = getCurrentContext()
@@ -196,14 +197,14 @@ export function getOrCreateContext(): TraceContext {
} }
/** /**
* Executa função com contexto de trace * Execute function with trace context
*/ */
export function runWithContext<T>(context: TraceContext, fn: () => T): T { export function runWithContext<T>(context: TraceContext, fn: () => T): T {
return traceStorage.run(context, fn) return traceStorage.run(context, fn)
} }
/** /**
* Executa função com novo contexto de trace * Execute function with new trace context
*/ */
export function runWithNewContext<T>(options: CreateContextOptions, fn: () => T): T { export function runWithNewContext<T>(options: CreateContextOptions, fn: () => T): T {
const context = createTraceContext(options) const context = createTraceContext(options)
@@ -211,7 +212,7 @@ export function runWithNewContext<T>(options: CreateContextOptions, fn: () => T)
} }
/** /**
* Executa função assíncrona com contexto de trace * Execute async function with trace context
*/ */
export async function runWithContextAsync<T>( export async function runWithContextAsync<T>(
context: TraceContext, context: TraceContext,
@@ -221,7 +222,7 @@ export async function runWithContextAsync<T>(
} }
/** /**
* Cria um novo span * Create a new span
*/ */
export function createSpan(options: CreateSpanOptions): Span { export function createSpan(options: CreateSpanOptions): Span {
const context = getCurrentContext() const context = getCurrentContext()
@@ -246,13 +247,13 @@ export function createSpan(options: CreateSpanOptions): Span {
} }
/** /**
* Inicia um span no contexto atual * Start a span in the current context
*/ */
export function startSpan(options: CreateSpanOptions): Span { export function startSpan(options: CreateSpanOptions): Span {
const context = getOrCreateContext() const context = getOrCreateContext()
const span = createSpan({ ...options, asChild: true }) const span = createSpan({ ...options, asChild: true })
// Push span atual para stack e define novo como atual // Push current span to stack and set new one as current
if (context.currentSpan) { if (context.currentSpan) {
context.spanStack.push(context.currentSpan) context.spanStack.push(context.currentSpan)
} }
@@ -262,7 +263,7 @@ export function startSpan(options: CreateSpanOptions): Span {
} }
/** /**
* Finaliza um span * End a span
*/ */
export function endSpan(span: Span, status?: SpanStatus): void { export function endSpan(span: Span, status?: SpanStatus): void {
if (span.ended) { if (span.ended) {
@@ -274,7 +275,7 @@ export function endSpan(span: Span, status?: SpanStatus): void {
span.status = status || 'ok' span.status = status || 'ok'
span.ended = true span.ended = true
// Pop span do stack no contexto // Pop span from stack in context
const context = getCurrentContext() const context = getCurrentContext()
if (context && context.currentSpan === span) { if (context && context.currentSpan === span) {
context.currentSpan = context.spanStack.pop() context.currentSpan = context.spanStack.pop()
@@ -282,7 +283,7 @@ export function endSpan(span: Span, status?: SpanStatus): void {
} }
/** /**
* Adiciona evento a um span * Add event to a span
*/ */
export function addSpanEvent(span: Span, name: string, attributes?: Record<string, unknown>): void { export function addSpanEvent(span: Span, name: string, attributes?: Record<string, unknown>): void {
if (span.ended) { if (span.ended) {
@@ -297,7 +298,7 @@ export function addSpanEvent(span: Span, name: string, attributes?: Record<strin
} }
/** /**
* Define atributos em um span * Set attributes on a span
*/ */
export function setSpanAttributes(span: Span, attributes: Record<string, unknown>): void { export function setSpanAttributes(span: Span, attributes: Record<string, unknown>): void {
if (span.ended) { if (span.ended) {
@@ -308,7 +309,7 @@ export function setSpanAttributes(span: Span, attributes: Record<string, unknown
} }
/** /**
* Marca span como erro * Mark span as error
*/ */
export function setSpanError(span: Span, error: Error): void { export function setSpanError(span: Span, error: Error): void {
if (span.ended) { if (span.ended) {
@@ -330,7 +331,7 @@ export function setSpanError(span: Span, error: Error): void {
} }
/** /**
* Decorator para rastrear função automaticamente * Decorator for automatic function tracing
*/ */
export function traced(name?: string) { export function traced(name?: string) {
return function <T extends (...args: unknown[]) => unknown>( return function <T extends (...args: unknown[]) => unknown>(
@@ -378,7 +379,7 @@ export function traced(name?: string) {
} }
/** /**
* Wrapper para rastrear função * Wrapper for tracing a function
*/ */
export function traceFunction<T extends (...args: unknown[]) => unknown>( export function traceFunction<T extends (...args: unknown[]) => unknown>(
name: string, name: string,
@@ -414,7 +415,7 @@ export function traceFunction<T extends (...args: unknown[]) => unknown>(
} }
/** /**
* Executa operação com span automático * Execute operation with automatic span
*/ */
export async function withSpan<T>( export async function withSpan<T>(
name: string, name: string,
@@ -435,7 +436,7 @@ export async function withSpan<T>(
} }
/** /**
* Executa operação síncrona com span automático * Execute sync operation with automatic span
*/ */
export function withSpanSync<T>( export function withSpanSync<T>(
name: string, name: string,
@@ -455,10 +456,10 @@ export function withSpanSync<T>(
} }
} }
// === Gerenciamento de Baggage === // === Baggage Management ===
/** /**
* Define item no baggage * Set item in baggage
*/ */
export function setBaggage(key: string, value: string | number | boolean): void { export function setBaggage(key: string, value: string | number | boolean): void {
const context = getCurrentContext() const context = getCurrentContext()
@@ -468,7 +469,7 @@ export function setBaggage(key: string, value: string | number | boolean): void
} }
/** /**
* Obtém item do baggage * Get item from baggage
*/ */
export function getBaggage(key: string): string | number | boolean | undefined { export function getBaggage(key: string): string | number | boolean | undefined {
const context = getCurrentContext() const context = getCurrentContext()
@@ -476,7 +477,7 @@ export function getBaggage(key: string): string | number | boolean | undefined {
} }
/** /**
* Obtém todo o baggage * Get all baggage
*/ */
export function getAllBaggage(): Baggage { export function getAllBaggage(): Baggage {
const context = getCurrentContext() const context = getCurrentContext()
@@ -484,7 +485,7 @@ export function getAllBaggage(): Baggage {
} }
/** /**
* Remove item do baggage * Remove item from baggage
*/ */
export function removeBaggage(key: string): void { export function removeBaggage(key: string): void {
const context = getCurrentContext() const context = getCurrentContext()
@@ -493,10 +494,10 @@ export function removeBaggage(key: string): void {
} }
} }
// === Utilitários de Headers === // === Header Utilities ===
/** /**
* Headers padrão para propagação de trace * Standard headers for trace propagation
*/ */
export const TRACE_HEADERS = { export const TRACE_HEADERS = {
TRACE_ID: 'x-trace-id', TRACE_ID: 'x-trace-id',
@@ -507,7 +508,7 @@ export const TRACE_HEADERS = {
} as const } as const
/** /**
* Injeta contexto em headers HTTP * Inject context into HTTP headers
*/ */
export function injectTraceHeaders(headers: Record<string, string>): Record<string, string> { export function injectTraceHeaders(headers: Record<string, string>): Record<string, string> {
const context = getCurrentContext() const context = getCurrentContext()
@@ -524,7 +525,7 @@ export function injectTraceHeaders(headers: Record<string, string>): Record<stri
result[TRACE_HEADERS.PARENT_SPAN_ID] = context.traceIds.parentSpanId result[TRACE_HEADERS.PARENT_SPAN_ID] = context.traceIds.parentSpanId
} }
// Baggage como lista de key=value // Baggage as key=value list
if (Object.keys(context.baggage).length > 0) { if (Object.keys(context.baggage).length > 0) {
result[TRACE_HEADERS.BAGGAGE] = Object.entries(context.baggage) result[TRACE_HEADERS.BAGGAGE] = Object.entries(context.baggage)
.map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`) .map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`)
@@ -535,7 +536,7 @@ export function injectTraceHeaders(headers: Record<string, string>): Record<stri
} }
/** /**
* Extrai contexto de headers HTTP * Extract context from HTTP headers
*/ */
export function extractTraceHeaders(headers: Record<string, string | undefined>): CreateContextOptions { export function extractTraceHeaders(headers: Record<string, string | undefined>): CreateContextOptions {
const options: CreateContextOptions = {} const options: CreateContextOptions = {}
@@ -568,7 +569,7 @@ export function extractTraceHeaders(headers: Record<string, string | undefined>)
} }
/** /**
* Exporta trace context para serialização * Export trace context for serialization
*/ */
export function exportContext(context: TraceContext): string { export function exportContext(context: TraceContext): string {
return JSON.stringify({ return JSON.stringify({
@@ -579,7 +580,7 @@ export function exportContext(context: TraceContext): string {
} }
/** /**
* Importa trace context de string serializada * Import trace context from serialized string
*/ */
export function importContext(serialized: string): CreateContextOptions { export function importContext(serialized: string): CreateContextOptions {
try { try {
@@ -599,19 +600,19 @@ export function importContext(serialized: string): CreateContextOptions {
// === Timer Utilities === // === Timer Utilities ===
/** /**
* Timer de alta precisão * High precision timer
*/ */
export interface PrecisionTimer { export interface PrecisionTimer {
/** Retorna tempo decorrido em milliseconds */ /** Return elapsed time in milliseconds */
elapsed(): number elapsed(): number
/** Retorna tempo decorrido formatado */ /** Return formatted elapsed time */
elapsedFormatted(): string elapsedFormatted(): string
/** Para o timer e retorna duração */ /** Stop the timer and return duration */
stop(): number stop(): number
} }
/** /**
* Cria um timer de alta precisão * Create a high precision timer
*/ */
export function createPrecisionTimer(): PrecisionTimer { export function createPrecisionTimer(): PrecisionTimer {
const start = process.hrtime.bigint() const start = process.hrtime.bigint()