logs-interceptor
Version:
High-performance, production-ready log interceptor for Node.js applications with Loki integration. Built with Clean Architecture principles. Supports Node.js, Browser, and Node-RED.
179 lines • 6.59 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LogService = void 0;
const LogLevel_1 = require("../../domain/value-objects/LogLevel");
const LogEntry_1 = require("../../domain/entities/LogEntry");
const crypto = __importStar(require("crypto"));
const os = __importStar(require("os"));
class LogService {
constructor(filter, buffer, transport, contextProvider, config) {
this.filter = filter;
this.buffer = buffer;
this.transport = transport;
this.contextProvider = contextProvider;
this.config = config;
this.startTime = Date.now();
this.metrics = this.initializeMetrics();
}
initializeMetrics() {
return {
logsProcessed: 0,
logsDropped: 0,
logsSanitized: 0,
flushCount: 0,
errorCount: 0,
bufferSize: 0,
avgFlushTime: 0,
lastFlushTime: 0,
memoryUsage: 0,
cpuUsage: 0,
circuitBreakerTrips: 0,
};
}
debug(message, context) {
this.log('debug', message, context);
}
info(message, context) {
this.log('info', message, context);
}
warn(message, context) {
this.log('warn', message, context);
}
error(message, context) {
this.log('error', message, context);
}
fatal(message, context) {
this.log('fatal', message, context);
// Force immediate flush for fatal errors
this.flush().catch(() => {
// Silent fail for fatal errors
});
}
log(level, message, context) {
if (!this.filter.isLevelEnabled(level)) {
return;
}
const entry = this.createLogEntry(level, message, context);
const filteredEntry = this.filter.filter(entry);
if (!this.filter.shouldProcess(filteredEntry)) {
this.metrics.logsDropped++;
return;
}
this.buffer.add(filteredEntry);
this.metrics.logsProcessed++;
this.updateMetrics();
}
trackEvent(eventName, properties) {
this.info(`[EVENT] ${eventName}`, properties);
}
createLogEntry(level, message, context) {
const levelVO = new LogLevel_1.LogLevelVO(level);
const asyncContext = this.contextProvider.getContext();
const logId = crypto.randomBytes(8).toString('hex');
// Compute dynamic labels
const dynamicLabels = Object.entries(this.config.dynamicLabels).reduce((acc, [key, fn]) => {
try {
acc[key] = String(fn());
}
catch (error) {
acc[key] = 'error';
}
return acc;
}, {});
return new LogEntry_1.LogEntryEntity(logId, new Date().toISOString(), level, message, {
...asyncContext,
...context,
}, dynamicLabels.trace_id, dynamicLabels.span_id, dynamicLabels.request_id, {
app: this.config.appName,
version: this.config.version,
environment: this.config.environment,
level: level,
hostname: os.hostname(),
pid: String(process.pid),
...this.config.labels,
...dynamicLabels,
}, {
memoryUsage: this.metrics.memoryUsage,
cpuUsage: this.metrics.cpuUsage,
});
}
async flush() {
if (this.buffer.size() === 0) {
return;
}
const entries = this.buffer.flush();
const startTime = Date.now();
try {
await this.transport.send(entries);
const flushTime = Date.now() - startTime;
this.metrics.flushCount++;
this.metrics.lastFlushTime = Date.now();
this.metrics.avgFlushTime =
(this.metrics.avgFlushTime * (this.metrics.flushCount - 1) + flushTime) /
this.metrics.flushCount;
this.updateMetrics();
}
catch (error) {
this.metrics.errorCount++;
// Re-add entries to buffer on failure
entries.forEach((entry) => this.buffer.add(entry));
throw error;
}
}
getMetrics() {
return { ...this.metrics, bufferSize: this.buffer.size() };
}
getHealth() {
const bufferMetrics = this.buffer.getMetrics();
const transportHealth = this.transport.getHealth();
return {
healthy: this.metrics.errorCount < 10 &&
Date.now() - this.metrics.lastFlushTime < 30000 &&
bufferMetrics.size / bufferMetrics.maxSize < 0.9 &&
transportHealth.healthy,
lastSuccessfulFlush: this.metrics.lastFlushTime,
consecutiveErrors: this.metrics.errorCount,
bufferUtilization: bufferMetrics.size / bufferMetrics.maxSize,
uptime: Date.now() - this.startTime,
memoryUsageMB: this.metrics.memoryUsage,
circuitBreakerState: transportHealth.healthy ? 'closed' : 'open',
};
}
async destroy() {
await this.flush();
await this.transport.destroy();
}
updateMetrics() {
if (!this.config.enableMetrics)
return;
const memUsage = process.memoryUsage();
this.metrics.memoryUsage = memUsage.heapUsed / 1024 / 1024;
this.metrics.cpuUsage = process.cpuUsage().user / 1000000;
this.metrics.bufferSize = this.buffer.size();
}
}
exports.LogService = LogService;
//# sourceMappingURL=LogService.js.map