UNPKG

roarr

Version:

JSON logger for Node.js and browser.

300 lines 12.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createLogger = void 0; const config_1 = require("../config"); const constants_1 = require("../constants"); const hasOwnProperty_1 = require("../utilities/hasOwnProperty"); const isBrowser_1 = require("../utilities/isBrowser"); const isTruthy_1 = require("../utilities/isTruthy"); const createMockLogger_1 = require("./createMockLogger"); const fast_printf_1 = require("fast-printf"); const safe_stable_stringify_1 = __importDefault(require("safe-stable-stringify")); let loggedWarningAsyncLocalContext = false; const getGlobalRoarrContext = () => { return globalThis.ROARR; }; const createDefaultAsyncLocalContext = () => { return { messageContext: {}, transforms: [], }; }; const getAsyncLocalContext = () => { const asyncLocalStorage = getGlobalRoarrContext().asyncLocalStorage; if (!asyncLocalStorage) { throw new Error('AsyncLocalContext is unavailable.'); } const asyncLocalContext = asyncLocalStorage.getStore(); if (asyncLocalContext) { return asyncLocalContext; } return createDefaultAsyncLocalContext(); }; const isAsyncLocalContextAvailable = () => { return Boolean(getGlobalRoarrContext().asyncLocalStorage); }; const getSequence = () => { if (isAsyncLocalContextAvailable()) { const asyncLocalContext = getAsyncLocalContext(); if ((0, hasOwnProperty_1.hasOwnProperty)(asyncLocalContext, 'sequenceRoot') && (0, hasOwnProperty_1.hasOwnProperty)(asyncLocalContext, 'sequence') && typeof asyncLocalContext.sequence === 'number') { return (String(asyncLocalContext.sequenceRoot) + '.' + String(asyncLocalContext.sequence++)); } return String(getGlobalRoarrContext().sequence++); } return String(getGlobalRoarrContext().sequence++); }; const createChildLogger = (log, logLevel) => { return (a, b, c, d, e, f, g, h, index, index_) => { if (typeof a === 'string') { // Message-only call: inject logLevel as context log({ logLevel }, a, b, c, d, e, f, g, h, index); } else { // Context + message call: merge logLevel into existing context log({ ...a, logLevel }, b, c, d, e, f, g, h, index, index_); } }; }; const MAX_ONCE_ENTRIES = 1000; const buildOnceKey = (logLevel, a, b) => { // For most use cases, the first two arguments (context/message) are sufficient // to uniquely identify a log call. This avoids expensive full serialization. if (typeof a === 'string') { return `${logLevel}:${a}`; } // When context is provided, include stringified context + message try { return `${logLevel}:${JSON.stringify(a)}:${b}`; } catch (_a) { return `${logLevel}:${(0, safe_stable_stringify_1.default)(a)}:${b}`; } }; const createOnceChildLogger = (log, logLevel) => { return (a, b, c, d, e, f, g, h, index, index_) => { const onceLog = getGlobalRoarrContext().onceLog; // Build key first, check cache before doing any other work const key = buildOnceKey(logLevel, a, b); if (onceLog.has(key)) { return; } onceLog.add(key); if (onceLog.size > MAX_ONCE_ENTRIES) { onceLog.clear(); } // Optimized: directly inject logLevel instead of creating child logger if (typeof a === 'string') { log({ logLevel }, a, b, c, d, e, f, g, h, index); } else { log({ ...a, logLevel }, b, c, d, e, f, g, h, index, index_); } }; }; const createLogger = (onMessage, parentMessageContext = {}, transforms = []) => { var _a; if (!(0, isBrowser_1.isBrowser)() && typeof process !== 'undefined') { // eslint-disable-next-line node/no-process-env const enabled = (0, isTruthy_1.isTruthy)((_a = process.env.ROARR_LOG) !== null && _a !== void 0 ? _a : ''); if (!enabled) { return (0, createMockLogger_1.createMockLogger)(onMessage, parentMessageContext); } } const log = (a, b, c, d, e, f, g, h, index, index_) => { var _a; const time = Date.now(); // Cache global context to avoid repeated lookups const globalContext = globalThis.ROARR; const asyncLocalStorage = globalContext.asyncLocalStorage; // Get async local context with single lookup (or use default) const asyncLocalContext = (_a = asyncLocalStorage === null || asyncLocalStorage === void 0 ? void 0 : asyncLocalStorage.getStore()) !== null && _a !== void 0 ? _a : createDefaultAsyncLocalContext(); // Generate sequence inline using cached references let sequence; if ('sequenceRoot' in asyncLocalContext && typeof asyncLocalContext.sequence === 'number') { sequence = asyncLocalContext.sequenceRoot + '.' + String(asyncLocalContext.sequence++); } else { sequence = String(globalContext.sequence++); } let context; let message; if (typeof a === 'string') { context = { ...asyncLocalContext.messageContext, ...parentMessageContext, }; } else { context = { ...asyncLocalContext.messageContext, ...parentMessageContext, ...a, }; } if (typeof a === 'string' && b === undefined) { message = a; } else if (typeof a === 'string') { if (!a.includes('%')) { throw new Error('When a string parameter is followed by other arguments, then it is assumed that you are attempting to format a message using printf syntax. You either forgot to add printf bindings or if you meant to add context to the log message, pass them in an object as the first parameter.'); } message = (0, fast_printf_1.printf)(a, b, c, d, e, f, g, h, index, index_); } else { let fallbackMessage = b; if (typeof b !== 'string') { if (b === undefined) { fallbackMessage = ''; } else { throw new TypeError('Message must be a string. Received ' + typeof b + '.'); } } message = (0, fast_printf_1.printf)(fallbackMessage, c, d, e, f, g, h, index, index_); } let packet = { context, message, sequence, time, version: config_1.ROARR_LOG_FORMAT_VERSION, }; // Iterate over transforms without creating a new array if (asyncLocalContext.transforms.length > 0 || transforms.length > 0) { for (const transform of asyncLocalContext.transforms) { packet = transform(packet); if (typeof packet !== 'object' || packet === null) { throw new Error('Message transform function must return a message object.'); } } for (const transform of transforms) { packet = transform(packet); if (typeof packet !== 'object' || packet === null) { throw new Error('Message transform function must return a message object.'); } } } onMessage(packet); }; /** * Creates a child logger with the provided context. * If context is an object, then its properties are prepended to all descending logs. * If context is a function, then that function is used to process all descending logs. */ log.child = (context) => { let asyncLocalContext; if (isAsyncLocalContextAvailable()) { asyncLocalContext = getAsyncLocalContext(); } else { asyncLocalContext = createDefaultAsyncLocalContext(); } if (typeof context === 'function') { return (0, exports.createLogger)(onMessage, { ...asyncLocalContext.messageContext, ...parentMessageContext, ...context, }, [context, ...transforms]); } return (0, exports.createLogger)(onMessage, { ...asyncLocalContext.messageContext, ...parentMessageContext, ...context, }, transforms); }; log.getContext = () => { let asyncLocalContext; if (isAsyncLocalContextAvailable()) { asyncLocalContext = getAsyncLocalContext(); } else { asyncLocalContext = createDefaultAsyncLocalContext(); } return { ...asyncLocalContext.messageContext, ...parentMessageContext, }; }; log.adopt = async (routine, context) => { if (!isAsyncLocalContextAvailable()) { if (loggedWarningAsyncLocalContext === false) { loggedWarningAsyncLocalContext = true; onMessage({ context: { logLevel: constants_1.logLevels.warn, package: 'roarr', }, message: 'async_hooks are unavailable; Roarr.adopt will not function as expected', sequence: getSequence(), time: Date.now(), version: config_1.ROARR_LOG_FORMAT_VERSION, }); } return routine(); } const asyncLocalContext = getAsyncLocalContext(); let sequenceRoot; if ((0, hasOwnProperty_1.hasOwnProperty)(asyncLocalContext, 'sequenceRoot') && (0, hasOwnProperty_1.hasOwnProperty)(asyncLocalContext, 'sequence') && typeof asyncLocalContext.sequence === 'number') { sequenceRoot = asyncLocalContext.sequenceRoot + '.' + String(asyncLocalContext.sequence++); } else { sequenceRoot = String(getGlobalRoarrContext().sequence++); } let nextContext = { ...asyncLocalContext.messageContext, }; const nextTransforms = [...asyncLocalContext.transforms]; if (typeof context === 'function') { nextTransforms.push(context); } else { nextContext = { ...nextContext, ...context, }; } const asyncLocalStorage = getGlobalRoarrContext().asyncLocalStorage; if (!asyncLocalStorage) { throw new Error('Async local context unavailable.'); } return asyncLocalStorage.run({ messageContext: nextContext, sequence: 0, sequenceRoot, transforms: nextTransforms, }, () => { return routine(); }); }; log.debug = createChildLogger(log, constants_1.logLevels.debug); log.debugOnce = createOnceChildLogger(log, constants_1.logLevels.debug); log.error = createChildLogger(log, constants_1.logLevels.error); log.errorOnce = createOnceChildLogger(log, constants_1.logLevels.error); log.fatal = createChildLogger(log, constants_1.logLevels.fatal); log.fatalOnce = createOnceChildLogger(log, constants_1.logLevels.fatal); log.info = createChildLogger(log, constants_1.logLevels.info); log.infoOnce = createOnceChildLogger(log, constants_1.logLevels.info); log.trace = createChildLogger(log, constants_1.logLevels.trace); log.traceOnce = createOnceChildLogger(log, constants_1.logLevels.trace); log.warn = createChildLogger(log, constants_1.logLevels.warn); log.warnOnce = createOnceChildLogger(log, constants_1.logLevels.warn); return log; }; exports.createLogger = createLogger; //# sourceMappingURL=createLogger.js.map