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:
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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'
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user