UNPKG

nstdlib-nightly

Version:

Node.js standard library converted to runtime-agnostic ES modules.

533 lines (462 loc) 12.3 kB
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/util/debuglog.js import { CHAR_LOWERCASE_B as kTraceBegin, CHAR_LOWERCASE_E as kTraceEnd, CHAR_LOWERCASE_N as kTraceInstant, } from "nstdlib/lib/internal/constants"; import { inspect, format, formatWithOptions, } from "nstdlib/lib/internal/util/inspect"; import { getCategoryEnabledBuffer, trace, } from "nstdlib/stub/binding/trace_events"; import * as __hoisted_internal_util_colors__ from "nstdlib/lib/internal/util/colors"; // `debugImpls` and `testEnabled` are deliberately not initialized so any call // to `debuglog()` before `initializeDebugEnv()` is called will throw. let debugImpls; let testEnabled; // `debugEnv` is initial value of process.env.NODE_DEBUG function initializeDebugEnv(debugEnv) { debugImpls = { __proto__: null }; if (debugEnv) { // This is run before any user code, it's OK not to use primordials. debugEnv = debugEnv .replace(/[|\\{}()[\]^$+?.]/g, "\\$&") .replaceAll("*", ".*") .replaceAll(",", "$|^"); const debugEnvRegex = new RegExp(`^${debugEnv}$`, "i"); testEnabled = (str) => RegExp.prototype.exec.call(debugEnvRegex, str) !== null; } else { testEnabled = () => false; } } // Emits warning when user sets // NODE_DEBUG=http or NODE_DEBUG=http2. function emitWarningIfNeeded(set) { if ("HTTP" === set || "HTTP2" === set) { process.emitWarning( "Setting the NODE_DEBUG environment variable " + "to '" + String.prototype.toLowerCase.call(set) + "' can expose sensitive " + "data (such as passwords, tokens and authentication headers) " + "in the resulting log.", ); } } const noop = () => {}; let utilColors; function lazyUtilColors() { utilColors ??= __hoisted_internal_util_colors__; return utilColors; } function debuglogImpl(enabled, set, args) { if (debugImpls[set] === undefined) { if (enabled) { const pid = process.pid; emitWarningIfNeeded(set); debugImpls[set] = function debug(...args) { const colors = lazyUtilColors().shouldColorize(process.stderr); const msg = formatWithOptions({ colors }, ...args); const coloredPID = inspect(pid, { colors }); process.stderr.write(format("%s %s: %s\n", set, coloredPID, msg)); }; } else { debugImpls[set] = noop; } } return debugImpls[set]; } // debuglogImpl depends on process.pid and process.env.NODE_DEBUG, // so it needs to be called lazily in top scopes of internal modules // that may be loaded before these run time states are allowed to // be accessed. function debuglog(set, cb) { function init() { set = String.prototype.toUpperCase.call(set); enabled = testEnabled(set); } let debug = (...args) => { init(); // Only invokes debuglogImpl() when the debug function is // called for the first time. debug = debuglogImpl(enabled, set); if (typeof cb === "function") cb(debug); switch (args.length) { case 1: return debug(args[0]); case 2: return debug(args[0], args[1]); default: return debug(...new (Array.prototype[Symbol.iterator]())(args)); } }; let enabled; let test = () => { init(); test = () => enabled; return enabled; }; const logger = (...args) => { switch (args.length) { case 1: return debug(args[0]); case 2: return debug(args[0], args[1]); default: return debug(...new (Array.prototype[Symbol.iterator]())(args)); } }; Object.defineProperty(logger, "enabled", { __proto__: null, get() { return test(); }, configurable: true, enumerable: true, }); return logger; } function pad(value) { return String.prototype.padStart.call(`${value}`, 2, "0"); } const kNone = 1 << 0; const kSkipLog = 1 << 1; const kSkipTrace = 1 << 2; const kSecond = 1000; const kMinute = 60 * kSecond; const kHour = 60 * kMinute; function formatTime(ms) { let hours = 0; let minutes = 0; let seconds = 0; if (ms >= kSecond) { if (ms >= kMinute) { if (ms >= kHour) { hours = Math.floor(ms / kHour); ms = ms % kHour; } minutes = Math.floor(ms / kMinute); ms = ms % kMinute; } seconds = ms / kSecond; } if (hours !== 0 || minutes !== 0) { ({ 0: seconds, 1: ms } = String.prototype.split.call( Number.prototype.toFixed.call(seconds, 3), ".", )); const res = hours !== 0 ? `${hours}:${pad(minutes)}` : minutes; return `${res}:${pad(seconds)}.${ms} (${hours !== 0 ? "h:m" : ""}m:ss.mmm)`; } if (seconds !== 0) { return `${Number.prototype.toFixed.call(seconds, 3)}s`; } return `${Number(Number.prototype.toFixed.call(ms, 3))}ms`; } function safeTraceLabel(label) { return label.replace(/\\/g, "\\\\"); } /** * @typedef {(label: string, timeFormatted: string, args?: any[]) => void} LogImpl */ /** * Returns true if label was found * @param {string} timesStore * @param {string} implementation * @param {LogImpl} logImp * @param {string} label * @param {any} args * @returns {void} */ function timeLogImpl(timesStore, implementation, logImp, label, args) { const time = timesStore.get(label); if (time === undefined) { process.emitWarning(`No such label '${label}' for ${implementation}`); return; } const duration = process.hrtime(time); const ms = duration[0] * 1000 + duration[1] / 1e6; const formatted = formatTime(ms); if (args === undefined) { logImp(label, formatted); } else { logImp(label, formatted, args); } } /** * @param {SafeMap} timesStore * @param {string} traceCategory * @param {string} implementation * @param {number} timerFlags * @param {string} logLabel * @param {string} traceLabel * @returns {void} */ function time( timesStore, traceCategory, implementation, timerFlags, logLabel = "default", traceLabel = undefined, ) { // Coerces everything other than Symbol to a string logLabel = `${logLabel}`; if (traceLabel !== undefined) { traceLabel = `${traceLabel}`; } else { traceLabel = logLabel; } if (timesStore.has(logLabel)) { process.emitWarning( `Label '${logLabel}' already exists for ${implementation}`, ); return; } if ((timerFlags & kSkipTrace) === 0) { traceLabel = safeTraceLabel(traceLabel); trace(kTraceBegin, traceCategory, traceLabel, 0); } timesStore.set(logLabel, process.hrtime()); } /** * @param {SafeMap} timesStore * @param {string} traceCategory * @param {string} implementation * @param {number} timerFlags * @param {LogImpl} logImpl * @param {string} logLabel * @param {string} traceLabel * @returns {void} */ function timeEnd( timesStore, traceCategory, implementation, timerFlags, logImpl, logLabel = "default", traceLabel = undefined, ) { // Coerces everything other than Symbol to a string logLabel = `${logLabel}`; if (traceLabel !== undefined) { traceLabel = `${traceLabel}`; } else { traceLabel = logLabel; } if ((timerFlags & kSkipLog) === 0) { timeLogImpl(timesStore, implementation, logImpl, logLabel); } if ((timerFlags & kSkipTrace) === 0) { traceLabel = safeTraceLabel(traceLabel); trace(kTraceEnd, traceCategory, traceLabel, 0); } timesStore.delete(logLabel); } /** * @param {SafeMap} timesStore * @param {string} traceCategory * @param {string} implementation * @param {number} timerFlags * @param {LogImpl} logImpl * @param {string} logLabel * @param {string} traceLabel * @param {any[]} args * @returns {void} */ function timeLog( timesStore, traceCategory, implementation, timerFlags, logImpl, logLabel = "default", traceLabel = undefined, args, ) { // Coerces everything other than Symbol to a string logLabel = `${logLabel}`; if (traceLabel !== undefined) { traceLabel = `${traceLabel}`; } else { traceLabel = logLabel; } if ((timerFlags & kSkipLog) === 0) { timeLogImpl(timesStore, implementation, logImpl, logLabel, args); } if ((timerFlags & kSkipTrace) === 0) { traceLabel = safeTraceLabel(traceLabel); trace(kTraceInstant, traceCategory, traceLabel, 0); } } /** * @type {Record<string, SafeMap>} */ let tracesStores; /** * @typedef {(logLabel: string, traceLabel?: string) => void} TimerStart */ /** * @typedef {(logLabel: string, traceLabel?: string) => void} TimerEnd */ /** * @typedef {(logLabel: string, traceLabel?: string, args?: any[]) => void} TimerLog */ /** * Debuglog with time fns and support for trace * @param {string} set * @param {(startTimer: TimerStart, endTimer: TimerEnd, logTimer: TimerLog) => void} cb * @returns {{startTimer: TimerStart, endTimer: TimerEnd, logTimer: TimerLog}} */ function debugWithTimer(set, cb) { set = String.prototype.toUpperCase.call(set); if (tracesStores === undefined) { tracesStores = { __proto__: null }; } /** * @type {LogImpl} */ function logImpl(label, timeFormatted, args) { const pid = process.pid; const colors = { colors: lazyUtilColors().shouldColorize(process.stderr) }; const coloredPID = inspect(pid, colors); if (args === undefined) process.stderr.write( format("%s %s %s: %s\n", set, coloredPID, label, timeFormatted), ); else process.stderr.write( format( "%s %s %s: %s\n", set, coloredPID, label, timeFormatted, ...new (Array.prototype[Symbol.iterator]())(args), ), ); } const traceCategory = `node,node.${String.prototype.toLowerCase.call(set)}`; let traceCategoryBuffer; let debugLogCategoryEnabled = false; let timerFlags = kNone; const skipAll = kSkipLog | kSkipTrace; function ensureTimerFlagsAreUpdated() { timerFlags &= ~kSkipTrace; if (traceCategoryBuffer[0] === 0) { timerFlags |= kSkipTrace; } } /** * @type {TimerStart} */ function internalStartTimer(logLabel, traceLabel) { ensureTimerFlagsAreUpdated(); if (timerFlags === skipAll) { return; } time( tracesStores[set], traceCategory, "debuglog.time", timerFlags, logLabel, traceLabel, ); } /** * @type {TimerEnd} */ function internalEndTimer(logLabel, traceLabel) { ensureTimerFlagsAreUpdated(); if (timerFlags === skipAll) { return; } timeEnd( tracesStores[set], traceCategory, "debuglog.timeEnd", timerFlags, logImpl, logLabel, traceLabel, ); } /** * @type {TimerLog} */ function internalLogTimer(logLabel, traceLabel, args) { ensureTimerFlagsAreUpdated(); if (timerFlags === skipAll) { return; } timeLog( tracesStores[set], traceCategory, "debuglog.timeLog", timerFlags, logImpl, logLabel, traceLabel, args, ); } function init() { if (tracesStores[set] === undefined) { tracesStores[set] = new Map(); } emitWarningIfNeeded(set); debugLogCategoryEnabled = testEnabled(set); traceCategoryBuffer = getCategoryEnabledBuffer(traceCategory); timerFlags = kNone; if (!debugLogCategoryEnabled) { timerFlags |= kSkipLog; } if (traceCategoryBuffer[0] === 0) { timerFlags |= kSkipTrace; } cb(internalStartTimer, internalEndTimer, internalLogTimer); } /** * @type {TimerStart} */ const startTimer = (logLabel, traceLabel) => { init(); if (timerFlags !== skipAll) internalStartTimer(logLabel, traceLabel); }; /** * @type {TimerEnd} */ const endTimer = (logLabel, traceLabel) => { init(); if (timerFlags !== skipAll) internalEndTimer(logLabel, traceLabel); }; /** * @type {TimerLog} */ const logTimer = (logLabel, traceLabel, args) => { init(); if (timerFlags !== skipAll) internalLogTimer(logLabel, traceLabel, args); }; return { startTimer, endTimer, logTimer, }; } export { kNone }; export { kSkipLog }; export { kSkipTrace }; export { formatTime }; export { time }; export { timeEnd }; export { timeLog }; export { debuglog }; export { debugWithTimer }; export { initializeDebugEnv };