UNPKG

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.

153 lines 5.26 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.LokiTransport = void 0; const axios_1 = __importDefault(require("axios")); const perf_hooks_1 = require("perf_hooks"); class LokiTransport { constructor(config, circuitBreaker) { this.config = config; this.circuitBreaker = circuitBreaker; this.consecutiveFailures = 0; this.health = { healthy: true, consecutiveFailures: 0, }; this.setupHttpClient(); } setupHttpClient() { const headers = { 'Content-Type': 'application/json', 'X-Scope-OrgID': this.config.tenantId, 'User-Agent': 'logs-interceptor/2.0.0', }; if (this.config.authToken) { headers['Authorization'] = `Bearer ${this.config.authToken}`; } this.httpClient = axios_1.default.create({ baseURL: this.config.url, timeout: this.config.timeout ?? 5000, headers, maxContentLength: 100 * 1024 * 1024, maxBodyLength: 100 * 1024 * 1024, }); this.httpClient.interceptors.request.use((config) => { config.metadata = { startTime: perf_hooks_1.performance.now() }; return config; }); } async send(entries) { if (entries.length === 0) { return; } const operation = async () => { const payload = this.formatForLoki(entries); let data = JSON.stringify(payload); const headers = {}; if (this.config.compression) { const zlib = require('zlib'); data = zlib.gzipSync(data, { level: this.config.compressionLevel ?? 6, }); headers['Content-Encoding'] = 'gzip'; } await this.httpClient.post('', data, { headers }); }; if (this.circuitBreaker) { await this.circuitBreaker.execute(operation); } else { await this.retryOperation(operation); } this.recordSuccess(); } async retryOperation(operation, maxRetries = this.config.maxRetries ?? 3, delay = this.config.retryDelay ?? 1000) { let lastError; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await operation(); } catch (error) { lastError = error; if (attempt === maxRetries) { throw lastError; } const backoffDelay = delay * Math.pow(2, attempt - 1); const jitter = Math.random() * 1000; await new Promise((resolve) => setTimeout(resolve, backoffDelay + jitter)); } } throw lastError; } formatForLoki(entries) { const streamMap = new Map(); entries.forEach((entry) => { const streamKey = JSON.stringify(entry.labels ?? {}); const timestamp = String(Date.parse(entry.timestamp) * 1000000); const logData = { id: entry.id, level: entry.level, message: entry.message, context: entry.context, }; if (entry.traceId && entry.traceId !== 'undefined') { logData.traceId = entry.traceId; } if (entry.spanId && entry.spanId !== 'undefined') { logData.spanId = entry.spanId; } if (entry.requestId && entry.requestId !== 'undefined') { logData.requestId = entry.requestId; } if (entry.metadata) { logData.metadata = entry.metadata; } const logLine = JSON.stringify(logData); if (!streamMap.has(streamKey)) { streamMap.set(streamKey, []); } streamMap.get(streamKey).push([timestamp, logLine]); }); return { streams: Array.from(streamMap.entries()).map(([streamKey, values]) => ({ stream: JSON.parse(streamKey), values: values.sort((a, b) => a[0].localeCompare(b[0])), })), }; } async isAvailable() { try { // Simple health check - could be improved return this.health.healthy; } catch { return false; } } getHealth() { return { ...this.health }; } recordSuccess() { this.consecutiveFailures = 0; this.health = { healthy: true, consecutiveFailures: 0, lastSuccessfulSend: Date.now(), }; } recordFailure(error) { this.consecutiveFailures++; this.health = { healthy: false, consecutiveFailures: this.consecutiveFailures, errorMessage: error.message, }; } async destroy() { // Cleanup if needed } } exports.LokiTransport = LokiTransport; //# sourceMappingURL=LokiTransport.js.map