UNPKG

@erickzao/api-stats-cli

Version:

CLI oficial para configuração automática do API Stats Logger

216 lines (184 loc) 6.07 kB
const axios = require('axios'); const performanceNow = require('performance-now'); class Logger { /** * @param {Object} options * @param {string} options.apiKey - Sua API Key * @param {string} options.url - URL do endpoint de logs * @param {string} [options.service] - Nome do serviço * @param {string} [options.environment] - Ambiente (production, staging, etc) * @param {number} [options.batchSize=10] - Tamanho do lote para envio * @param {number} [options.flushInterval=2000] - Intervalo de flush em ms * @param {boolean} [options.enabled=true] - Se o logger está habilitado * @param {number} [options.maxRetries=3] - Máximo de tentativas de reenvio * @param {number} [options.retryDelay=1000] - Delay entre tentativas (ms) */ constructor({ apiKey, url, service, environment, batchSize = 10, flushInterval = 2000, enabled = true, maxRetries = 3, retryDelay = 1000 }) { this.apiKey = apiKey; this.url = url; this.service = service; this.environment = environment; this.batchSize = batchSize; this.flushInterval = flushInterval; this.enabled = enabled; this.maxRetries = maxRetries; this.retryDelay = retryDelay; this.buffer = []; this.failedBuffer = []; // Buffer para logs que falharam this.stats = { sent: 0, failed: 0, retries: 0, lastFlush: null }; if (this.enabled) { this.timer = setInterval(() => this.flush(), this.flushInterval); // Timer para retry de logs que falharam this.retryTimer = setInterval(() => this.retryFailed(), this.retryDelay * 2); } } log({ level, message, service, environment, metadata = {} }) { if (!this.enabled) return; const logEntry = { timestamp: new Date().toISOString(), level, message, service: service || this.service, environment: environment || this.environment, metadata: { ...metadata, sdk_version: '1.0.0', pid: process.pid }, }; this.buffer.push(logEntry); if (this.buffer.length >= this.batchSize) { this.flush(); } } async flush() { if (this.buffer.length === 0) return; const toSend = this.buffer.splice(0, this.batchSize); const startTime = performanceNow(); try { await this._sendLogs(toSend); const duration = performanceNow() - startTime; this.stats.sent += toSend.length; this.stats.lastFlush = new Date().toISOString(); // Log de métricas ocasionalmente if (this.stats.sent % 100 === 0) { this._logStats(duration); } } catch (err) { this.stats.failed += toSend.length; this.failedBuffer.push(...toSend.map(log => ({ log, attempts: 0 }))); this._handleError(err, toSend.length); } } async retryFailed() { if (this.failedBuffer.length === 0) return; const toRetry = this.failedBuffer.splice(0, this.batchSize); const validRetries = toRetry.filter(item => item.attempts < this.maxRetries); if (validRetries.length === 0) return; try { await this._sendLogs(validRetries.map(item => item.log)); this.stats.sent += validRetries.length; this.stats.retries += validRetries.length; } catch (err) { // Incrementa tentativas e recoloca no buffer de falhas validRetries.forEach(item => { item.attempts++; if (item.attempts < this.maxRetries) { this.failedBuffer.push(item); } }); this.stats.failed += validRetries.length; } } async _sendLogs(logs) { const response = await axios.post(this.url, logs, { headers: { 'x-api-key': this.apiKey, 'Content-Type': 'application/json', 'User-Agent': 'api-stats-logger/1.0.0' }, timeout: 10000, }); if (response.status !== 200 && response.status !== 201) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return response; } _logStats(lastFlushDuration) { console.log(`[ApiStatsLogger] Stats: ${this.stats.sent} sent, ${this.stats.failed} failed, ${this.stats.retries} retries, last flush: ${lastFlushDuration.toFixed(2)}ms`); } _handleError(error, logCount) { const errorMessage = error.response ? `HTTP ${error.response.status}: ${error.response.statusText}` : error.message; // Só loga erro no console se estiver em desenvolvimento if (process.env.NODE_ENV === 'development') { console.error(`[ApiStatsLogger] Erro ao enviar ${logCount} logs: ${errorMessage}`); } } // Método para obter estatísticas getStats() { return { ...this.stats, bufferSize: this.buffer.length, failedBufferSize: this.failedBuffer.length, enabled: this.enabled }; } // Método para desabilitar temporariamente disable() { this.enabled = false; if (this.timer) { clearInterval(this.timer); this.timer = null; } if (this.retryTimer) { clearInterval(this.retryTimer); this.retryTimer = null; } } // Método para reabilitar enable() { if (this.enabled) return; this.enabled = true; this.timer = setInterval(() => this.flush(), this.flushInterval); this.retryTimer = setInterval(() => this.retryFailed(), this.retryDelay * 2); } async close() { if (this.timer) { clearInterval(this.timer); this.timer = null; } if (this.retryTimer) { clearInterval(this.retryTimer); this.retryTimer = null; } // Flush final de todos os logs pendentes await this.flush(); // Tenta enviar logs que falharam uma última vez if (this.failedBuffer.length > 0) { try { const finalLogs = this.failedBuffer.map(item => item.log); await this._sendLogs(finalLogs); } catch (err) { console.error('[ApiStatsLogger] Falha ao enviar logs finais:', err.message); } } } } module.exports = Logger;