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
JavaScript
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;