UNPKG

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
#!/usr/bin/env node // 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