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

380 lines (327 loc) 10.9 kB
const middleware = require('./middleware'); class AutoInstrument { static setup(logger, options = {}) { const { detectFramework = true, captureHTTP = true, captureDatabase = true, captureErrors = true } = options; if (detectFramework && captureHTTP) { this._detectAndInstrumentFramework(logger, options); } if (captureDatabase) { this._instrumentDatabase(logger, options); } if (captureErrors) { this._instrumentErrorHandling(logger, options); } } static _detectAndInstrumentFramework(logger, options) { // Detectar Express if (this._isExpressAvailable()) { this._instrumentExpress(logger, options); } // Detectar NestJS if (this._isNestJSAvailable()) { this._instrumentNestJS(logger, options); } // Detectar Fastify if (this._isFastifyAvailable()) { this._instrumentFastify(logger, options); } // Detectar Koa if (this._isKoaAvailable()) { this._instrumentKoa(logger, options); } } static _isExpressAvailable() { try { const express = require.cache[require.resolve('express')]; return !!express; } catch (e) { return false; } } static _isNestJSAvailable() { try { const nest = require.cache[require.resolve('@nestjs/core')]; return !!nest; } catch (e) { return false; } } static _isFastifyAvailable() { try { const fastify = require.cache[require.resolve('fastify')]; return !!fastify; } catch (e) { return false; } } static _isKoaAvailable() { try { const koa = require.cache[require.resolve('koa')]; return !!koa; } catch (e) { return false; } } static _instrumentExpress(logger, options) { try { const express = require('express'); const originalApplication = express.application.use; // Interceptar o método use() do Express para adicionar middleware automaticamente express.application.use = function(...args) { // Se for a primeira chamada, adicionar nosso middleware if (!this._apiStatsInstrumented) { this._apiStatsInstrumented = true; logger.info('Auto-instrumenting Express application', { type: 'auto_instrument', framework: 'express' }); originalApplication.call(this, middleware.express({ logger, ...options })); } return originalApplication.apply(this, args); }; logger.info('Express auto-instrumentation setup completed', { type: 'auto_instrument', framework: 'express', status: 'ready' }); } catch (error) { logger.error('Failed to instrument Express', { type: 'auto_instrument_error', framework: 'express', error: error.message }); } } static _instrumentNestJS(logger, options) { try { // Para NestJS, é mais complexo fazer auto-instrumentação // Vamos apenas logar que está disponível e dar instruções logger.info('NestJS detected - manual middleware setup recommended', { type: 'auto_instrument', framework: 'nestjs', instructions: 'Use ApiStatsLogger.nestMiddleware() in your main.ts file' }); } catch (error) { logger.error('Failed to setup NestJS instrumentation', { type: 'auto_instrument_error', framework: 'nestjs', error: error.message }); } } static _instrumentFastify(logger, options) { try { const fastify = require('fastify'); const originalFastify = fastify; // Interceptar criação de instâncias Fastify const instrumentedFastify = function(...args) { const instance = originalFastify(...args); // Adicionar hook de request/response instance.addHook('onRequest', async (request, reply) => { request.startTime = Date.now(); request.requestId = `fastify_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; }); instance.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: `Fastify ${request.method} ${request.url} ${reply.statusCode}`, metadata: { type: 'http_response', framework: 'fastify', requestId: request.requestId, method: request.method, url: request.url, statusCode: reply.statusCode, duration, ip: request.ip } }); }); logger.info('Fastify instance auto-instrumented', { type: 'auto_instrument', framework: 'fastify' }); return instance; }; // Copiar propriedades do fastify original Object.setPrototypeOf(instrumentedFastify, originalFastify); Object.assign(instrumentedFastify, originalFastify); module.exports = instrumentedFastify; } catch (error) { logger.error('Failed to instrument Fastify', { type: 'auto_instrument_error', framework: 'fastify', error: error.message }); } } static _instrumentKoa(logger, options) { try { const Koa = require('koa'); const originalUse = Koa.prototype.use; Koa.prototype.use = function(fn) { // Se for a primeira middleware, adicionar instrumentação if (!this._apiStatsInstrumented) { this._apiStatsInstrumented = true; const instrumentationMiddleware = async (ctx, next) => { const startTime = Date.now(); const requestId = `koa_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; ctx.requestId = requestId; logger.info('Koa Request Started', { type: 'http_request', framework: 'koa', requestId, method: ctx.method, url: ctx.url, ip: ctx.ip }); try { await next(); } catch (error) { logger.error('Koa Request Error', { type: 'http_error', framework: 'koa', 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: `Koa ${ctx.method} ${ctx.url} ${ctx.status}`, metadata: { type: 'http_response', framework: 'koa', requestId, method: ctx.method, url: ctx.url, statusCode: ctx.status, duration, ip: ctx.ip } }); }; originalUse.call(this, instrumentationMiddleware); logger.info('Koa application auto-instrumented', { type: 'auto_instrument', framework: 'koa' }); } return originalUse.call(this, fn); }; } catch (error) { logger.error('Failed to instrument Koa', { type: 'auto_instrument_error', framework: 'koa', error: error.message }); } } static _instrumentDatabase(logger, options) { // MongoDB/Mongoose try { const mongoose = require('mongoose'); if (mongoose) { const dbMiddleware = middleware.database({ logger, ...options }); dbMiddleware.mongoose(); logger.info('MongoDB/Mongoose auto-instrumented', { type: 'auto_instrument', database: 'mongodb' }); } } catch (e) { // Mongoose não está disponível } // PostgreSQL (pg) try { const pg = require('pg'); if (pg && pg.Client) { const originalQuery = pg.Client.prototype.query; pg.Client.prototype.query = function(text, params, callback) { const startTime = Date.now(); const queryId = `query_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`; logger.info('Database Query Started', { type: 'database_query', database: 'postgresql', queryId, query: typeof text === 'string' ? text.substring(0, 200) : '[COMPLEX_QUERY]' }); const originalCallback = callback || (() => {}); const instrumentedCallback = (err, result) => { const duration = Date.now() - startTime; if (err) { logger.error('Database Query Error', { type: 'database_error', database: 'postgresql', queryId, error: err.message, duration }); } else { logger.info('Database Query Completed', { type: 'database_response', database: 'postgresql', queryId, rowCount: result?.rowCount || 0, duration }); } originalCallback(err, result); }; if (typeof params === 'function') { return originalQuery.call(this, text, instrumentedCallback); } else { return originalQuery.call(this, text, params, instrumentedCallback); } }; logger.info('PostgreSQL (pg) auto-instrumented', { type: 'auto_instrument', database: 'postgresql' }); } } catch (e) { // pg não está disponível } } static _instrumentErrorHandling(logger, options) { // Instrumentar console.error para capturar logs de erro const originalError = console.error; console.error = function(...args) { logger.error('Console Error', { type: 'console_error', message: args.join(' '), stack: new Error().stack }); return originalError.apply(console, args); }; // Instrumentar console.warn const originalWarn = console.warn; console.warn = function(...args) { logger.warn('Console Warning', { type: 'console_warn', message: args.join(' ') }); return originalWarn.apply(console, args); }; logger.info('Error handling auto-instrumented', { type: 'auto_instrument', feature: 'error_handling' }); } } module.exports = AutoInstrument;