@erickzao/api-stats-cli
Version:
CLI oficial para configuração automática do API Stats Logger
216 lines (184 loc) • 6.07 kB
JavaScript
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;