UNPKG

strogger

Version:

📊 A modern structured logging library with functional programming, duck-typing, and comprehensive third-party integrations

230 lines • 10.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.printLoggerConfig = exports.createStrogger = exports.strogger = exports.createLogger = void 0; const json_formatter_1 = require("./formatters/json-formatter"); const console_transport_1 = require("./transports/console-transport"); const types_1 = require("./types"); const batching_1 = require("./utils/batching"); const enrichment_1 = require("./utils/enrichment"); const environment_1 = require("./utils/environment"); const sampling_1 = require("./utils/sampling"); const getLogLevelFromEnv = (env) => { if (!env || typeof env !== 'object') { console.warn('[strogger] No environment provided, defaulting to DEBUG'); return types_1.LogLevel.DEBUG; } const level = env.LOG_LEVEL?.toUpperCase(); switch (level) { case "DEBUG": return types_1.LogLevel.DEBUG; case "INFO": return types_1.LogLevel.INFO; case "WARN": return types_1.LogLevel.WARN; case "ERROR": return types_1.LogLevel.ERROR; case "FATAL": return types_1.LogLevel.FATAL; case undefined: // No LOG_LEVEL set return env.STAGE === "prod" ? types_1.LogLevel.INFO : types_1.LogLevel.DEBUG; default: console.warn(`[strogger] Invalid LOG_LEVEL '${env.LOG_LEVEL}' provided. Defaulting to ${env.STAGE === "prod" ? "INFO" : "DEBUG"}.`); return env.STAGE === "prod" ? types_1.LogLevel.INFO : types_1.LogLevel.DEBUG; } }; const shouldLog = (level, config) => { return level >= (config.level !== undefined ? config.level : types_1.LogLevel.INFO); }; const createLogEntry = (config, level, message, context, error, metadata) => { return { timestamp: new Date().toISOString(), level, message, context: { ...(config.stage && { stage: config.stage }), ...(config.serviceName && { serviceName: config.serviceName }), ...context, }, error: error ? { name: error.name, message: error.message, stack: error.stack, } : undefined, metadata, }; }; const createLogger = ({ config = {}, transports = [], formatter: _formatter, env, }) => { // Generate or use provided instance ID const instanceId = config.instanceId || (0, enrichment_1.generateLoggerInstanceId)(); // Merge config: config.level should override env const mergedConfig = { level: getLogLevelFromEnv(env), serviceName: env.SERVICE_NAME || undefined, stage: env.STAGE || "dev", enableStructuredLogging: env.ENABLE_STRUCTURED_LOGGING ?? true, includeTimestamp: true, includeLogLevel: true, instanceId, // Always include the instance ID ...config, // config.level will override above }; // Normalize config.level to a number if (typeof mergedConfig.level === "string") { switch (mergedConfig.level.toUpperCase()) { case "DEBUG": mergedConfig.level = types_1.LogLevel.DEBUG; break; case "INFO": mergedConfig.level = types_1.LogLevel.INFO; break; case "WARN": mergedConfig.level = types_1.LogLevel.WARN; break; case "ERROR": mergedConfig.level = types_1.LogLevel.ERROR; break; case "FATAL": mergedConfig.level = types_1.LogLevel.FATAL; break; default: mergedConfig.level = types_1.LogLevel.INFO; } } // Always prefer explicit userConfig.level if provided (even if 0) if (config.level !== undefined) { mergedConfig.level = config.level; } // Create sampling and rate limiting filter const logFilter = (0, sampling_1.createLogFilter)(mergedConfig); // Create enrichment middleware const enrichmentMiddleware = (0, enrichment_1.createDefaultEnrichmentMiddleware)(mergedConfig.serviceName, mergedConfig.stage, undefined, // sessionId instanceId); // Only use batching if explicitly configured const useBatching = config.batching === true; const loggerTransports = useBatching ? transports.map((transport) => (0, batching_1.createBatchedTransport)(transport, { maxSize: 50, maxWaitTime: 2000, maxBatchSize: 512 * 1024, })) : transports; const log = async (level, message, context, error, metadata) => { // Check log level first if (!shouldLog(level, mergedConfig)) { return; } // Check sampling and rate limiting only if configured if (mergedConfig.samplingRate !== undefined || mergedConfig.rateLimit) { if (!logFilter.shouldLog()) { return; } } // Enrich context with correlation IDs and tracing const enrichedContext = enrichmentMiddleware(context || {}); // Create log entry const entry = createLogEntry(mergedConfig, level, message, enrichedContext, error, metadata); // Medium-priority: log redaction/encryption let processedEntry = entry; if (typeof mergedConfig.redact === "function") { processedEntry = mergedConfig.redact(entry); } // Medium-priority: log validation if (typeof mergedConfig.validate === "function") { try { mergedConfig.validate(processedEntry); } catch (validationError) { // Log validation error and skip sending console.error("[LOGGER ERROR] Log entry validation failed:", validationError); return; } } // Medium-priority: custom hooks (analytics/metrics) if (Array.isArray(mergedConfig.hooks)) { for (const hook of mergedConfig.hooks) { try { // Await if hook returns a promise const result = hook(processedEntry); if (result && typeof result.then === "function") { await result; } } catch (hookError) { // Log hook errors but do not block logging console.error("[LOGGER ERROR] Log hook failed:", hookError); } } } // Send to all transports const results = await Promise.allSettled(loggerTransports.map((transport) => transport.log(processedEntry))); // Log any async transport errors for (const result of results) { if (result.status === "rejected") { console.error(result.reason); } } }; return { debug: (message, context, metadata) => log(types_1.LogLevel.DEBUG, message, context, undefined, metadata), info: (message, context, metadata) => log(types_1.LogLevel.INFO, message, context, undefined, metadata), warn: (message, context, error, metadata) => log(types_1.LogLevel.WARN, message, context, error, metadata), error: (message, context, error, metadata) => log(types_1.LogLevel.ERROR, message, context, error, metadata), fatal: (message, context, error, metadata) => log(types_1.LogLevel.FATAL, message, context, error, metadata), logFunctionStart: (functionName, context, metadata) => log(types_1.LogLevel.INFO, `Function ${functionName} started`, { ...context, functionName }, undefined, metadata), logFunctionEnd: (functionName, duration, context, metadata) => log(types_1.LogLevel.INFO, `Function ${functionName} completed in ${duration}ms`, { ...context, functionName, duration }, undefined, metadata), logDatabaseOperation: (operation, table, context, metadata) => log(types_1.LogLevel.DEBUG, `Database operation: ${operation} on table ${table}`, { ...context, operation, table }, undefined, metadata), logApiRequest: (method, path, statusCode, context, metadata) => log(types_1.LogLevel.INFO, `API ${method} ${path} - ${statusCode}`, { ...context, method, path, statusCode }, undefined, metadata), setLevel: (level) => { mergedConfig.level = level; for (const t of transports) { t.setLevel?.(level); } }, getLevel: () => mergedConfig.level !== undefined ? mergedConfig.level : types_1.LogLevel.INFO, getInstanceId: () => instanceId, addTransport: (transport) => transports.push(transport), removeTransport: (transport) => { const idx = transports.indexOf(transport); if (idx > -1) transports.splice(idx, 1); }, setFormatter: (_newFormatter) => { // Note: formatter is available for custom formatting // formatter = newFormatter; }, getSamplingStats: () => logFilter.getStats(), flush: async () => { await Promise.allSettled(loggerTransports.map((transport) => transport.flush?.() || Promise.resolve())); }, getBatchStats: () => loggerTransports.map((t) => t.getStats?.() || {}), }; }; exports.createLogger = createLogger; // Create and export a default logger instance const env = (0, environment_1.getEnvironment)(); const defaultFormatter = (0, json_formatter_1.createJsonFormatter)(); const defaultTransport = (0, console_transport_1.createConsoleTransport)({ formatter: defaultFormatter, }); exports.strogger = (0, exports.createLogger)({ config: {}, transports: [defaultTransport], formatter: defaultFormatter, env, }); // Branded alias for createLogger exports.createStrogger = exports.createLogger; const printLoggerConfig = (env) => { const level = getLogLevelFromEnv(env); console.log('--- Strogger Logger Configuration ---'); console.log('LOG_LEVEL:', env.LOG_LEVEL ?? '(default)'); console.log('STAGE:', env.STAGE ?? 'dev'); console.log('SERVICE_NAME:', env.SERVICE_NAME ?? '(none)'); console.log('ENABLE_STRUCTURED_LOGGING:', env.ENABLE_STRUCTURED_LOGGING ?? true); console.log('Effective log level:', level); console.log('--------------------------------------'); }; exports.printLoggerConfig = printLoggerConfig; //# sourceMappingURL=logger.js.map