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
* @module Utils/baileys-event-stream
* Baileys Event Stream Management
*
* Fornece:
* - Event buffering com backpressure
* - Event transformation e filtering
* - Priority queues para eventos
* Provides:
* - Event buffering with backpressure
* - Event transformation and filtering
* - Priority queues for events
* - Batch processing
* - Dead letter queue para eventos falhos
* - Replay de eventos
* - Integração com logging e métricas
* - Dead letter queue for failed events
* - Event replay
* - Logging and metrics integration
*
* @module Utils/baileys-event-stream
*/
import { EventEmitter } from 'events'
@@ -17,7 +18,7 @@ import { metrics } from './prometheus-metrics.js'
import type { BaileysLogCategory } from './baileys-logger.js'
/**
* Tipos de eventos do Baileys
* Baileys event types
*/
export type BaileysEventType =
| 'connection.update'
@@ -41,15 +42,15 @@ export type BaileysEventType =
| 'call'
| 'blocklist.set'
| 'blocklist.update'
| string // Para eventos customizados
| string // For custom events
/**
* Prioridade de eventos
* Event priority
*/
export type EventPriority = 'critical' | 'high' | 'normal' | 'low'
/**
* Valores numéricos de prioridade
* Numeric priority values
*/
const PRIORITY_VALUES: Record<EventPriority, number> = {
critical: 0,
@@ -59,7 +60,7 @@ const PRIORITY_VALUES: Record<EventPriority, number> = {
}
/**
* Evento do stream
* Stream event
*/
export interface StreamEvent<T = unknown> {
id: string
@@ -74,48 +75,48 @@ export interface StreamEvent<T = unknown> {
}
/**
* Opções do Event Stream
* Event Stream options
*/
export interface EventStreamOptions {
/** Tamanho máximo do buffer (default: 10000) */
/** Maximum buffer size (default: 10000) */
maxBufferSize?: number
/** Se deve aplicar backpressure quando buffer cheio */
/** Whether to apply backpressure when buffer is full */
enableBackpressure?: boolean
/** Limite de highWaterMark para backpressure */
/** High water mark limit for backpressure */
highWaterMark?: number
/** Limite de lowWaterMark para retomar */
/** Low water mark limit to resume */
lowWaterMark?: number
/** Tamanho do batch para processamento */
/** Batch size for processing */
batchSize?: number
/** Intervalo de flush em ms (0 = desabilitado) */
/** Flush interval in ms (0 = disabled) */
flushInterval?: number
/** Máximo de retries para eventos falhos */
/** Maximum retries for failed events */
maxRetries?: number
/** Tamanho da dead letter queue */
/** Dead letter queue size */
deadLetterQueueSize?: number
/** Se deve coletar métricas */
/** Whether to collect metrics */
collectMetrics?: boolean
/** Nome do stream para métricas */
/** Stream name for metrics */
streamName?: string
}
/**
* Handler de evento
* Event handler
*/
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
/**
* Transformador de evento
* Event transformer
*/
export type EventTransformer<T = unknown, R = unknown> = (event: StreamEvent<T>) => StreamEvent<R>
/**
* Resultado de processamento de batch
* Batch processing result
*/
export interface BatchResult {
processed: number
@@ -124,7 +125,7 @@ export interface BatchResult {
}
/**
* Estatísticas do stream
* Stream statistics
*/
export interface EventStreamStats {
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> = {
'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>> = {
'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 {
return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`
}
/**
* Classe principal do Event Stream
* Main Event Stream class
*/
export class BaileysEventStream extends EventEmitter {
private buffer: StreamEvent[] = []
@@ -217,7 +218,7 @@ export class BaileysEventStream extends EventEmitter {
this.stats = this.createInitialStats()
// Iniciar flush periódico se configurado
// Start periodic flush if configured
if (this.options.flushInterval > 0) {
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 {
// Verificar backpressure
// Check backpressure
if (this.options.enableBackpressure && this.buffer.length >= this.options.highWaterMark) {
this.stats.isBackpressured = true
this.emit('backpressure', { bufferSize: this.buffer.length })
@@ -274,23 +275,23 @@ export class BaileysEventStream extends EventEmitter {
retryCount: 0,
}
// Aplicar transformadores
// Apply transformers
let transformedEvent: StreamEvent = event
for (const transformer of this.transformers) {
transformedEvent = transformer(transformedEvent)
}
// Aplicar filtros
// Apply filters
for (const filter of this.filters) {
if (!filter(transformedEvent)) {
return false
}
}
// Adicionar ao buffer na posição correta (por prioridade)
// Add to buffer at correct position (by priority)
this.insertByPriority(transformedEvent)
// Atualizar estatísticas
// Update statistics
this.stats.totalReceived++
this.stats.bufferSize = this.buffer.length
this.stats.lastEventTimestamp = Date.now()
@@ -303,7 +304,7 @@ export class BaileysEventStream extends EventEmitter {
this.emit('event', transformedEvent)
// Processar se não estiver pausado
// Process if not paused
if (!this.paused && !this.isProcessing) {
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 {
const eventPriorityValue = PRIORITY_VALUES[event.priority]
// Encontrar posição correta
// Find correct position
let insertIndex = this.buffer.length
for (let i = 0; i < this.buffer.length; i++) {
if (PRIORITY_VALUES[this.buffer[i].priority] > eventPriorityValue) {
@@ -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 {
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 {
const wrappedHandler: EventHandler<T> = (e) => {
@@ -363,7 +364,7 @@ export class BaileysEventStream extends EventEmitter {
}
/**
* Adiciona filtro
* Add filter
*/
addFilter(filter: EventFilter): this {
this.filters.push(filter)
@@ -371,7 +372,7 @@ export class BaileysEventStream extends EventEmitter {
}
/**
* Remove filtro
* Remove filter
*/
removeFilter(filter: EventFilter): this {
const index = this.filters.indexOf(filter)
@@ -382,7 +383,7 @@ export class BaileysEventStream extends EventEmitter {
}
/**
* Adiciona transformador
* Add transformer
*/
addTransformer(transformer: EventTransformer): this {
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> {
if (this.isProcessing || this.paused || this.buffer.length === 0) {
@@ -400,17 +401,17 @@ export class BaileysEventStream extends EventEmitter {
this.isProcessing = true
try {
// Pegar batch de eventos
// Get batch of events
const batch = this.buffer.splice(0, this.options.batchSize)
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) {
this.stats.isBackpressured = false
this.emit('drain')
}
// Processar batch
// Process batch
const startTime = Date.now()
let processed = 0
let failed = 0
@@ -430,7 +431,7 @@ export class BaileysEventStream extends EventEmitter {
const duration = Date.now() - startTime
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) {
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> {
// Handlers específicos do tipo
// Type-specific handlers
const typeHandlers = this.handlers.get(event.type)
if (typeHandlers) {
for (const handler of typeHandlers) {
@@ -451,7 +452,7 @@ export class BaileysEventStream extends EventEmitter {
}
}
// Handlers globais
// Global handlers
const globalHandlers = this.handlers.get('*')
if (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> {
event.retryCount = (event.retryCount || 0) + 1
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.timestamp = Date.now()
this.buffer.push(event)
@@ -475,7 +476,7 @@ export class BaileysEventStream extends EventEmitter {
this.emit('retry', { event, error, attempt: event.retryCount })
} else {
// Enviar para dead letter queue
// Send to dead letter queue
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 {
const dlqEvent = {
@@ -500,7 +501,7 @@ export class BaileysEventStream extends EventEmitter {
this.deadLetterQueue.push(dlqEvent)
// Limitar tamanho da DLQ
// Limit DLQ size
while (this.deadLetterQueue.length > this.options.deadLetterQueueSize) {
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> {
const startTime = Date.now()
@@ -543,7 +544,7 @@ export class BaileysEventStream extends EventEmitter {
}
/**
* Pausa o processamento
* Pause processing
*/
pause(): void {
this.paused = true
@@ -551,7 +552,7 @@ export class BaileysEventStream extends EventEmitter {
}
/**
* Resume o processamento
* Resume processing
*/
resume(): void {
this.paused = false
@@ -560,14 +561,14 @@ export class BaileysEventStream extends EventEmitter {
}
/**
* Verifica se está pausado
* Check if paused
*/
isPaused(): boolean {
return this.paused
}
/**
* Limpa o buffer
* Clear the buffer
*/
clear(): void {
this.buffer = []
@@ -576,14 +577,14 @@ export class BaileysEventStream extends EventEmitter {
}
/**
* Retorna eventos da dead letter queue
* Return dead letter queue events
*/
getDeadLetterQueue(): StreamEvent[] {
return [...this.deadLetterQueue]
}
/**
* Limpa dead letter queue
* Clear dead letter queue
*/
clearDeadLetterQueue(): void {
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> {
const events = this.deadLetterQueue.splice(0)
@@ -625,14 +626,14 @@ export class BaileysEventStream extends EventEmitter {
}
/**
* Retorna estatísticas
* Return statistics
*/
getStats(): EventStreamStats {
return { ...this.stats }
}
/**
* Reseta estatísticas
* Reset statistics
*/
resetStats(): void {
this.stats = this.createInitialStats()
@@ -641,7 +642,7 @@ export class BaileysEventStream extends EventEmitter {
}
/**
* Destroy e limpa recursos
* Destroy and clean up resources
*/
destroy(): void {
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 {
return new BaileysEventStream(options)
}
/**
* Filtros pré-definidos
* Pre-defined filters
*/
export const eventFilters = {
/** Filtra por tipo de evento */
/** Filter by event type */
byType:
(...types: BaileysEventType[]): EventFilter =>
(event) =>
types.includes(event.type),
/** Filtra por categoria */
/** Filter by category */
byCategory:
(...categories: BaileysLogCategory[]): EventFilter =>
(event) =>
categories.includes(event.category),
/** Filtra por prioridade mínima */
/** Filter by minimum priority */
byMinPriority:
(minPriority: EventPriority): EventFilter =>
(event) =>
PRIORITY_VALUES[event.priority] <= PRIORITY_VALUES[minPriority],
/** Filtra eventos recentes (dentro de ms) */
/** Filter recent events (within ms) */
recentOnly:
(maxAgeMs: number): EventFilter =>
(event) =>
Date.now() - event.timestamp <= maxAgeMs,
/** Combina filtros com AND */
/** Combine filters with AND */
and:
(...filters: EventFilter[]): EventFilter =>
(event) =>
filters.every((f) => f(event)),
/** Combina filtros com OR */
/** Combine filters with OR */
or:
(...filters: EventFilter[]): EventFilter =>
(event) =>
@@ -705,10 +706,10 @@ export const eventFilters = {
}
/**
* Transformadores pré-definidos
* Pre-defined transformers
*/
export const eventTransformers = {
/** Adiciona timestamp de processamento */
/** Add processing timestamp */
addProcessingTimestamp: (): EventTransformer => (event) => ({
...event,
metadata: {
@@ -717,7 +718,7 @@ export const eventTransformers = {
},
}),
/** Adiciona ID de trace */
/** Add trace ID */
addTraceId:
(traceIdGenerator: () => string): EventTransformer =>
(event) => ({
@@ -728,7 +729,7 @@ export const eventTransformers = {
},
}),
/** Eleva prioridade baseado em condição */
/** Elevate priority based on condition */
elevatepriorityIf:
(condition: (event: StreamEvent) => boolean, newPriority: EventPriority): EventTransformer =>
(event) =>
+73 -61
View File
@@ -1,60 +1,61 @@
/**
* @fileoverview Logger customizado específico para Baileys
* @module Utils/baileys-logger
* Custom Logger for Baileys/WhatsApp
*
* Fornece:
* - Logger pré-configurado para contexto Baileys/WhatsApp
* - Categorização de eventos por tipo (connection, message, media, etc.)
* - Filtros específicos para reduzir ruído
* - Métricas de eventos WhatsApp
* - Formatação otimizada para debugging
* Provides:
* - Pre-configured logger for Baileys/WhatsApp context
* - Event categorization by type (connection, message, media, etc.)
* - Specific filters to reduce noise
* - WhatsApp event metrics
* - Optimized formatting for debugging
*
* @module Utils/baileys-logger
*/
import type { ILogger } from './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 =
| 'connection' // Eventos de conexão WebSocket
| 'auth' // Autenticação e QR code
| 'message' // Envio/recebimento de mensagens
| 'media' // Upload/download de mídia
| 'group' // Operações de grupo
| 'presence' // Status de presença
| 'call' // Chamadas de voz/vídeo
| 'sync' // Sincronização de dados
| 'encryption' // Operações de criptografia
| 'retry' // Retentativas de operações
| 'socket' // Eventos de socket baixo nível
| 'binary' // Codificação/decodificação binária
| 'unknown' // Categoria desconhecida
| 'connection' // WebSocket connection events
| 'auth' // Authentication and QR code
| 'message' // Message send/receive
| 'media' // Media upload/download
| 'group' // Group operations
| 'presence' // Presence status
| 'call' // Voice/video calls
| 'sync' // Data synchronization
| 'encryption' // Encryption operations
| 'retry' // Operation retries
| 'socket' // Low-level socket events
| 'binary' // Binary encoding/decoding
| 'unknown' // Unknown category
/**
* Configuração do Baileys Logger
* Baileys Logger configuration
*/
export interface BaileysLoggerConfig {
/** Nível de log padrão */
/** Default log level */
level: LogLevel
/** Categorias a serem ignoradas */
/** Categories to ignore */
ignoredCategories?: BaileysLogCategory[]
/** Categorias com nível de log elevado (debug sempre) */
/** Categories with elevated log level (always debug) */
verboseCategories?: BaileysLogCategory[]
/** Se deve logar payloads de mensagens (pode ser sensível) */
/** Whether to log message payloads (may be sensitive) */
logMessagePayloads?: boolean
/** Se deve logar dados binários em hex */
/** Whether to log binary data in hex */
logBinaryData?: boolean
/** Prefixo para identificar instância */
/** Instance identifier prefix */
instanceId?: string
/** Handler para eventos específicos */
/** Handler for specific events */
eventHandler?: (category: BaileysLogCategory, entry: LogEntry) => void
/** Limite de tamanho para payloads logados (bytes) */
/** Size limit for logged payloads (bytes) */
maxPayloadSize?: number
}
/**
* Métricas específicas do Baileys
* Baileys-specific metrics
*/
export interface BaileysLoggerMetrics {
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 }> = [
{ 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 {
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 {
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 {
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)) {
return false
}
// Categorias verbose sempre logam em debug ou superior
// Verbose categories always log at debug or higher
if (this.config.verboseCategories.includes(category)) {
return true
}
@@ -206,14 +218,14 @@ export class BaileysLogger implements ILogger {
}
/**
* Sanitiza payload de mensagem
* Sanitize message payload
*/
private sanitizePayload(obj: unknown): unknown {
if (!this.config.logMessagePayloads) {
if (typeof obj === 'object' && obj !== null) {
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']
for (const field of sensitiveFields) {
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) {
const str = JSON.stringify(obj)
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 {
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 {
const category = this.detectCategory(obj, msg)
@@ -305,13 +317,13 @@ export class BaileysLogger implements ILogger {
return
}
// Atualizar métricas
// Update metrics
this.updateMetrics(category, level, obj)
// Sanitizar payload
// Sanitize payload
const sanitizedObj = this.sanitizePayload(obj)
// Adicionar contexto do Baileys
// Add Baileys context
const enrichedObj = {
category,
instanceId: this.config.instanceId,
@@ -319,10 +331,10 @@ export class BaileysLogger implements ILogger {
...(typeof sanitizedObj === 'object' && sanitizedObj !== null ? sanitizedObj : { value: sanitizedObj }),
}
// Log estruturado
// Structured log
this.structuredLogger[level](enrichedObj, msg)
// Handler de evento
// Event handler
if (this.config.eventHandler) {
const entry: LogEntry = {
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 {
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(
direction: 'send' | 'receive',
@@ -388,7 +400,7 @@ export class BaileysLogger implements ILogger {
}
/**
* Log de mídia específico
* Log media-specific event
*/
logMedia(
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 {
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('@')
if (parts.length === 2 && parts[0].length > 4) {
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 {
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 {
return { ...this.metrics }
}
/**
* Retorna métricas do structured logger interno
* Get internal structured logger metrics
*/
getStructuredMetrics() {
return this.structuredLogger.getMetrics()
}
/**
* Reseta métricas
* Reset metrics
*/
resetMetrics(): void {
this.metrics = this.createInitialMetrics()
@@ -458,7 +470,7 @@ export class BaileysLogger implements ILogger {
}
/**
* Retorna instance ID
* Get instance ID
*/
getInstanceId(): string {
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 {
return new BaileysLogger(config)
}
/**
* Singleton para logger padrão do Baileys
* Default Baileys logger singleton
*/
let defaultBaileysLogger: BaileysLogger | null = null
+49 -48
View File
@@ -1,44 +1,45 @@
/**
* @fileoverview Sistema de cache inteligente
* @module Utils/cache-utils
* Smart Cache System
*
* Fornece:
* - Cache em memória com TTL configurável
* - Invalidação automática e manual
* - Métricas de hit/miss
* - Estratégia LRU (Least Recently Used)
* - Cache distribuído (preparado para Redis)
* - Namespace para isolamento
* - Serialização customizável
* Provides:
* - In-memory cache with configurable TTL
* - Automatic and manual invalidation
* - Hit/miss metrics
* - LRU (Least Recently Used) strategy
* - Distributed cache (prepared for Redis)
* - Namespace for isolation
* - Customizable serialization
*
* @module Utils/cache-utils
*/
import { LRUCache } from 'lru-cache'
import { metrics } from './prometheus-metrics.js'
/**
* Opções de configuração do cache
* Cache configuration options
*/
export interface CacheOptions<V> {
/** Tempo de vida em ms (default: 5 minutos) */
/** Time to live in ms (default: 5 minutes) */
ttl?: number
/** Tamanho máximo do cache (default: 1000) */
/** Maximum cache size (default: 1000) */
maxSize?: number
/** Função para calcular tamanho de um item */
/** Function to calculate item size */
sizeCalculation?: (value: V) => number
/** Se deve atualizar TTL no acesso */
/** Whether to update TTL on access */
updateAgeOnGet?: boolean
/** Namespace para isolamento */
/** Namespace for isolation */
namespace?: string
/** Callback quando item expira */
/** Callback when item expires */
onExpire?: (key: string, value: V) => void
/** Se deve coletar métricas */
/** Whether to collect metrics */
collectMetrics?: boolean
/** Nome do cache para métricas */
/** Cache name for metrics */
metricName?: string
}
/**
* Estatísticas do cache
* Cache statistics
*/
export interface CacheStats {
hits: number
@@ -49,7 +50,7 @@ export interface CacheStats {
}
/**
* Item do cache com metadados
* Cache item with metadata
*/
export interface CacheItem<V> {
value: V
@@ -60,7 +61,7 @@ export interface CacheItem<V> {
}
/**
* Resultado de operação de cache
* Cache operation result
*/
export interface CacheResult<V> {
value: V | undefined
@@ -70,7 +71,7 @@ export interface CacheResult<V> {
}
/**
* Classe principal do Cache
* Main Cache class
*/
export class Cache<V = unknown> {
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 {
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> {
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 {
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 {
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 {
const fullKey = this.getFullKey(key)
@@ -212,7 +213,7 @@ export class Cache<V = unknown> {
}
/**
* Limpa todo o cache
* Clear the entire cache
*/
clear(): void {
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> {
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 {
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 {
let count = 0
@@ -274,14 +275,14 @@ export class Cache<V = unknown> {
}
/**
* Invalida itens por prefixo
* Invalidate items by prefix
*/
invalidateByPrefix(prefix: string): number {
return this.invalidateByPattern(new RegExp(`^${prefix}`))
}
/**
* Retorna estatísticas do cache
* Return cache statistics
*/
getStats(): CacheStats {
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 {
return this.cache.size
}
/**
* Retorna todas as chaves
* Return all keys
*/
keys(): string[] {
const prefix = `${this.namespace}:`
@@ -310,14 +311,14 @@ export class Cache<V = unknown> {
}
/**
* Retorna todos os valores
* Return all values
*/
values(): V[] {
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> }> {
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 {
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 {
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> {
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> {
private l1: Cache<V>
@@ -389,17 +390,17 @@ export class MultiLevelCache<V> {
}
async get(key: string): Promise<V | undefined> {
// Tentar L1 primeiro
// Try L1 first
const l1Value = this.l1.get(key)
if (l1Value !== undefined) {
return l1Value
}
// Tentar L2 se disponível
// Try L2 if available
if (this.l2) {
const l2Value = await this.l2.get(key)
if (l2Value !== undefined) {
// Promover para L1
// Promote to L1
this.l1.set(key, 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 } = {}) {
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>(
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()
+332 -111
View File
@@ -1,66 +1,85 @@
/**
* @fileoverview Proteção contra falhas em cascata - Circuit Breaker
* @module Utils/circuit-breaker
* Circuit Breaker Pattern Implementation
*
* Fornece:
* - Estados: Closed, Open, Half-Open
* - Thresholds configuráveis
* - Recuperação automática
* - Callbacks de estado
* - Integração com métricas
* - Fallback handlers
* Provides protection against cascading failures by monitoring operation outcomes
* and temporarily blocking requests when failure thresholds are exceeded.
*
* States:
* - CLOSED: Normal operation, requests pass through
* - OPEN: Failures exceeded threshold, requests are blocked
* - HALF_OPEN: Testing recovery, limited requests allowed
*
* @module Utils/circuit-breaker
*/
import { EventEmitter } from 'events'
import { metrics } from './prometheus-metrics.js'
/**
* Estados do Circuit Breaker
* Circuit breaker operational states
*/
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 {
/** Nome do circuit breaker (para métricas) */
name: string
/** 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
export interface FailureRecord {
timestamp: number
error: Error
}
/**
* 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 {
state: CircuitState
failures: number
successes: number
consecutiveFailures: number
consecutiveSuccesses: number
totalCalls: number
totalFailures: number
totalSuccesses: number
totalRejected: number
failureRate: number
lastFailureTime?: number
lastSuccessTime?: 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 {
constructor(
@@ -83,7 +102,7 @@ export class CircuitOpenError extends Error {
}
/**
* Erro de timeout
* Error thrown when operation exceeds timeout
*/
export class CircuitTimeoutError extends Error {
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 {
private state: CircuitState = 'closed'
private failures = 0
private successes = 0
private failureRecords: FailureRecord[] = []
private consecutiveFailures = 0
private consecutiveSuccesses = 0
private totalCalls = 0
private totalFailures = 0
private totalSuccesses = 0
private totalRejected = 0
private lastFailureTime?: number
private lastSuccessTime?: number
private lastStateChange?: number
private lastStateChange: number
private resetTimer?: ReturnType<typeof setTimeout>
private options: Required<CircuitBreakerOptions>
private readonly options: Required<CircuitBreakerOptions>
constructor(options: CircuitBreakerOptions) {
super()
@@ -117,10 +155,12 @@ export class CircuitBreaker extends EventEmitter {
this.options = {
name: options.name,
failureThreshold: options.failureThreshold ?? 5,
failureWindow: options.failureWindow ?? 60000,
successThreshold: options.successThreshold ?? 2,
resetTimeout: options.resetTimeout ?? 30000,
timeout: options.timeout ?? 10000,
isFailure: options.isFailure ?? (() => true),
volumeThreshold: options.volumeThreshold ?? 5,
shouldCountError: options.shouldCountError ?? (() => true),
collectMetrics: options.collectMetrics ?? true,
fallback: options.fallback ?? (() => {
throw new CircuitOpenError(this.options.name, this.state)
@@ -130,27 +170,46 @@ export class CircuitBreaker extends EventEmitter {
onSuccess: options.onSuccess ?? (() => {}),
onOpen: options.onOpen ?? (() => {}),
onClose: options.onClose ?? (() => {}),
onHalfOpen: options.onHalfOpen ?? (() => {}),
}
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> {
this.totalCalls++
// Verificar estado
// Check if circuit allows execution
if (this.state === 'open') {
this.totalRejected++
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
}
// Executar com timeout
// Execute with timeout protection
try {
const result = await this.executeWithTimeout(operation)
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 {
this.totalCalls++
if (this.state === 'open') {
this.totalRejected++
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
@@ -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> {
return new Promise<T>((resolve, reject) => {
@@ -207,12 +268,13 @@ export class CircuitBreaker extends EventEmitter {
}
/**
* Registra sucesso
* Record a successful operation
*/
private recordSuccess(): void {
this.totalSuccesses++
this.lastSuccessTime = Date.now()
this.failures = 0
this.consecutiveSuccesses++
this.consecutiveFailures = 0
if (this.options.collectMetrics) {
metrics.socketEvents.inc({ event: 'circuit_success' })
@@ -221,28 +283,34 @@ export class CircuitBreaker extends EventEmitter {
this.options.onSuccess()
this.emit('success')
// In HALF_OPEN state, check if we can close the circuit
if (this.state === 'half-open') {
this.successes++
if (this.successes >= this.options.successThreshold) {
if (this.consecutiveSuccesses >= this.options.successThreshold) {
this.transitionTo('closed')
}
}
}
/**
* Registra falha
* Record a failed operation
*/
private recordFailure(error: Error): void {
// Verificar se erro deve contar como falha
if (!this.options.isFailure(error)) {
// Check if this error should count as a failure
if (!this.options.shouldCountError(error)) {
return
}
const now = Date.now()
this.totalFailures++
this.lastFailureTime = Date.now()
this.failures++
this.successes = 0
this.lastFailureTime = now
this.consecutiveFailures++
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) {
metrics.errors.inc({ category: 'circuit_breaker', code: 'failure' })
@@ -251,15 +319,35 @@ export class CircuitBreaker extends EventEmitter {
this.options.onFailure(error)
this.emit('failure', error)
// State transition logic
if (this.state === 'half-open') {
// Any failure in HALF_OPEN immediately reopens the circuit
this.transitionTo('open')
} else if (this.state === 'closed' && this.failures >= this.options.failureThreshold) {
this.transitionTo('open')
} else if (this.state === 'closed') {
// 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 {
const oldState = this.state
@@ -271,31 +359,39 @@ export class CircuitBreaker extends EventEmitter {
this.state = newState
this.lastStateChange = Date.now()
// Limpar timer existente
// Clear existing reset timer
if (this.resetTimer) {
clearTimeout(this.resetTimer)
this.resetTimer = undefined
}
// Resetar contadores baseado no novo estado
if (newState === 'closed') {
this.failures = 0
this.successes = 0
this.options.onClose()
this.emit('close')
} else if (newState === 'open') {
this.successes = 0
this.options.onOpen()
this.emit('open')
// State-specific actions
switch (newState) {
case 'closed':
this.consecutiveFailures = 0
this.consecutiveSuccesses = 0
this.failureRecords = []
this.options.onClose()
this.emit('close')
break
// Agendar tentativa de half-open
this.resetTimer = setTimeout(() => {
this.transitionTo('half-open')
}, this.options.resetTimeout)
} else if (newState === 'half-open') {
this.successes = 0
this.failures = 0
this.emit('half-open')
case 'open':
this.consecutiveSuccesses = 0
this.options.onOpen()
this.emit('open')
// Schedule transition to HALF_OPEN
this.resetTimer = setTimeout(() => {
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)
@@ -303,58 +399,68 @@ export class CircuitBreaker extends EventEmitter {
}
/**
* Força abertura do circuito
* Manually trip the circuit to OPEN state
*/
trip(): void {
this.transitionTo('open')
}
/**
* Força fechamento do circuito
* Manually reset the circuit to CLOSED state
*/
reset(): void {
this.transitionTo('closed')
}
/**
* Retorna estado atual
* Get current circuit state
*/
getState(): CircuitState {
return this.state
}
/**
* Verifica se circuito está aberto
* Check if circuit is OPEN
*/
isOpen(): boolean {
return this.state === 'open'
}
/**
* Verifica se circuito está fechado
* Check if circuit is CLOSED
*/
isClosed(): boolean {
return this.state === 'closed'
}
/**
* Verifica se circuito está half-open
* Check if circuit is HALF_OPEN
*/
isHalfOpen(): boolean {
return this.state === 'half-open'
}
/**
* Retorna estatísticas
* Get circuit breaker statistics
*/
getStats(): CircuitBreakerStats {
this.cleanOldFailures()
const failureRate = this.totalCalls > 0
? (this.totalFailures / this.totalCalls) * 100
: 0
return {
state: this.state,
failures: this.failures,
successes: this.successes,
failures: this.failureRecords.length,
successes: this.consecutiveSuccesses,
consecutiveFailures: this.consecutiveFailures,
consecutiveSuccesses: this.consecutiveSuccesses,
totalCalls: this.totalCalls,
totalFailures: this.totalFailures,
totalSuccesses: this.totalSuccesses,
totalRejected: this.totalRejected,
failureRate,
lastFailureTime: this.lastFailureTime,
lastSuccessTime: this.lastSuccessTime,
lastStateChange: this.lastStateChange,
@@ -365,38 +471,39 @@ export class CircuitBreaker extends EventEmitter {
}
/**
* Retorna nome do circuit breaker
* Get circuit breaker name
*/
getName(): string {
return this.options.name
}
/**
* Destroy e limpa recursos
* Destroy circuit breaker and clean up resources
*/
destroy(): void {
if (this.resetTimer) {
clearTimeout(this.resetTimer)
}
this.failureRecords = []
this.removeAllListeners()
}
}
/**
* Factory para criar circuit breaker
* Factory function to create a circuit breaker
*/
export function createCircuitBreaker(options: CircuitBreakerOptions): CircuitBreaker {
return new CircuitBreaker(options)
}
/**
* Registry de circuit breakers
* Registry for managing multiple circuit breakers
*/
export class CircuitBreakerRegistry {
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 {
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 {
return this.breakers.has(name)
}
/**
* Remove circuit breaker
* Remove a circuit breaker
*/
remove(name: string): boolean {
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> {
return new Map(this.breakers)
}
/**
* Retorna estatísticas de todos os circuit breakers
* Get statistics for all circuit breakers
*/
getAllStats(): 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 {
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 {
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()
/**
* 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 } = {}) {
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>(
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(): {
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
+4 -4
View File
@@ -17,18 +17,18 @@ export * from './process-message'
export * from './message-retry-manager'
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 './logger-adapter'
export * from './baileys-logger'
// Observabilidade e rastreamento
// Observability and tracing
export * from './trace-context'
export * from './prometheus-metrics'
// Resiliência e performance
// Resilience and performance
export * from './cache-utils'
export * from './circuit-breaker'
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
*
* 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'
/**
* Labels para métricas
* Metric labels
*/
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]
/**
* Percentis padrão para summaries
* Default summary percentiles
*/
export const DEFAULT_PERCENTILES = [0.5, 0.9, 0.95, 0.99]
/**
* Interface base para métricas
* Base metric interface
*/
export interface BaseMetric {
name: string
@@ -46,7 +47,7 @@ export interface BaseMetric {
}
/**
* Valor de uma métrica com labels
* Metric value with labels
*/
export interface MetricValue {
labels: Labels
@@ -55,7 +56,7 @@ export interface MetricValue {
}
/**
* Valores de histograma
* Histogram values
*/
export interface HistogramValue {
labels: Labels
@@ -65,7 +66,7 @@ export interface HistogramValue {
}
/**
* Valores de summary
* Summary values
*/
export interface SummaryValue {
labels: Labels
@@ -75,7 +76,7 @@ export interface SummaryValue {
}
/**
* Classe Counter - incrementa monotonicamente
* Counter class - monotonically increasing
*/
export class Counter implements BaseMetric {
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 {
let labels: Labels = {}
@@ -117,7 +118,7 @@ export class Counter implements BaseMetric {
}
/**
* Retorna valor atual
* Get current value
*/
get(labels: Labels = {}): number {
const key = this.labelsToKey(labels)
@@ -125,7 +126,7 @@ export class Counter implements BaseMetric {
}
/**
* Reseta o counter
* Reset the counter
*/
reset(labels?: Labels): void {
if (labels) {
@@ -137,7 +138,7 @@ export class Counter implements BaseMetric {
}
/**
* Retorna todos os valores
* Get all values
*/
getValues(): MetricValue[] {
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 } {
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 {
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 {
let labels: Labels = {}
@@ -193,7 +194,7 @@ export class Gauge implements BaseMetric {
}
/**
* Incrementa valor
* Increment value
*/
inc(labelsOrValue?: Labels | number, value?: number): void {
let labels: Labels = {}
@@ -214,7 +215,7 @@ export class Gauge implements BaseMetric {
}
/**
* Decrementa valor
* Decrement value
*/
dec(labelsOrValue?: Labels | number, value?: number): void {
let labels: Labels = {}
@@ -235,14 +236,14 @@ export class Gauge implements BaseMetric {
}
/**
* Define para timestamp atual
* Set to current timestamp
*/
setToCurrentTime(labels: Labels = {}): void {
this.set(labels, Date.now() / 1000)
}
/**
* Retorna valor atual
* Get current value
*/
get(labels: Labels = {}): number {
const key = this.labelsToKey(labels)
@@ -250,7 +251,7 @@ export class Gauge implements BaseMetric {
}
/**
* Reseta o gauge
* Reset the gauge
*/
reset(labels?: Labels): void {
if (labels) {
@@ -262,7 +263,7 @@ export class Gauge implements BaseMetric {
}
/**
* Retorna todos os valores
* Get all values
*/
getValues(): MetricValue[] {
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): {
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 {
const start = process.hrtime.bigint()
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)
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 {
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 {
let labels: Labels = {}
@@ -344,7 +345,7 @@ export class Histogram implements BaseMetric {
this.values.set(key, histValue)
}
// Incrementar buckets apropriados
// Increment appropriate buckets
for (const bucket of this.buckets) {
if (observeValue <= bucket) {
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 {
const key = this.labelsToKey(labels)
@@ -376,7 +377,7 @@ export class Histogram implements BaseMetric {
}
/**
* Reseta o histogram
* Reset the histogram
*/
reset(labels?: Labels): void {
if (labels) {
@@ -388,14 +389,14 @@ export class Histogram implements BaseMetric {
}
/**
* Retorna todos os valores
* Get all values
*/
getValues(): HistogramValue[] {
return Array.from(this.values.values())
}
/**
* Retorna buckets configurados
* Get configured buckets
*/
getBuckets(): number[] {
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): {
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 {
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 {
let labels: Labels = {}
@@ -471,7 +472,7 @@ export class Summary implements BaseMetric {
summaryValue.sum += observeValue
summaryValue.count++
// Limitar tamanho
// Limit size
if (summaryValue.values.length > this.maxSize) {
const removed = summaryValue.values.shift()
if (removed !== undefined) {
@@ -494,7 +495,7 @@ export class Summary implements BaseMetric {
}
/**
* Calcula percentil
* Calculate percentile
*/
getPercentile(labels: Labels, percentile: number): number | undefined {
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 {
const key = this.labelsToKey(labels)
@@ -518,7 +519,7 @@ export class Summary implements BaseMetric {
}
/**
* Reseta o summary
* Reset the summary
*/
reset(labels?: Labels): void {
if (labels) {
@@ -530,14 +531,14 @@ export class Summary implements BaseMetric {
}
/**
* Retorna todos os valores
* Get all values
*/
getValues(): SummaryValue[] {
return Array.from(this.values.values())
}
/**
* Retorna percentis configurados
* Get configured percentiles
*/
getPercentiles(): number[] {
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): {
observe: (value: number) => void
@@ -562,7 +563,7 @@ export class Summary implements BaseMetric {
}
/**
* Registry de métricas
* Metrics registry
*/
export class MetricsRegistry {
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 {
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 {
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 {
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 {
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> {
const lines: string[] = []
@@ -675,7 +676,7 @@ export class MetricsRegistry {
}
/**
* Retorna content type para Prometheus
* Return content type for Prometheus
*/
contentType(): string {
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' })
/**
* Métricas pré-definidas para Baileys
* Pre-defined metrics for Baileys
*/
export const metrics = {
// Conexão
// Connection
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(
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(
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(
new Counter('messages_sent_total', 'Total de mensagens enviadas', ['type'])
new Counter('messages_sent_total', 'Total messages sent', ['type'])
),
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(
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(
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(
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(
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(
new Counter('errors_total', 'Total de erros', ['category', 'code'])
new Counter('errors_total', 'Total errors', ['category', 'code'])
),
// Retries
retries: baileysMetrics.register(
new Counter('retries_total', 'Total de retentativas', ['operation'])
new Counter('retries_total', 'Total retries', ['operation'])
),
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
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(
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(
new Counter('encryption_operations_total', 'Total de operações de criptografia', ['operation'])
new Counter('encryption_operations_total', 'Total encryption operations', ['operation'])
),
// Cache
cacheHits: baileysMetrics.register(new Counter('cache_hits_total', 'Total de cache hits', ['cache'])),
cacheMisses: baileysMetrics.register(new Counter('cache_misses_total', 'Total de cache misses', ['cache'])),
cacheSize: baileysMetrics.register(new Gauge('cache_size', 'Tamanho atual do cache', ['cache'])),
cacheHits: baileysMetrics.register(new Counter('cache_hits_total', 'Total cache hits', ['cache'])),
cacheMisses: baileysMetrics.register(new Counter('cache_misses_total', 'Total cache misses', ['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) {
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
* @module Utils/retry-utils
* Smart Retry Logic
*
* Fornece:
* Provides:
* - Exponential backoff
* - Jitter para evitar thundering herd
* - Max attempts configurável
* - Predicates de retry customizáveis
* - Integração com circuit breaker
* - Hooks de eventos
* - Cancelamento
* - Jitter to avoid thundering herd
* - Configurable max attempts
* - Customizable retry predicates
* - Circuit breaker integration
* - Event hooks
* - Cancellation support
*
* @module Utils/retry-utils
*/
import { EventEmitter } from 'events'
@@ -17,48 +18,48 @@ import { metrics } from './prometheus-metrics.js'
import type { CircuitBreaker } from './circuit-breaker.js'
/**
* Estratégias de backoff
* Backoff strategies
*/
export type BackoffStrategy = 'exponential' | 'linear' | 'constant' | 'fibonacci'
/**
* Opções de configuração de retry
* Retry configuration options
*/
export interface RetryOptions {
/** Número máximo de tentativas (default: 3) */
/** Maximum number of attempts (default: 3) */
maxAttempts?: number
/** Delay base em ms (default: 1000) */
/** Base delay in ms (default: 1000) */
baseDelay?: number
/** Delay máximo em ms (default: 30000) */
/** Maximum delay in ms (default: 30000) */
maxDelay?: number
/** Estratégia de backoff (default: exponential) */
/** Backoff strategy (default: exponential) */
backoffStrategy?: BackoffStrategy
/** Multiplicador para exponential backoff (default: 2) */
/** Multiplier for exponential backoff (default: 2) */
backoffMultiplier?: number
/** Percentual de jitter (0-1, default: 0.1) */
/** Jitter percentage (0-1, default: 0.1) */
jitter?: number
/** Função para determinar se deve retry */
/** Function to determine if should retry */
shouldRetry?: (error: Error, attempt: number) => boolean | Promise<boolean>
/** Timeout por tentativa em ms */
/** Timeout per attempt in ms */
timeout?: number
/** Nome da operação para métricas */
/** Operation name for metrics */
operationName?: string
/** Coletar métricas */
/** Collect metrics */
collectMetrics?: boolean
/** Circuit breaker para integração */
/** Circuit breaker for integration */
circuitBreaker?: CircuitBreaker
/** Callback antes de cada retry */
/** Callback before each retry */
onRetry?: (error: Error, attempt: number, delay: number) => void | Promise<void>
/** Callback em sucesso */
/** Callback on success */
onSuccess?: (result: unknown, attempt: number) => void
/** Callback em falha final */
/** Callback on final failure */
onFailure?: (error: Error, attempts: number) => void
/** Signal para cancelamento */
/** Signal for cancellation */
abortSignal?: AbortSignal
}
/**
* Resultado de operação com retry
* Result of operation with retry
*/
export interface RetryResult<T> {
success: boolean
@@ -70,7 +71,7 @@ export interface RetryResult<T> {
}
/**
* Contexto de retry
* Retry context
*/
export interface RetryContext {
attempt: number
@@ -81,7 +82,7 @@ export interface RetryContext {
}
/**
* Erro de retry esgotado
* Retry exhausted error
*/
export class RetryExhaustedError extends Error {
constructor(
@@ -97,7 +98,7 @@ export class RetryExhaustedError extends Error {
}
/**
* Erro de abort
* Abort error
*/
export class RetryAbortedError extends Error {
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(
attempt: number,
@@ -142,10 +143,10 @@ export function calculateDelay(
delay = baseDelay
}
// Aplicar cap de delay máximo
// Apply max delay cap
delay = Math.min(delay, maxDelay)
// Aplicar jitter
// Apply jitter
if (jitter > 0) {
const jitterAmount = delay * jitter
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 {
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> {
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>(
operation: () => Promise<T>,
@@ -236,7 +237,7 @@ async function executeWithTimeout<T>(
}
/**
* Função principal de retry
* Main retry function
*/
export async function retry<T>(
operation: (context: RetryContext) => T | Promise<T>,
@@ -269,7 +270,7 @@ export async function retry<T>(
let lastError: Error | undefined
// Verificar abort inicial
// Check initial abort
if (config.abortSignal?.aborted) {
throw new RetryAbortedError(0)
}
@@ -277,19 +278,19 @@ export async function retry<T>(
for (let attempt = 1; attempt <= config.maxAttempts; attempt++) {
context.attempt = attempt
// Verificar abort
// Check abort
if (config.abortSignal?.aborted) {
context.aborted = true
throw new RetryAbortedError(attempt)
}
// Verificar circuit breaker
// Check circuit breaker
if (config.circuitBreaker?.isOpen()) {
throw new Error(`Circuit breaker "${config.circuitBreaker.getName()}" is open`)
}
try {
// Executar operação
// Execute operation
let result: T
if (config.timeout) {
@@ -302,7 +303,7 @@ export async function retry<T>(
result = await operation(context)
}
// Sucesso
// Success
if (config.collectMetrics) {
metrics.retries.inc({ operation: config.operationName })
}
@@ -313,11 +314,11 @@ export async function retry<T>(
lastError = error as Error
context.lastError = lastError
// Verificar se deve retry
// Check if should retry
const shouldRetry = await config.shouldRetry(lastError, attempt)
if (!shouldRetry || attempt >= config.maxAttempts) {
// Falha final
// Final failure
if (config.collectMetrics) {
metrics.errors.inc({ category: 'retry', code: 'exhausted' })
}
@@ -327,7 +328,7 @@ export async function retry<T>(
throw new RetryExhaustedError(lastError, attempt, config.operationName)
}
// Calcular delay
// Calculate delay
const delay = calculateDelay(
attempt,
config.baseDelay,
@@ -337,24 +338,24 @@ export async function retry<T>(
config.jitter
)
// Callback de retry
// Retry callback
await config.onRetry(lastError, attempt, delay)
if (config.collectMetrics) {
metrics.retryLatency.observe({ operation: config.operationName }, delay)
}
// Aguardar delay
// Wait for delay
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)
}
/**
* Retry com resultado detalhado
* Retry with detailed result
*/
export async function retryWithResult<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 = {}) {
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 = {}) {
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>(
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 {
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>(
id: string,
operation: (context: RetryContext) => T | Promise<T>,
options?: RetryOptions
): Promise<T> {
// Cancelar retry anterior com mesmo ID
// Cancel previous retry with same ID
this.cancel(id)
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 {
const active = this.activeRetries.get(id)
@@ -501,7 +502,7 @@ export class RetryManager extends EventEmitter {
}
/**
* Cancela todos os retries
* Cancel all retries
*/
cancelAll(): void {
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 {
return this.activeRetries.has(id)
}
/**
* Retorna contexto de retry ativo
* Return active retry context
*/
getContext(id: string): RetryContext | undefined {
return this.activeRetries.get(id)?.context
}
/**
* Retorna IDs de retries ativos
* Return active retry IDs
*/
getActiveIds(): string[] {
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 = {
/** Sempre retry (até max attempts) */
/** Always retry (up to max attempts) */
always: () => true,
/** Nunca retry */
/** Never retry */
never: () => false,
/** Retry apenas em erros de rede */
/** Retry only on network errors */
onNetworkError: (error: Error) => {
const networkErrors = ['ECONNREFUSED', 'ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND', 'EAI_AGAIN']
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:
(codes: string[]) =>
(error: Error): boolean => {
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:
(codes: string[]) =>
(error: Error): boolean => {
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) => {
const message = error.message.toLowerCase()
return (
@@ -575,14 +576,14 @@ export const retryPredicates = {
)
},
/** Combina múltiplos predicates com OR */
/** Combine multiple predicates with OR */
or:
(...predicates: Array<(error: Error, attempt: number) => boolean>) =>
(error: Error, attempt: number): boolean => {
return predicates.some((p) => p(error, attempt))
},
/** Combina múltiplos predicates com AND */
/** Combine multiple predicates with AND */
and:
(...predicates: Array<(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 = {
/** Retry agressivo (muitas tentativas, delays curtos) */
/** Aggressive retry (many attempts, short delays) */
aggressive: {
maxAttempts: 10,
baseDelay: 100,
@@ -603,7 +604,7 @@ export const retryConfigs = {
jitter: 0.2,
},
/** Retry conservador (poucas tentativas, delays longos) */
/** Conservative retry (few attempts, long delays) */
conservative: {
maxAttempts: 3,
baseDelay: 2000,
@@ -612,7 +613,7 @@ export const retryConfigs = {
jitter: 0.1,
},
/** Retry rápido (para operações curtas) */
/** Fast retry (for short operations) */
fast: {
maxAttempts: 5,
baseDelay: 50,
@@ -621,7 +622,7 @@ export const retryConfigs = {
jitter: 0.05,
},
/** Retry para operações de rede */
/** Retry for network operations */
network: {
maxAttempts: 5,
baseDelay: 1000,
+70 -57
View File
@@ -1,25 +1,26 @@
/**
* @fileoverview Sistema de logging estruturado para InfiniteAPI
* @module Utils/structured-logger
* Structured Logging System for InfiniteAPI
*
* Fornece:
* - Níveis de log configuráveis (trace, debug, info, warn, error, fatal)
* - Formatação JSON para análise
* - Contexto hierárquico com child loggers
* - Integração com sistemas externos (hooks)
* - Suporte a métricas de logging
* - Sanitização de dados sensíveis
* Provides:
* - Configurable log levels (trace, debug, info, warn, error, fatal)
* - JSON formatting for log analysis
* - Hierarchical context with child loggers
* - External system integration via hooks
* - Logging metrics support
* - Sensitive data sanitization
*
* @module Utils/structured-logger
*/
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'
/**
* Valores numéricos para cada nível de log
* Numeric values for each log level
*/
export const LOG_LEVEL_VALUES: Record<LogLevel, number> = {
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 {
/** Nível mínimo de log a ser registrado */
/** Minimum log level to record */
level: LogLevel
/** Nome do serviço/componente */
/** Service/component name */
name?: string
/** Contexto adicional a ser incluído em todos os logs */
/** Additional context to include in all logs */
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
/** Campos a serem sanitizados (senhas, tokens, etc.) */
/** Fields to sanitize (passwords, tokens, etc.) */
redactFields?: string[]
/** Hook para enviar logs para sistemas externos */
/** Hook for sending logs to external systems */
externalHook?: (entry: LogEntry) => void | Promise<void>
/** Se deve incluir stack trace em erros */
/** Include stack trace in errors */
includeStackTrace?: boolean
/** Timezone para timestamps (default: UTC) */
/** Timezone for timestamps (default: UTC) */
timezone?: string
}
/**
* Entrada de log estruturada
* Structured log entry
*/
export interface LogEntry {
/** Timestamp ISO 8601 */
/** ISO 8601 timestamp */
timestamp: string
/** Nível do log */
/** Log level */
level: LogLevel
/** Valor numérico do nível */
/** Numeric level value */
levelValue: number
/** Mensagem principal */
/** Main message */
message: string
/** Nome do logger/componente */
/** Logger/component name */
name?: string
/** Contexto adicional */
/** Additional context */
context?: Record<string, unknown>
/** Dados do objeto logado */
/** Logged object data */
data?: Record<string, unknown>
/** Stack trace (para erros) */
/** Stack trace (for errors) */
stack?: string
/** ID de correlação para rastreamento */
/** Correlation ID for tracing */
correlationId?: string
/** Duração de operação em ms (se aplicável) */
/** Operation duration in ms (if applicable) */
durationMs?: number
}
/**
* Métricas de logging
* Logger metrics
*/
export interface LoggerMetrics {
totalLogs: number
@@ -90,7 +91,7 @@ export interface LoggerMetrics {
}
/**
* Campos padrão a serem sanitizados
* Default fields to sanitize
*/
const DEFAULT_REDACT_FIELDS = [
'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 {
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 {
return this.config.level
}
/**
* Setter para o nível do logger
* Set log level
*/
set level(newLevel: string) {
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 {
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 {
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 {
if (!this.isLevelEnabled(level)) {
@@ -188,13 +201,13 @@ export class StructuredLogger implements ILogger {
const entry = this.createLogEntry(level, obj, msg)
// Atualizar métricas
// Update metrics
this.updateMetrics(level)
// Output
this.output(entry)
// Hook externo (async, não bloqueia)
// External hook (async, non-blocking)
if (this.config.externalHook) {
Promise.resolve(this.config.externalHook(entry)).catch(() => {
// 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 {
const timestamp = new Date().toISOString()
@@ -211,7 +224,7 @@ export class StructuredLogger implements ILogger {
let data: Record<string, unknown> | undefined
let stack: string | undefined
// Processar objeto
// Process object
if (obj instanceof Error) {
message = message || obj.message
if (this.config.includeStackTrace && obj.stack) {
@@ -231,7 +244,7 @@ export class StructuredLogger implements ILogger {
message = message || obj
}
// Extrair correlationId e durationMs se presentes
// Extract correlationId and durationMs if present
const correlationId = data?.correlationId as string | 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> {
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 {
this.metrics.totalLogs++
@@ -284,7 +297,7 @@ export class StructuredLogger implements ILogger {
}
/**
* Output do log
* Output log entry
*/
private output(entry: LogEntry): void {
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 {
const parts = [
@@ -333,7 +346,7 @@ export class StructuredLogger implements ILogger {
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 {
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 {
return this.child(context)
}
/**
* Log com correlation ID
* Log with correlation ID
*/
withCorrelationId(correlationId: string): StructuredLogger {
return this.child({ correlationId })
}
/**
* Log de operação com duração
* Log operation with duration tracking
*/
logOperation<T>(
operationName: string,
@@ -412,14 +425,14 @@ export class StructuredLogger implements ILogger {
}
/**
* Retorna métricas do logger
* Get logger metrics
*/
getMetrics(): LoggerMetrics {
return { ...this.metrics }
}
/**
* Reseta métricas
* Reset metrics
*/
resetMetrics(): void {
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 {
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
@@ -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 } {
const start = process.hrtime.bigint()
+86 -85
View File
@@ -1,153 +1,154 @@
/**
* @fileoverview Contexto de rastreamento para requests
* @module Utils/trace-context
* Request Tracing Context
*
* Fornece:
* - Geração de trace IDs únicos
* - Context propagation entre operações
* - Correlation IDs para rastrear requests
* Provides:
* - Unique trace ID generation
* - Context propagation between operations
* - Correlation IDs for request tracking
* - Performance timing
* - Span tracking para operações aninhadas
* - Baggage para dados contextuais
* - Span tracking for nested operations
* - Baggage for contextual data
*
* @module Utils/trace-context
*/
import { randomBytes } from 'crypto'
import { AsyncLocalStorage } from 'async_hooks'
/**
* Identificadores de trace
* Trace identifiers
*/
export interface TraceIds {
/** ID único do trace (16 bytes hex) */
/** Unique trace ID (16 bytes hex) */
traceId: string
/** ID do span atual (8 bytes hex) */
/** Current span ID (8 bytes hex) */
spanId: string
/** ID do span pai (opcional) */
/** Parent span ID (optional) */
parentSpanId?: string
/** ID de correlação para logging */
/** Correlation ID for logging */
correlationId: string
}
/**
* Dados de baggage (contexto propagado)
* Baggage data (propagated context)
*/
export type Baggage = Record<string, string | number | boolean>
/**
* Status de um span
* Span status
*/
export type SpanStatus = 'unset' | 'ok' | 'error'
/**
* Span representa uma unidade de trabalho
* Span represents a unit of work
*/
export interface Span {
/** Nome da operação */
/** Operation name */
name: string
/** IDs de rastreamento */
/** Trace identifiers */
traceIds: TraceIds
/** Timestamp de início (ms) */
/** Start timestamp (ms) */
startTime: number
/** Timestamp de fim (ms) */
/** End timestamp (ms) */
endTime?: number
/** Duração em ms */
/** Duration in ms */
duration?: number
/** Status do span */
/** Span status */
status: SpanStatus
/** Atributos do span */
/** Span attributes */
attributes: Record<string, unknown>
/** Eventos ocorridos durante o span */
/** Events occurred during the span */
events: SpanEvent[]
/** Se o span está finalizado */
/** Whether the span has ended */
ended: boolean
}
/**
* Evento dentro de um span
* Event within a span
*/
export interface SpanEvent {
/** Nome do evento */
/** Event name */
name: string
/** Timestamp do evento */
/** Event timestamp */
timestamp: number
/** Atributos do evento */
/** Event attributes */
attributes?: Record<string, unknown>
}
/**
* Contexto completo de trace
* Complete trace context
*/
export interface TraceContext {
/** IDs de rastreamento */
/** Trace identifiers */
traceIds: TraceIds
/** Baggage (dados propagados) */
/** Baggage (propagated data) */
baggage: Baggage
/** Span atual */
/** Current span */
currentSpan?: Span
/** Stack de spans (para spans aninhados) */
/** Span stack (for nested spans) */
spanStack: Span[]
/** Timestamp de criação do contexto */
/** Context creation timestamp */
createdAt: number
/** Metadados adicionais */
/** Additional metadata */
metadata: Record<string, unknown>
}
/**
* Opções para criar um novo contexto
* Options for creating a new context
*/
export interface CreateContextOptions {
/** Trace ID existente (para propagação) */
/** Existing trace ID (for propagation) */
traceId?: string
/** Parent span ID */
parentSpanId?: string
/** Correlation ID existente */
/** Existing correlation ID */
correlationId?: string
/** Baggage inicial */
/** Initial baggage */
baggage?: Baggage
/** Metadados iniciais */
/** Initial metadata */
metadata?: Record<string, unknown>
}
/**
* Opções para criar um span
* Options for creating a span
*/
export interface CreateSpanOptions {
/** Nome do span */
/** Span name */
name: string
/** Atributos iniciais */
/** Initial attributes */
attributes?: Record<string, unknown>
/** Se deve ser filho do span atual */
/** Whether to be a child of the current span */
asChild?: boolean
}
/**
* Storage assíncrono para contexto de trace
* Async storage for trace context
*/
const traceStorage = new AsyncLocalStorage<TraceContext>()
/**
* Gera um ID hexadecimal aleatório
* Generate a random hexadecimal ID
*/
function generateId(bytes: number): string {
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 {
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 {
return generateId(8)
}
/**
* Gera um correlation ID mais legível
* Generate a more readable correlation ID
*/
export function generateCorrelationId(): string {
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 {
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 {
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 {
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 {
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 {
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>(
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 {
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 {
const context = getOrCreateContext()
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) {
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 {
if (span.ended) {
@@ -274,7 +275,7 @@ export function endSpan(span: Span, status?: SpanStatus): void {
span.status = status || 'ok'
span.ended = true
// Pop span do stack no contexto
// Pop span from stack in context
const context = getCurrentContext()
if (context && context.currentSpan === span) {
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 {
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 {
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 {
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) {
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>(
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>(
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>(
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 {
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 {
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 {
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 {
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 = {
TRACE_ID: 'x-trace-id',
@@ -507,7 +508,7 @@ export const TRACE_HEADERS = {
} as const
/**
* Injeta contexto em headers HTTP
* Inject context into HTTP headers
*/
export function injectTraceHeaders(headers: Record<string, string>): Record<string, string> {
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
}
// Baggage como lista de key=value
// Baggage as key=value list
if (Object.keys(context.baggage).length > 0) {
result[TRACE_HEADERS.BAGGAGE] = Object.entries(context.baggage)
.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 {
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 {
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 {
try {
@@ -599,19 +600,19 @@ export function importContext(serialized: string): CreateContextOptions {
// === Timer Utilities ===
/**
* Timer de alta precisão
* High precision timer
*/
export interface PrecisionTimer {
/** Retorna tempo decorrido em milliseconds */
/** Return elapsed time in milliseconds */
elapsed(): number
/** Retorna tempo decorrido formatado */
/** Return formatted elapsed time */
elapsedFormatted(): string
/** Para o timer e retorna duração */
/** Stop the timer and return duration */
stop(): number
}
/**
* Cria um timer de alta precisão
* Create a high precision timer
*/
export function createPrecisionTimer(): PrecisionTimer {
const start = process.hrtime.bigint()