strogger
Version:
📊 A modern structured logging library with functional programming, duck-typing, and comprehensive third-party integrations
230 lines • 10.5 kB
JavaScript
;
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