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