api-stats-logger
Version:
SDK completo de logging e monitoramento de APIs com nova estrutura de logs organizada, auto-instrumentação, dashboard em tempo real e CLI para configuração automática. Suporta logs estruturados por contexto (HTTP, business, security, system) com campos op
1,722 lines (1,416 loc) • 49.9 kB
JavaScript
// Redirecionamento para CLI moderna
const { ModernApiStatsCLI } = require('./modern-cli');
// Verificar se deve usar CLI moderna (padrão) ou antiga (--legacy)
const useModernCLI = !process.argv.includes('--legacy');
if (useModernCLI) {
console.log('🚀 Iniciando API Stats Logger CLI moderna...\n');
const cli = new ModernApiStatsCLI();
cli.run().catch(console.error);
} else {
console.log('⚠️ Usando CLI legada (--legacy)...\n');
// Código original da CLI
const fs = require('fs');
const path = require('path');
const readline = require('readline');
const axios = require('axios');
class ApiStatsCLI {
constructor() {
this.rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
this.baseUrl = process.env.API_STATS_URL || 'https://apistats.eway-api.com.br';
this.authToken = null; // Armazenar token JWT
this.deviceId = null; // Armazenar deviceId para requisições autenticadas
// Inicializar armazenamento seguro
const SecureStorage = require('./secure-storage');
this.secureStorage = new SecureStorage();
// Migrar credenciais antigas automaticamente
this.secureStorage.migrateOldCredentials();
}
async run() {
console.log('🚀 API Stats Logger - Configuração Inicial\n');
try {
// 1. Verificar se usuário já está autenticado
console.log('🔄 Verificando autenticação...');
await this.checkAuthentication();
console.log('✅ Autenticação verificada!');
// 2. Coletar configurações do projeto
console.log('\n🔄 Coletando configurações do projeto...');
const config = await this.collectConfig();
console.log('✅ Configurações coletadas!');
// 3. Validar ou criar projeto/API key
console.log('\n🔄 Processando projeto e API key...');
if (config.existingKey) {
const isValid = await this.validateApiKey(config.existingKey);
if (!isValid) {
throw new Error('API key fornecida é inválida');
}
config.apiKey = config.existingKey;
console.log('✅ API key validada com sucesso!');
} else {
const result = await this.createProjectAndApiKey(config);
config.projectId = result.projectId;
config.apiKey = result.apiKey;
console.log('✅ Projeto e API key criados com sucesso!');
}
// 4. Configurar projeto localmente
console.log('\n🔄 Configurando projeto localmente...');
await this.setupProject(config);
console.log('✅ Projeto configurado localmente!');
console.log('\n🎉 Configuração concluída com sucesso!');
console.log('\n📖 Próximos passos:');
console.log('1. Copie as variáveis do .env.api-stats para seu .env');
console.log('2. Execute o exemplo gerado para testar');
console.log('3. Acesse o dashboard para visualizar os logs');
console.log(`4. Dashboard: ${this.baseUrl.replace('/api', '')}\n`);
// Fechar a interface readline de forma segura
await this.cleanup();
// Encerrar o processo
process.exit(0);
} catch (error) {
console.error('\n❌ Erro na configuração:', error.message);
console.log('\n💡 Precisa de ajuda? Verifique:');
console.log('- Se o servidor API Stats está rodando');
console.log('- Se as credenciais estão corretas');
console.log('- Se você tem permissão para criar projetos');
console.log('- Se há conexão com a internet');
// Fechar a interface readline de forma segura
await this.cleanup();
process.exit(1);
}
}
async checkAuthentication() {
console.log('🔐 Verificando autenticação...\n');
// Verificar se já existe token armazenado
const storedData = await this.getStoredAuthData();
if (storedData && storedData.token) {
const isValid = await this.validateToken(storedData.token);
if (isValid) {
this.authToken = storedData.token;
this.deviceId = storedData.deviceId;
console.log('✅ Já autenticado!\n');
return;
} else {
console.log('⚠️ Sessão expirada, fazendo novo login...');
await this.clearStoredAuthData();
}
}
// Perguntar se quer fazer login ou criar conta
console.log('Você precisa estar autenticado para criar projetos.');
const action = await this.question('Escolha uma opção:\n1. 🔐 Login com Google (recomendado)\n2. 📧 Login tradicional (email/senha)\n3. 📝 Criar conta\n4. 🔑 Usar API key existente\nOpção [1]: ', '1');
console.log(`\n🔄 Processando opção ${action}...\n`);
switch (action) {
case '1':
await this.googleLogin();
break;
case '2':
await this.login();
break;
case '3':
await this.register();
break;
case '4':
const apiKey = await this.question('API Key: ');
const isValid = await this.validateApiKey(apiKey);
if (!isValid) {
throw new Error('API key inválida');
}
return { existingKey: apiKey };
default:
await this.googleLogin();
break;
}
}
async login() {
console.log('\n📝 Login:');
const username = await this.question('Username ou email: ');
const password = await this.question('Senha: ', '', true); // true = esconder input
console.log('\n🔄 Conectando ao servidor...');
try {
const response = await axios.post(`${this.baseUrl}/auth/login`, {
username,
password
}, {
timeout: 15000, // Aumentar timeout para 15s
headers: {
'Content-Type': 'application/json',
'User-Agent': 'API-Stats-CLI/1.1.2'
},
withCredentials: true, // Para receber cookies
});
// Extrair token dos cookies se disponível
let accessToken = null;
let deviceId = null;
// Verificar se tem token no body (formato antigo)
if (response.data.access_token) {
accessToken = response.data.access_token;
deviceId = response.data.deviceId;
}
// Extrair token dos cookies se disponível (formato novo)
if (response.headers['set-cookie']) {
const cookies = response.headers['set-cookie'];
for (const cookie of cookies) {
if (cookie.startsWith('access_token=')) {
accessToken = cookie.split(';')[0].split('=')[1];
}
if (cookie.startsWith('device_id=')) {
deviceId = cookie.split(';')[0].split('=')[1];
}
}
}
if (!accessToken) {
throw new Error('Token de acesso não foi retornado pela API. Verifique a configuração do servidor.');
}
this.authToken = accessToken;
this.deviceId = deviceId;
await this.storeAuthData(this.authToken, this.deviceId);
console.log('✅ Login realizado com sucesso!\n');
} catch (error) {
console.log('\n❌ Erro no login:');
if (error.code === 'ECONNREFUSED') {
throw new Error(`Não foi possível conectar ao servidor em ${this.baseUrl}. Verifique se a API está rodando.`);
}
if (error.code === 'ENOTFOUND') {
throw new Error(`Servidor não encontrado: ${this.baseUrl}. Verifique a URL.`);
}
if (error.code === 'ECONNRESET' || error.code === 'ETIMEDOUT') {
throw new Error(`Timeout na conexão com ${this.baseUrl}. O servidor pode estar sobrecarregado.`);
}
if (error.response) {
if (error.response.status === 401) {
throw new Error('Credenciais inválidas. Verifique username/email e senha.');
}
if (error.response.status === 404) {
throw new Error(`Endpoint /auth/login não encontrado em ${this.baseUrl}. A API pode não estar configurada corretamente.`);
}
if (error.response.status >= 500) {
throw new Error(`Erro interno do servidor (${error.response.status}). Tente novamente em alguns minutos.`);
}
throw new Error(`Erro HTTP ${error.response.status}: ${error.response.data?.message || 'Erro desconhecido'}`);
}
throw new Error(`Erro na conexão: ${error.message}`);
}
}
async register() {
console.log('\n📝 Criar conta:');
const username = await this.question('Username: ');
const email = await this.question('Email (@grupoloyalty.com.br ou @eway.dev): ');
const password = await this.question('Senha: ', '', true);
const confirmPassword = await this.question('Confirmar senha: ', '', true);
try {
await axios.post(`${this.baseUrl}/auth/register`, {
username,
email,
password,
confirmPassword
}, {
timeout: 10000,
headers: { 'Content-Type': 'application/json' }
});
console.log('✅ Conta criada com sucesso!');
console.log('Fazendo login automaticamente...\n');
// Fazer login automaticamente
const loginResponse = await axios.post(`${this.baseUrl}/auth/login`, {
username,
password
}, {
timeout: 10000,
headers: {
'Content-Type': 'application/json',
'User-Agent': 'API-Stats-CLI/1.0'
}
});
this.authToken = loginResponse.data.access_token;
this.deviceId = loginResponse.data.deviceId;
await this.storeAuthData(this.authToken, this.deviceId);
console.log('✅ Login realizado com sucesso!\n');
} catch (error) {
throw new Error(`Erro ao criar conta: ${error.response?.data?.message || error.message}`);
}
}
async googleLogin() {
const ora = require('ora');
const spinner = ora('Iniciando autenticação Google').start();
try {
// Verificar se o backend está disponível
spinner.text = 'Verificando conexão com o servidor';
await axios.get(`${this.baseUrl}/health`, { timeout: 5000 });
// Iniciar servidor local temporário para capturar callback
const serverPort = await this.startLocalServer();
spinner.text = 'Abrindo autenticação Google no navegador';
// Abrir URL de autenticação Google com parâmetro da porta da CLI
const authUrl = `${this.baseUrl}/auth/google?port=${serverPort}`;
const open = require('open');
await open(authUrl);
spinner.stop();
console.log('\n🌐 Autenticação Google aberta no navegador');
console.log('⚠️ Complete a autenticação no navegador - o retorno será automático\n');
const validationSpinner = ora('Aguardando autenticação').start();
// Aguardar resultado do servidor local
const result = await this.waitForAuthResult();
// Parar servidor local
this.stopLocalServer();
if (!result.success) {
throw new Error(result.error || 'Autenticação falhou');
}
this.authToken = result.data.access_token;
this.deviceId = result.data.deviceId;
await this.storeAuthData(this.authToken, this.deviceId);
validationSpinner.succeed('Autenticação Google concluída com sucesso!');
console.log(`👤 Bem-vindo, ${result.data.user.name || result.data.user.email}!\n`);
} catch (error) {
// Garantir que o servidor local seja parado em caso de erro
this.stopLocalServer();
if (spinner.isSpinning) spinner.fail('Erro na autenticação Google');
console.error(`❌ Erro: ${error.message}`);
if (error.message.includes('Conta não encontrada')) {
console.log('⚠️ Você precisa ter uma conta no sistema antes de usar a CLI.');
console.log('🌐 Acesse o dashboard web primeiro para criar sua conta.');
}
// Oferecer alternativa
console.log('\n💡 Alternativas:');
console.log(' 1. Tente novamente');
console.log(' 2. Use login tradicional (email/senha)');
console.log(' 3. Use API key existente');
throw error;
}
}
async startLocalServer() {
const http = require('http');
// Encontrar porta disponível
const getAvailablePort = () => {
return new Promise((resolve, reject) => {
const server = require('net').createServer();
server.listen(0, (err) => {
if (err) reject(err);
const port = server.address().port;
server.close(() => resolve(port));
});
});
};
const port = await getAvailablePort();
this.localServer = http.createServer((req, res) => {
const url = new URL(req.url, `http://localhost:${port}`);
if (url.pathname === '/auth/success') {
const data = url.searchParams.get('data');
if (data) {
try {
this.authResult = {
success: true,
data: JSON.parse(decodeURIComponent(data))
};
} catch (error) {
this.authResult = {
success: false,
error: 'Dados de autenticação inválidos'
};
}
} else {
this.authResult = {
success: false,
error: 'Dados de autenticação não recebidos'
};
}
// Resposta simples para o navegador
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(`
<html>
<head><title>API Stats Logger - Sucesso</title></head>
<body style="font-family: Arial; text-align: center; padding: 50px;">
<h2>✅ Autenticação realizada com sucesso!</h2>
<p>Você pode fechar esta janela e voltar para o terminal.</p>
</body>
</html>
`);
} else if (url.pathname === '/auth/error') {
const error = url.searchParams.get('error') || 'Erro desconhecido';
this.authResult = {
success: false,
error: decodeURIComponent(error)
};
// Resposta de erro para o navegador
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(`
<html>
<head><title>API Stats Logger - Erro</title></head>
<body style="font-family: Arial; text-align: center; padding: 50px;">
<h2>❌ Erro na autenticação</h2>
<p>${error}</p>
<p>Você pode fechar esta janela e voltar para o terminal.</p>
</body>
</html>
`);
} else {
res.writeHead(404);
res.end('Not Found');
}
});
this.localServer.listen(port);
return port;
}
stopLocalServer() {
if (this.localServer) {
this.localServer.close();
this.localServer = null;
}
}
async waitForAuthResult() {
return new Promise((resolve) => {
const checkResult = () => {
if (this.authResult) {
const result = this.authResult;
this.authResult = null;
resolve(result);
} else {
setTimeout(checkResult, 500);
}
};
// Timeout de 5 minutos
setTimeout(() => {
if (!this.authResult) {
resolve({
success: false,
error: 'Timeout: autenticação não foi completada em 5 minutos'
});
}
}, 300000);
checkResult();
});
}
async validateToken(token) {
try {
await axios.get(`${this.baseUrl}/auth/profile`, {
headers: { Authorization: `Bearer ${token}` },
timeout: 5000
});
return true;
} catch (error) {
return false;
}
}
async validateApiKey(apiKey) {
try {
await axios.post(`${this.baseUrl}/logs`, [{
timestamp: new Date().toISOString(),
level: 'info',
message: 'API key validation test',
service: 'cli-validation',
environment: 'test'
}], {
headers: {
'x-api-key': apiKey,
'Content-Type': 'application/json'
},
timeout: 5000
});
return true;
} catch (error) {
return false;
}
}
async getStoredAuthData() {
try {
return await this.secureStorage.getCredentials();
} catch (error) {
console.warn('⚠️ Erro ao recuperar credenciais:', error.message);
return null;
}
}
async storeAuthData(token, deviceId) {
try {
this.authToken = token;
this.deviceId = deviceId;
const success = await this.secureStorage.setCredentials(token, deviceId);
if (success) {
console.log('✅ Credenciais armazenadas de forma segura');
} else {
console.warn('⚠️ Não foi possível salvar credenciais de forma segura');
}
} catch (error) {
console.warn('⚠️ Erro ao salvar credenciais:', error.message);
}
}
async clearStoredAuthData() {
try {
await this.secureStorage.clearCredentials();
this.authToken = null;
this.deviceId = null;
} catch (error) {
console.warn('⚠️ Erro ao limpar credenciais:', error.message);
}
}
// Métodos de compatibilidade (mantidos para não quebrar código existente)
getStoredToken() {
const authData = this.getStoredAuthData();
return authData ? authData.token : null;
}
storeToken(token) {
this.storeAuthData(token, this.deviceId);
}
clearStoredToken() {
this.clearStoredAuthData();
}
async collectConfig() {
console.log('📋 Configuração do Projeto\n');
const config = {};
// Verificar se deve usar API key existente
if (!this.authToken) {
const useExisting = await this.questionBoolean('Usar API key existente? (caso contrário será criada uma nova)', false);
if (useExisting) {
config.existingKey = await this.question('API Key: ');
// Validar API key imediatamente
const isValid = await this.validateApiKey(config.existingKey);
if (!isValid) {
throw new Error('API key inválida');
}
console.log('✅ API key validada!');
return config;
}
}
// Configurações do projeto (para criação automática)
config.service = await this.question('Nome do serviço/projeto: ', 'my-api');
config.url = await this.question('URL base da API (ex: https://api.exemplo.com): ', 'https://api.example.com');
config.description = await this.question('Descrição (opcional): ', `API logs para ${config.service}`);
config.environment = await this.question('Ambiente [development/staging/production]: ', 'development');
// Detecção automática de framework
console.log('\n🔍 Detectando framework...');
const detectedFramework = this.detectFramework();
if (detectedFramework) {
console.log(`✅ Framework detectado: ${detectedFramework}`);
const useDetected = await this.questionBoolean(`Usar ${detectedFramework}?`, true);
config.framework = useDetected ? detectedFramework : await this.selectFramework();
} else {
console.log('❓ Framework não detectado automaticamente');
config.framework = await this.selectFramework();
}
// Configurações avançadas
console.log('\n⚙️ Configurações Avançadas:');
config.autoInstrument = await this.questionBoolean('Habilitar auto-instrumentação?', true);
config.captureBody = await this.questionBoolean('Capturar body das requisições?', false);
config.captureHeaders = await this.questionBoolean('Capturar headers das requisições?', true);
console.log('\n✅ Configuração coletada!');
return config;
}
async createProjectAndApiKey(config) {
try {
console.log('📦 Criando projeto...');
// Headers com autenticação
const headers = {
'Content-Type': 'application/json',
'User-Agent': 'API-Stats-CLI/1.0'
};
if (this.authToken) {
headers['Authorization'] = `Bearer ${this.authToken}`;
if (this.deviceId) {
headers['x-device-id'] = this.deviceId;
}
}
// 1. Criar projeto (agora requer autenticação)
const projectResponse = await axios.post(`${this.baseUrl}/projects`, {
name: config.service,
url: config.url,
description: config.description || `Projeto para ${config.service}`
}, {
timeout: 10000,
headers
});
const projectId = projectResponse.data._id || projectResponse.data.id;
console.log(`✅ Projeto criado: ${projectId}`);
// 2. Gerar API key
console.log('🔑 Gerando API key...');
const apiKeyResponse = await axios.post(`${this.baseUrl}/api-keys/generate`, {
projectId,
description: `API key para ${config.service} - Gerada pelo CLI`
}, {
timeout: 10000,
headers
});
const apiKey = apiKeyResponse.data.key;
console.log(`✅ API key gerada: ${apiKey.substring(0, 8)}...`);
return { projectId, apiKey };
} catch (error) {
console.error('❌ Erro ao criar projeto/API key:', error.response?.data || error.message);
if (error.response?.status === 401) {
throw new Error('Não autorizado. Faça login novamente.');
}
if (error.response?.status === 403) {
throw new Error('Acesso negado. Verifique suas permissões.');
}
if (error.response?.status === 404) {
throw new Error(`
Servidor API Stats não encontrado em ${this.baseUrl}
💡 Soluções:
1. Verifique se o servidor está rodando
2. Configure a URL correta: export API_STATS_URL=http://seu-servidor:porta
`.trim());
}
throw new Error('Falha ao criar projeto automaticamente.');
}
}
async setupProject(config) {
console.log('\n⚙️ Configurando projeto...');
// 1. Criar arquivo .env
await this.createEnvFile(config);
// 2. Criar exemplo de integração baseado no framework
await this.createIntegrationExample(config);
// 3. Criar arquivo de configuração
await this.createConfigFile(config);
// 4. Atualizar package.json se existir
await this.updatePackageJson();
// 5. Criar README com instruções
await this.createSetupInstructions(config);
}
async createEnvFile(config) {
const envContent = `# API Stats Logger Configuration
API_STATS_ENABLED=true
API_STATS_API_KEY=${config.apiKey}
API_STATS_URL=${this.baseUrl}/logs
API_STATS_SERVICE=${config.service}
API_STATS_ENVIRONMENT=${config.environment}
API_STATS_BATCH_SIZE=10
API_STATS_FLUSH_INTERVAL=2000
# Optional: Capture settings
API_STATS_CAPTURE_BODY=${config.captureBody}
API_STATS_CAPTURE_HEADERS=${config.captureHeaders}
# Project Info (for reference)
API_STATS_PROJECT_ID=${config.projectId || 'auto-generated'}
`;
const envPath = path.join(process.cwd(), '.env.api-stats');
fs.writeFileSync(envPath, envContent);
console.log('📝 Criado: .env.api-stats');
console.log(' Copie as variáveis para seu .env principal');
}
async createIntegrationExample(config) {
let example = '';
switch (config.framework) {
case 'express':
example = this.getExpressExample(config);
break;
case 'nestjs':
example = this.getNestJSExample(config);
break;
case 'fastify':
example = this.getFastifyExample(config);
break;
case 'koa':
example = this.getKoaExample(config);
break;
default:
example = this.getGenericExample(config);
}
const examplePath = path.join(process.cwd(), `api-stats-${config.framework}-example.js`);
fs.writeFileSync(examplePath, example);
console.log(`📝 Criado: api-stats-${config.framework}-example.js`);
}
getExpressExample(config) {
return `// Express + API Stats Logger Integration
const express = require('express');
const axios = require('axios');
const app = express();
// Configuração do logger
const API_STATS_CONFIG = {
apiKey: process.env.API_STATS_API_KEY || '${config.apiKey}',
baseUrl: process.env.API_STATS_URL || 'http://localhost:3000/logs',
service: '${config.service}',
environment: '${config.environment}',
captureBody: ${config.captureBody},
captureHeaders: ${config.captureHeaders}
};
// Logger simples para enviar dados para API Stats
class SimpleApiStatsLogger {
constructor(config) {
this.config = config;
this.queue = [];
this.flushInterval = setInterval(() => this.flush(), 2000);
}
log(level, message, metadata = {}) {
const logEntry = {
timestamp: new Date().toISOString(),
level,
message,
service: this.config.service,
environment: this.config.environment,
metadata: {
...metadata,
framework: 'express'
}
};
this.queue.push(logEntry);
console.log(\`[\${level.toUpperCase()}] \${message}\`, metadata);
// Flush imediato para logs de erro
if (level === 'error') {
this.flush();
}
}
info(message, metadata) { this.log('info', message, metadata); }
warn(message, metadata) { this.log('warn', message, metadata); }
error(message, metadata) { this.log('error', message, metadata); }
async flush() {
if (this.queue.length === 0) return;
const logs = [...this.queue];
this.queue = [];
try {
await axios.post(this.config.baseUrl, logs, {
headers: {
'x-api-key': this.config.apiKey,
'Content-Type': 'application/json'
},
timeout: 5000
});
} catch (error) {
console.warn('Falha ao enviar logs:', error.message);
// Re-adicionar logs na fila em caso de erro
this.queue.unshift(...logs);
}
}
async close() {
clearInterval(this.flushInterval);
await this.flush();
}
}
// Inicializar logger
const logger = new SimpleApiStatsLogger(API_STATS_CONFIG);
// Middleware de logging automático
app.use((req, res, next) => {
const startTime = Date.now();
const requestId = \`req_\${Date.now()}_\${Math.random().toString(36).substr(2, 9)}\`;
req.requestId = requestId;
// Log da requisição
const requestData = {
requestId,
method: req.method,
url: req.url,
ip: req.ip || req.connection.remoteAddress
};
if (API_STATS_CONFIG.captureHeaders) {
requestData.headers = req.headers;
}
logger.info('Request started', requestData);
// Interceptar resposta
const originalSend = res.send;
res.send = function(data) {
const duration = Date.now() - startTime;
const level = res.statusCode >= 500 ? 'error' :
res.statusCode >= 400 ? 'warn' : 'info';
const responseData = {
requestId,
statusCode: res.statusCode,
duration,
ip: req.ip || req.connection.remoteAddress
};
if (API_STATS_CONFIG.captureBody && data) {
responseData.responseBody = data.toString().substring(0, 1000); // Limitar tamanho
}
logger.log(level, \`\${req.method} \${req.url} \${res.statusCode}\`, responseData);
originalSend.call(this, data);
};
next();
});
// Middleware padrão
app.use(express.json());
// Exemplo de rota com logging manual
app.get('/users/:id', async (req, res) => {
const { id } = req.params;
logger.info('Buscando usuário', { userId: id, requestId: req.requestId });
try {
// Simular busca no banco de dados
await new Promise(resolve => setTimeout(resolve, 100));
const user = { id, name: 'João', email: 'joao@exemplo.com' };
logger.info('Usuário encontrado', {
userId: id,
userName: user.name,
requestId: req.requestId
});
res.json(user);
} catch (error) {
logger.error('Erro ao buscar usuário', {
userId: id,
error: error.message,
requestId: req.requestId
});
res.status(500).json({ error: 'Internal server error' });
}
});
// Health check (sem logging para evitar spam)
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// Rota de teste para gerar diferentes tipos de log
app.get('/test-logs', (req, res) => {
logger.info('Teste de log info', { test: true });
logger.warn('Teste de log warning', { test: true });
logger.error('Teste de log error', { test: true });
res.json({ message: 'Logs de teste enviados' });
});
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
logger.info('Servidor iniciado', { port: PORT, framework: 'express' });
console.log(\`🚀 Server running on port \${PORT}\`);
console.log(\`📊 Logs sendo enviados para: \${API_STATS_CONFIG.baseUrl}\`);
});
// Graceful shutdown
process.on('SIGINT', async () => {
console.log('\\nShutting down...');
await logger.close();
process.exit(0);
});
process.on('SIGTERM', async () => {
console.log('\\nShutting down...');
await logger.close();
process.exit(0);
});
`;
}
getNestJSExample(config) {
return `// NestJS + API Stats Logger Integration
// Add this to your main.ts file
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
// Para desenvolvimento local:
const ApiStatsLogger = require('./node_modules/api-stats-logger');
// Para produção após npm install:
// import { ApiStatsLogger } from 'api-stats-logger';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Configurar API Stats Logger
const logger = new ApiStatsLogger({
service: '${config.service}',
environment: '${config.environment}'
});
// Adicionar middleware
app.use(ApiStatsLogger.nestMiddleware({
logger,
captureBody: ${config.captureBody},
captureHeaders: ${config.captureHeaders},
skipRoutes: ['/health', '/metrics']
}));
await app.listen(3000);
logger.info('NestJS application started', {
port: 3000,
framework: 'nestjs'
});
}
bootstrap();
// Exemplo de uso em um controller:
/*
import { Controller, Get, Logger } from '@nestjs/common';
const ApiStatsLogger = require('./node_modules/api-stats-logger');
@Controller('users')
export class UsersController {
private readonly logger = new ApiStatsLogger({
service: 'users-service'
});
@Get()
findAll() {
this.logger.info('Listing all users');
try {
// Sua lógica aqui
const users = [];
this.logger.info('Users retrieved successfully', { count: users.length });
return users;
} catch (error) {
this.logger.error('Error retrieving users', { error: error.message });
throw error;
}
}
}
*/
`;
}
getFastifyExample(config) {
return `// Fastify + API Stats Logger Integration
const fastify = require('fastify')({ logger: false });
const ApiStatsLogger = require('./node_modules/api-stats-logger');
// Configurar API Stats Logger
const logger = new ApiStatsLogger({
service: '${config.service}',
environment: '${config.environment}'
});
// Hooks para logging automático
fastify.addHook('onRequest', async (request, reply) => {
request.startTime = Date.now();
request.requestId = \`req_\${Date.now()}_\${Math.random().toString(36).substr(2, 9)}\`;
logger.info('Request started', {
requestId: request.requestId,
method: request.method,
url: request.url,
ip: request.ip
});
});
fastify.addHook('onResponse', async (request, reply) => {
const duration = Date.now() - request.startTime;
const level = reply.statusCode >= 500 ? 'error' :
reply.statusCode >= 400 ? 'warn' : 'info';
logger.log({
level,
message: \`\${request.method} \${request.url} \${reply.statusCode}\`,
metadata: {
requestId: request.requestId,
statusCode: reply.statusCode,
duration,
ip: request.ip
}
});
});
// Rotas
fastify.get('/users/:id', async (request, reply) => {
const { id } = request.params;
logger.info('Getting user', { userId: id });
try {
// Sua lógica aqui
const user = { id, name: 'João' };
logger.info('User found', { userId: id });
return user;
} catch (error) {
logger.error('Error getting user', { userId: id, error: error.message });
reply.status(500);
return { error: 'Internal server error' };
}
});
fastify.get('/health', async (request, reply) => {
return { status: 'ok', timestamp: new Date().toISOString() };
});
// Start server
const start = async () => {
try {
await fastify.listen({ port: 3000 });
logger.info('Server started', { port: 3000, framework: 'fastify' });
console.log('🚀 Server running on port 3000');
} catch (err) {
logger.error('Error starting server', { error: err.message });
process.exit(1);
}
};
start();
// Graceful shutdown
process.on('SIGINT', async () => {
await logger.close();
await fastify.close();
process.exit(0);
});
`;
}
getKoaExample(config) {
return `// Koa + API Stats Logger Integration
const Koa = require('koa');
const Router = require('@koa/router');
const bodyParser = require('koa-bodyparser');
const ApiStatsLogger = require('./node_modules/api-stats-logger');
const app = new Koa();
const router = new Router();
// Configurar logger
const logger = new ApiStatsLogger({
service: '${config.service}',
environment: '${config.environment}'
});
// Middleware de logging
app.use(async (ctx, next) => {
const startTime = Date.now();
const requestId = \`koa_\${Date.now()}_\${Math.random().toString(36).substr(2, 9)}\`;
ctx.requestId = requestId;
logger.info('Request started', {
requestId,
method: ctx.method,
url: ctx.url,
ip: ctx.ip
});
try {
await next();
} catch (error) {
logger.error('Request error', {
requestId,
error: error.message,
stack: error.stack
});
throw error;
}
const duration = Date.now() - startTime;
const level = ctx.status >= 500 ? 'error' :
ctx.status >= 400 ? 'warn' : 'info';
logger.log({
level,
message: \`\${ctx.method} \${ctx.url} \${ctx.status}\`,
metadata: {
requestId,
statusCode: ctx.status,
duration,
ip: ctx.ip
}
});
});
app.use(bodyParser());
// Rotas
router.get('/users/:id', async (ctx) => {
const { id } = ctx.params;
logger.info('Getting user', { userId: id });
try {
// Sua lógica aqui
const user = { id, name: 'João' };
logger.info('User found', { userId: id });
ctx.body = user;
} catch (error) {
logger.error('Error getting user', { userId: id, error: error.message });
ctx.status = 500;
ctx.body = { error: 'Internal server error' };
}
});
router.get('/health', async (ctx) => {
ctx.body = { status: 'ok', timestamp: new Date().toISOString() };
});
app.use(router.routes());
app.use(router.allowedMethods());
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
logger.info('Server started', { port: PORT, framework: 'koa' });
console.log(\`🚀 Server running on port \${PORT}\`);
});
// Graceful shutdown
process.on('SIGINT', async () => {
await logger.close();
process.exit(0);
});
`;
}
getGenericExample(config) {
return `// Generic API Stats Logger Integration
const axios = require('axios');
// Configuração do logger
const API_STATS_CONFIG = {
apiKey: process.env.API_STATS_API_KEY || '${config.apiKey}',
baseUrl: process.env.API_STATS_URL || 'http://localhost:3000/logs',
service: '${config.service}',
environment: '${config.environment}'
};
// Logger simples para enviar dados para API Stats
class SimpleApiStatsLogger {
constructor(config) {
this.config = config;
this.queue = [];
this.flushInterval = setInterval(() => this.flush(), 2000);
}
log(level, message, metadata = {}) {
const logEntry = {
timestamp: new Date().toISOString(),
level,
message,
service: this.config.service,
environment: this.config.environment,
metadata: {
...metadata,
framework: '${config.framework}'
}
};
this.queue.push(logEntry);
console.log(\`[\${level.toUpperCase()}] \${message}\`, metadata);
// Flush imediato para logs de erro
if (level === 'error') {
this.flush();
}
}
info(message, metadata) { this.log('info', message, metadata); }
warn(message, metadata) { this.log('warn', message, metadata); }
error(message, metadata) { this.log('error', message, metadata); }
async flush() {
if (this.queue.length === 0) return;
const logs = [...this.queue];
this.queue = [];
try {
await axios.post(this.config.baseUrl, logs, {
headers: {
'x-api-key': this.config.apiKey,
'Content-Type': 'application/json'
},
timeout: 5000
});
console.log(\`✅ Enviados \${logs.length} logs para API Stats\`);
} catch (error) {
console.warn('⚠️ Falha ao enviar logs:', error.message);
// Re-adicionar logs na fila em caso de erro
this.queue.unshift(...logs);
}
}
async close() {
clearInterval(this.flushInterval);
await this.flush();
console.log('📊 Logger finalizado');
}
}
// Configuração básica
const logger = new SimpleApiStatsLogger(API_STATS_CONFIG);
// Exemplos de uso
logger.info('Application started', {
version: '1.0.0',
framework: '${config.framework}'
});
// Exemplo de logging de operação
async function processUser(userId) {
const startTime = Date.now();
const operationId = \`op_\${Date.now()}_\${Math.random().toString(36).substr(2, 9)}\`;
logger.info('Processing user', { userId, operationId });
try {
// Simular processamento
await new Promise(resolve => setTimeout(resolve, Math.random() * 500));
const duration = Date.now() - startTime;
logger.info('User processed successfully', { userId, operationId, duration });
return { id: userId, processed: true, duration };
} catch (error) {
const duration = Date.now() - startTime;
logger.error('Error processing user', {
userId,
operationId,
error: error.message,
duration
});
throw error;
}
}
// Exemplo de monitoramento de performance
async function monitorOperation(name, operation) {
const startTime = Date.now();
const operationId = \`\${name}_\${Date.now()}\`;
logger.info(\`Starting \${name}\`, { operationId });
try {
const result = await operation();
const duration = Date.now() - startTime;
logger.info(\`Completed \${name}\`, {
operationId,
duration,
success: true
});
return result;
} catch (error) {
const duration = Date.now() - startTime;
logger.error(\`Failed \${name}\`, {
operationId,
duration,
error: error.message,
success: false
});
throw error;
}
}
// Teste das funções
async function runTests() {
try {
// Teste 1: Processamento de usuário
await processUser('123');
await processUser('456');
// Teste 2: Operação monitorada
await monitorOperation('database-query', async () => {
await new Promise(resolve => setTimeout(resolve, 200));
return { rows: 10 };
});
// Teste 3: Operação que falha
try {
await monitorOperation('failing-operation', async () => {
throw new Error('Simulated failure');
});
} catch (error) {
// Erro esperado
}
logger.info('All tests completed');
} catch (error) {
logger.error('Test failed', { error: error.message });
}
}
// Executar testes
runTests();
// Graceful shutdown
process.on('SIGINT', async () => {
console.log('\\nShutting down...');
await logger.close();
process.exit(0);
});
process.on('SIGTERM', async () => {
console.log('\\nShutting down...');
await logger.close();
process.exit(0);
});
// Exemplo de monitoramento contínuo
setInterval(() => {
logger.info('Heartbeat', {
timestamp: new Date().toISOString(),
memory: process.memoryUsage(),
uptime: process.uptime()
});
}, 30000); // A cada 30 segundos
`;
}
async createConfigFile(config) {
const configContent = `// API Stats Logger Configuration
module.exports = {
// Basic settings
service: '${config.service}',
environment: '${config.environment}',
framework: '${config.framework}',
// Features
autoInstrument: ${config.autoInstrument},
captureBody: ${config.captureBody},
captureHeaders: ${config.captureHeaders},
// Performance settings
batchSize: 10,
flushInterval: 2000,
maxRetries: 3,
// Skip patterns
skipPaths: ['/health', '/metrics', '/favicon.ico'],
skipMethods: ['OPTIONS'],
// Security
maxBodySize: 1024 * 10, // 10KB
sensitiveHeaders: ['authorization', 'cookie', 'x-api-key']
};
`;
const configPath = path.join(process.cwd(), 'api-stats.config.js');
fs.writeFileSync(configPath, configContent);
console.log('📝 Criado: api-stats.config.js');
}
async updatePackageJson() {
const packagePath = path.join(process.cwd(), 'package.json');
if (fs.existsSync(packagePath)) {
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
if (!packageJson.dependencies) packageJson.dependencies = {};
// Adicionar axios se não existir (necessário para enviar logs)
if (!packageJson.dependencies['axios']) {
packageJson.dependencies['axios'] = '^1.6.0';
}
if (!packageJson.scripts) packageJson.scripts = {};
if (!packageJson.scripts['logs:test']) {
packageJson.scripts['logs:test'] = 'node api-stats-*-example.js';
}
fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
console.log('📝 Atualizado: package.json');
}
}
async createSetupInstructions(config) {
const instructions = `# API Stats Logger - Setup Completo
## ✅ Configuração realizada:
- **Framework**: ${config.framework}
- **Serviço**: ${config.service}
- **Ambiente**: ${config.environment}
- **Auto-instrumentação**: ${config.autoInstrument ? 'Ativada' : 'Desativada'}
- **Captura de body**: ${config.captureBody ? 'Ativada' : 'Desativada'}
- **Captura de headers**: ${config.captureHeaders ? 'Ativada' : 'Desativada'}
## 📋 Próximos passos:
### 1. Instalar dependências
\`\`\`bash
npm install api-stats-logger
# ou
yarn add api-stats-logger
\`\`\`
### 2. Configurar variáveis de ambiente
Copie as variáveis do arquivo \`.env.api-stats\` para seu \`.env\` principal:
\`\`\`env
API_STATS_API_KEY=${config.apiKey}
API_STATS_SERVICE=${config.service}
API_STATS_ENVIRONMENT=${config.environment}
\`\`\`
### 3. Integrar no seu código
Veja o arquivo de exemplo: \`api-stats-${config.framework}-example.js\`
### 4. Verificar logs
- Execute sua aplicação
- Faça algumas requisições
- Verifique os logs no dashboard: http://localhost:3000
## 🔧 Configurações avançadas
Edite o arquivo \`api-stats.config.js\` para personalizar:
- Paths para ignorar
- Tamanho máximo do body
- Intervals de flush
- Headers sensíveis
## 📊 Monitoramento
Verifique estatísticas do logger:
\`\`\`bash
npm run logs:stats
\`\`\`
## 🆘 Suporte
- Documentação: [Link para docs]
- GitHub Issues: [Link para issues]
- Discord: [Link para Discord]
---
Gerado pelo API Stats CLI em ${new Date().toISOString()}
`;
const readmePath = path.join(process.cwd(), 'API-STATS-SETUP.md');
fs.writeFileSync(readmePath, instructions);
console.log('📝 Criado: API-STATS-SETUP.md');
}
question(query, defaultValue = '', hideInput = false) {
return new Promise((resolve) => {
if (hideInput) {
// Implementação mais robusta para esconder senha
const readline = require('readline');
// Fechar readline temporariamente para evitar conflitos
if (this.rl) {
this.rl.pause();
}
process.stdout.write(query);
let password = '';
let muted = false;
// Função para esconder o cursor
const hideCursor = () => {
process.stdout.write('\x1B[?25l');
};
// Função para mostrar o cursor
const showCursor = () => {
process.stdout.write('\x1B[?25h');
};
const cleanup = () => {
showCursor();
if (process.stdin.setRawMode) {
process.stdin.setRawMode(false);
}
process.stdin.removeAllListeners('data');
process.stdin.pause();
// Recriar readline após input da senha
if (this.rl) {
this.rl.close();
}
this.rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
};
const onData = (buffer) => {
const char = buffer.toString();
const byte = char.charCodeAt(0);
if (byte === 13 || byte === 10 || byte === 4) { // Enter ou Ctrl+D
cleanup();
console.log(); // Nova linha
resolve(password || defaultValue);
return;
}
if (byte === 3) { // Ctrl+C
cleanup();
console.log('\n\n❌ Operação cancelada');
process.exit(1);
return;
}
if (byte === 127 || byte === 8) { // Backspace
if (password.length > 0) {
password = password.slice(0, -1);
process.stdout.write('\b \b');
}
return;
}
// Apenas caracteres visíveis ASCII
if (byte >= 32 && byte <= 126) {
password += char;
process.stdout.write('*');
}
};
// Configurar modo raw se disponível
try {
if (process.stdin.setRawMode) {
process.stdin.setRawMode(true);
}
hideCursor();
muted = true;
} catch (err) {
// Se não conseguir usar raw mode, usar método alternativo
console.log('\n⚠️ Modo seguro de senha não disponível neste terminal');
}
process.stdin.resume();
process.stdin.setEncoding('utf8');
process.stdin.on('data', onData);
// Timeout de segurança para evitar travamento
const timeout = setTimeout(() => {
cleanup();
console.log('\n\n⏰ Timeout - tente novamente');
resolve(defaultValue);
}, 120000); // 2 minutos
// Limpar timeout quando resolver
const originalResolve = resolve;
resolve = (value) => {
clearTimeout(timeout);
originalResolve(value);
};
} else {
// Input normal usando readline existente
this.rl.question(query, (answer) => {
resolve(answer.trim() || defaultValue);
});
}
});
}
questionBoolean(query, defaultValue = false) {
return new Promise((resolve) => {
// Adicionar exemplo de resposta baseado no valor padrão
const example = defaultValue ? '[S/n]' : '[s/N]';
const fullQuery = `${query} ${example}: `;
this.rl.question(fullQuery, (answer) => {
const input = answer.trim().toLowerCase();
if (input === 'y' || input === 'yes' || input === 'sim' || input === 's') {
resolve(true);
} else if (input === 'n' || input === 'no' || input === 'não') {
resolve(false);
} else if (input === '') {
// Se usuário apenas der Enter, usar valor padrão
resolve(defaultValue);
} else {
// Para entrada inválida, usar valor padrão
resolve(defaultValue);
}
});
});
}
detectFramework() {
const fs = require('fs');
const path = require('path');
try {
const packageJsonPath = path.join(process.cwd(), 'package.json');
if (fs.existsSync(packageJsonPath)) {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
if (deps['@nestjs/core'] || deps['@nestjs/common']) return 'nestjs';
if (deps['express']) return 'express';
if (deps['fastify']) return 'fastify';
if (deps['koa']) return 'koa';
}
} catch (error) {
// Ignorar erros de leitura
}
return null;
}
async selectFramework() {
console.log('\nFrameworks disponíveis:');
console.log('1. Express');
console.log('2. NestJS');
console.log('3. Fastify');
console.log('4. Koa');
console.log('5. Outro/Generic');
const choice = await this.question('Escolha o framework [1-5]: ', '1');
switch (choice) {
case '1': return 'express';
case '2': return 'nestjs';
case '3': return 'fastify';
case '4': return 'koa';
case '5': return 'generic';
default: return 'express';
}
}
// Método para limpar recursos de forma segura
async cleanup() {
try {
if (this.rl) {
this.rl.close();
this.rl = null;
}
// Restaurar configurações do terminal
if (process.stdin.setRawMode) {
process.stdin.setRawMode(false);
}
// Mostrar cursor se estiver escondido
process.stdout.write('\x1B[?25h');
// Pequeno delay para permitir limpeza
await new Promise(resolve => setTimeout(resolve, 100));
} catch (error) {
// Ignorar erros de limpeza
}
}
}
// Se executado diretamente
if (require.main === module) {
const cli = new ApiStatsCLI();
cli.run().catch(console.error);
}
module.exports = ApiStatsCLI;
} // Fechamento do bloco else do useModernCLI