UNPKG

@loglayer/transport-simple-pretty-terminal

Version:

Pretty log output in the terminal / browser / Next.js for the LogLayer logging library.

676 lines (668 loc) 22.2 kB
// src/SimplePrettyTerminalTransport.ts import { LoggerlessTransport } from "@loglayer/transport"; import chalk4 from "chalk"; // src/themes.ts import chalk from "chalk"; var moonlight = { colors: { trace: chalk.rgb(114, 135, 153), // Muted blue-grey for less important info debug: chalk.rgb(130, 170, 255), // Soft blue that pops but doesn't strain info: chalk.rgb(195, 232, 141), // Sage green for good readability warn: chalk.rgb(255, 203, 107), // Warm yellow, less harsh than pure yellow error: chalk.rgb(247, 118, 142), // Soft red that stands out without being aggressive fatal: chalk.bgRgb(247, 118, 142).white // Inverted soft red for maximum visibility }, logIdColor: chalk.rgb(84, 98, 117), // Darker blue-grey for secondary information dataValueColor: chalk.rgb(209, 219, 231), // Light grey-blue for primary content dataKeyColor: chalk.rgb(130, 170, 255) // Matching debug blue for consistency }; var sunlight = { colors: { trace: chalk.rgb(110, 110, 110), // Dark grey for subtle information debug: chalk.rgb(32, 96, 159), // Deep blue for strong contrast on white info: chalk.rgb(35, 134, 54), // Forest green, easier on the eyes than bright green warn: chalk.rgb(176, 95, 0), // Brown-orange for natural warning color error: chalk.rgb(191, 0, 0), // Deep red for clear visibility on light backgrounds fatal: chalk.bgRgb(191, 0, 0).white // White on deep red for critical issues }, logIdColor: chalk.rgb(110, 110, 110), dataValueColor: chalk.rgb(0, 0, 0), dataKeyColor: chalk.rgb(32, 96, 159) }; var neon = { colors: { trace: chalk.rgb(108, 108, 255), // Electric blue debug: chalk.rgb(255, 82, 246), // Hot pink info: chalk.rgb(0, 255, 163), // Cyber green warn: chalk.rgb(255, 231, 46), // Electric yellow error: chalk.rgb(255, 53, 91), // Neon red fatal: chalk.bgRgb(255, 53, 91).rgb(0, 255, 163) // Neon red bg with cyber green text }, logIdColor: chalk.rgb(187, 134, 252), // Bright purple dataValueColor: chalk.rgb(255, 255, 255), // Pure white dataKeyColor: chalk.rgb(0, 255, 240) // Cyan }; var nature = { colors: { trace: chalk.rgb(101, 115, 126), // Slate grey debug: chalk.rgb(34, 139, 34), // Forest green info: chalk.rgb(46, 139, 87), // Sea green warn: chalk.rgb(218, 165, 32), // Golden rod error: chalk.rgb(139, 69, 19), // Saddle brown fatal: chalk.bgRgb(139, 69, 19).white // Brown background with white text }, logIdColor: chalk.rgb(101, 115, 126), dataValueColor: chalk.rgb(0, 0, 0), dataKeyColor: chalk.rgb(34, 139, 34) }; var pastel = { colors: { trace: chalk.rgb(200, 200, 200), // Light grey debug: chalk.rgb(173, 216, 230), // Light blue info: chalk.rgb(144, 238, 144), // Light green warn: chalk.rgb(255, 218, 185), // Peach error: chalk.rgb(255, 182, 193), // Light pink fatal: chalk.bgRgb(255, 182, 193).rgb(105, 105, 105) // Light pink bg with dim grey text }, logIdColor: chalk.rgb(200, 200, 200), dataValueColor: chalk.rgb(105, 105, 105), dataKeyColor: chalk.rgb(173, 216, 230) }; // src/vendor/prettyjson.js import chalk2 from "chalk"; // src/vendor/utils.js function indent(numSpaces) { return new Array(numSpaces + 1).join(" "); } function getMaxIndexLength(input) { var maxWidth = 0; Object.getOwnPropertyNames(input).forEach((key) => { if (input[key] === void 0) { return; } maxWidth = Math.max(maxWidth, key.length); }); return maxWidth; } function isIsoStringDate(isoString) { if (typeof isoString !== "string") { return false; } if (!/^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?$/.test(isoString)) { return false; } const testDate = new Date(isoString); return !Number.isNaN(testDate.getTime()); } // src/vendor/prettyjson.js var conflictChars = /[^\w\s\n\r\v\t.,]/i; var isPrintable = (input, options) => input !== void 0 || options.renderUndefined; var isSerializable = (input, onlyPrimitives, options) => { if (typeof input === "boolean" || typeof input === "number" || typeof input === "function" || input === null || input === void 0 || input instanceof Date) { return true; } if (typeof input === "string" && input.indexOf("\n") === -1) { return true; } if (options.inlineArrays && !onlyPrimitives) { if (Array.isArray(input) && isSerializable(input[0], true, options)) { return true; } } return false; }; var getColorRenderer = (colorFn, options) => { if (options.noColor) { return (text) => text; } if (typeof colorFn !== "function") { return (text) => text; } return colorFn; }; var addColorToData = (input, options) => { if (options.noColor) { return input; } if (input instanceof Date) { return getColorRenderer(options.dateColor, options)(input.toISOString()); } if (typeof input === "string") { if (isIsoStringDate(input)) { return getColorRenderer(options.dateColor, options)(input); } return getColorRenderer(options.stringColor, options)(input); } const sInput = `${input}`; if (typeof input === "boolean") { return getColorRenderer(options.booleanColor, options)(sInput); } if (input === null || input === void 0) { return getColorRenderer(options.nullUndefinedColor, options)(sInput); } if (typeof input === "number") { if (input >= 0) { return getColorRenderer(options.positiveNumberColor, options)(sInput); } return getColorRenderer(options.negativeNumberColor, options)(sInput); } if (typeof input === "function") { return "function() {}"; } if (Array.isArray(input)) { return input.join(", "); } return sInput; }; var colorMultilineString = (options, line) => getColorRenderer(options.multilineStringColor, options)(line); var indentLines = (string, spaces, options) => { let lines = string.split("\n"); lines = lines.map((line) => indent(spaces) + colorMultilineString(options, line)); return lines.join("\n"); }; var renderToArray = (data, options, indentation) => { if (typeof data === "string" && data.match(conflictChars) && options.escape) { data = JSON.stringify(data); } if (!isPrintable(data, options)) { return []; } if (isSerializable(data, false, options)) { return [indent(indentation) + addColorToData(data, options)]; } if (typeof data === "string") { return [ indent(indentation) + colorMultilineString(options, '"""'), indentLines(data, indentation + options.defaultIndentation, options), indent(indentation) + colorMultilineString(options, '"""') ]; } if (Array.isArray(data)) { if (data.length === 0) { return [indent(indentation) + options.emptyArrayMsg]; } if (options.collapseArrays && data.length > 0) { return [`${indent(indentation)}[... ${data.length} items]`]; } const outputArray = []; data.forEach((element) => { if (!isPrintable(element, options)) { return; } let line = "- "; line = getColorRenderer(options.dashColor, options)(line); line = indent(indentation) + line; if (isSerializable(element, false, options)) { line += renderToArray(element, options, 0)[0]; outputArray.push(line); } else { outputArray.push(line); outputArray.push.apply(outputArray, renderToArray(element, options, indentation + options.defaultIndentation)); } }); return outputArray; } if (data instanceof Error) { return renderToArray( { message: data.message, stack: data.stack.split("\n") }, options, indentation ); } const maxIndexLength = options.noAlign ? 0 : getMaxIndexLength(data); let key; const output = []; Object.getOwnPropertyNames(data).forEach((i) => { if (!isPrintable(data[i], options)) { return; } key = `${i}: `; key = getColorRenderer(options.keysColor, options)(key); key = indent(indentation) + key; if (isSerializable(data[i], false, options)) { const nextIndentation = options.noAlign ? 0 : maxIndexLength - i.length; key += renderToArray(data[i], options, nextIndentation)[0]; output.push(key); } else { output.push(key); output.push.apply(output, renderToArray(data[i], options, indentation + options.defaultIndentation)); } }); return output; }; var validateOptionsAndSetDefaults = (options) => { options = options || {}; options.emptyArrayMsg = options.emptyArrayMsg || "(empty array)"; options.keysColor = options.keysColor || chalk2.green; options.dashColor = options.dashColor || chalk2.green; options.booleanColor = options.booleanColor || chalk2.cyan; options.nullUndefinedColor = options.nullUndefinedColor || chalk2.grey; options.numberColor = options.numberColor || chalk2.blue; options.positiveNumberColor = options.positiveNumberColor || options.numberColor; options.negativeNumberColor = options.negativeNumberColor || options.numberColor; options.dateColor = options.dateColor || chalk2.magenta; options.defaultIndentation = options.defaultIndentation || 2; options.noColor = !!options.noColor; options.noAlign = !!options.noAlign; options.escape = !!options.escape; options.renderUndefined = !!options.renderUndefined; options.collapseArrays = !!options.collapseArrays; options.stringColor = options.stringColor || null; options.multilineStringColor = options.multilineStringColor || options.stringColor || null; return options; }; function render(data, options, indentation) { indentation = indentation || 0; options = validateOptionsAndSetDefaults(options); return renderToArray(data, options, indentation).join("\n"); } // src/views/utils.ts import chalk3 from "chalk"; import { format } from "date-fns"; function formatTimestamp(timestamp, colorFn, timestampFormat = "HH:mm:ss.SSS") { let formattedTime; if (typeof timestampFormat === "function") { formattedTime = timestampFormat(timestamp); } else { const date = new Date(timestamp); formattedTime = format(date, timestampFormat); } return colorFn(`[${formattedTime}]`); } function getLevelColor(level, colors) { const levelLower = level.toLowerCase(); switch (levelLower) { case "trace": return colors.trace || chalk3.white; case "debug": return colors.debug || chalk3.white; case "info": return colors.info || chalk3.white; case "warn": return colors.warn || chalk3.white; case "error": return colors.error || chalk3.white; case "fatal": return colors.fatal || chalk3.white; default: return colors.info || chalk3.white; } } function formatValue(value, expanded = false, collapseArrays = true) { if (typeof value === "string") return value; if (typeof value === "number" || typeof value === "boolean") return value.toString(); if (value === null) return "null"; if (value === void 0) return "undefined"; if (Array.isArray(value)) return expanded ? JSON.stringify(value) : collapseArrays ? "[...]" : value.toString(); if (typeof value === "object") return expanded ? JSON.stringify(value) : "{...}"; return value.toString(); } function formatInlineData(data, config, maxDepth, expanded = false, collapseArrays = true) { if (!data) return ""; const pairs = []; const traverse = (obj, prefix = "", depth = 0) => { if (!expanded && depth >= maxDepth) return; for (const [key, value] of Object.entries(obj)) { const fullKey = prefix ? `${prefix}.${key}` : key; if (value && typeof value === "object" && !Array.isArray(value)) { if (expanded) { pairs.push(`${config.dataKeyColor(fullKey)}=${config.dataValueColor(JSON.stringify(value))}`); } else { traverse(value, fullKey, depth + 1); } } else { pairs.push( `${config.dataKeyColor(fullKey)}=${config.dataValueColor(formatValue(value, expanded, collapseArrays))}` ); } } }; traverse(data); const result = pairs.join(" "); return result; } // src/views/SimpleView.ts var SimpleView = class { config; viewMode; maxInlineDepth; showLogId; timestampFormat; collapseArrays; flattenNestedObjects; runtime; includeDataInBrowserConsole; constructor(config) { this.config = config; this.viewMode = config.viewMode; this.maxInlineDepth = config.maxInlineDepth; this.showLogId = config.showLogId; this.timestampFormat = config.timestampFormat; this.collapseArrays = config.collapseArrays !== false; this.flattenNestedObjects = config.flattenNestedObjects !== false; this.runtime = config.runtime; this.includeDataInBrowserConsole = config.includeDataInBrowserConsole || false; } getConfig() { return this.config; } getViewMode() { return this.viewMode; } /** * Writes a message to the appropriate output based on runtime */ writeMessage(message, level, data) { if (this.runtime === "node") { process?.stdout?.write?.(`${message} `); } else { const shouldIncludeData = this.includeDataInBrowserConsole && data !== void 0; switch (level) { case "trace": if (shouldIncludeData) { console.debug(message, data); } else { console.debug(message); } break; case "debug": if (shouldIncludeData) { console.debug(message, data); } else { console.debug(message); } break; case "info": if (shouldIncludeData) { console.info(message, data); } else { console.info(message); } break; case "warn": if (shouldIncludeData) { console.warn(message, data); } else { console.warn(message); } break; case "error": case "fatal": if (shouldIncludeData) { console.error(message, data); } else { console.error(message); } break; default: if (shouldIncludeData) { console.log(message, data); } else { console.log(message); } break; } } } /** * Renders a single log entry based on the current view mode */ renderLogLine(entry) { const levelColor = getLevelColor(entry.level, this.config.config.colors); const chevron = levelColor(`\u25B6 ${entry.level.toUpperCase()} `); const message = entry.message || "(no message)"; const timestamp = formatTimestamp(entry.timestamp, this.config.config.logIdColor, this.timestampFormat); const parsedData = entry.data ? JSON.parse(entry.data) : void 0; switch (this.viewMode) { case "message-only": { const condensedLine = `${timestamp} ${chevron}${message}`; this.writeMessage(condensedLine, entry.level, parsedData); break; } case "inline": { const logId = this.showLogId ? this.config.config.logIdColor(`[${entry.id}]`) : ""; const fullData = entry.data ? formatInlineData( JSON.parse(entry.data), this.config.config, this.maxInlineDepth, !this.flattenNestedObjects, this.collapseArrays ) : ""; const expandedLine = `${timestamp} ${chevron}${logId ? `${logId} ` : ""}${message}${fullData ? ` ${fullData}` : ""}`; this.writeMessage(expandedLine, entry.level, parsedData); break; } case "expanded": { const logId = this.showLogId ? this.config.config.logIdColor(`[${entry.id}]`) : ""; const firstLine = `${timestamp} ${chevron}${logId ? `${logId} ` : ""}${message}`; this.writeMessage(firstLine, entry.level, parsedData); if (entry.data) { const jsonLines = render(JSON.parse(entry.data), { defaultIndentation: 2, keysColor: this.config.config.dataKeyColor, dashColor: this.config.config.dataKeyColor, numberColor: this.config.config.dataValueColor, stringColor: this.config.config.dataValueColor, multilineStringColor: this.config.config.dataValueColor, positiveNumberColor: this.config.config.dataValueColor, negativeNumberColor: this.config.config.dataValueColor, booleanColor: this.config.config.dataValueColor, nullUndefinedColor: this.config.config.dataValueColor, dateColor: this.config.config.dataValueColor, collapseArrays: this.collapseArrays }).split("\n"); for (const line of jsonLines) { if (line.trim() !== "") { this.writeMessage(` ${line}`, entry.level); } } } break; } default: { const logId = this.showLogId ? this.config.config.logIdColor(`[${entry.id}]`) : ""; const fullData = entry.data ? formatInlineData(JSON.parse(entry.data), this.config.config, this.maxInlineDepth, true, this.collapseArrays) : ""; const expandedLine = `${timestamp} ${chevron}${logId ? `${logId} ` : ""}${message}${fullData ? ` ${fullData}` : ""}`; this.writeMessage(expandedLine, entry.level, parsedData); break; } } } }; // src/SimplePrettyTerminalTransport.ts var SimplePrettyTerminalTransport = class extends LoggerlessTransport { /** Handles rendering and formatting of logs */ renderer; /** Configuration options */ config; /** * Creates a new SimplePrettyTerminalTransport instance. * * @param config - Configuration options for the transport */ constructor(config) { super(config); this.config = config; if (config.enabled === false) { return; } const maxInlineDepth = config.maxInlineDepth || 4; const theme = config.theme || moonlight; const viewMode = config.viewMode || "inline"; const showLogId = config.showLogId || false; const timestampFormat = config.timestampFormat || "HH:mm:ss.SSS"; const collapseArrays = config.collapseArrays !== false; const flattenNestedObjects = config.flattenNestedObjects !== false; const runtime = config.runtime; const includeDataInBrowserConsole = config.includeDataInBrowserConsole || false; const viewConfig = { colors: { trace: chalk4.gray, // Lowest level, used for verbose output debug: chalk4.blue, // Debug information info: chalk4.green, // Normal operation warn: chalk4.yellow, // Warning conditions error: chalk4.red, // Error conditions fatal: chalk4.bgRed.white, // Critical errors ...theme.colors }, logIdColor: theme.logIdColor || chalk4.dim, dataValueColor: theme.dataValueColor || chalk4.white, dataKeyColor: theme.dataKeyColor || chalk4.dim }; this.renderer = new SimpleView({ viewMode, maxInlineDepth, showLogId, timestampFormat, collapseArrays, flattenNestedObjects, runtime, includeDataInBrowserConsole, config: viewConfig }); } /** * Generates a random ID for each log entry. * Uses base36 encoding for compact, readable IDs. * * @returns A 6-character string ID * @example * "a1b2c3" // Example generated ID */ generateId() { return Math.random().toString(36).substring(2, 8); } /** * Main transport method that receives logs from LogLayer. * This method is called for each log event and handles: * - Generating a unique ID for the log * - Converting the log data to a storable format * - Rendering the log using the SimpleView renderer * * @param logLevel - The severity level of the log * @param messages - Array of message strings to be joined * @param data - Additional structured data to be logged * @param hasData - Whether the log includes additional data * @returns The original messages array */ shipToLogger({ logLevel, messages, data, hasData }) { if (this.config.enabled === false) { return messages; } const entry = { id: this.generateId(), timestamp: Date.now(), level: logLevel, message: messages.join(" "), data: hasData ? JSON.stringify(data) : null }; this.renderer.renderLogLine(entry); return messages; } /** * Changes the view mode for log display. * * @param viewMode - The new view mode to use */ setViewMode(viewMode) { if (this.config.enabled === false) { return; } const theme = this.config.theme || moonlight; const runtime = this.config.runtime; const viewConfig = { colors: { trace: chalk4.gray, debug: chalk4.blue, info: chalk4.green, warn: chalk4.yellow, error: chalk4.red, fatal: chalk4.bgRed.white, ...theme.colors }, logIdColor: theme.logIdColor || chalk4.dim, dataValueColor: theme.dataValueColor || chalk4.white, dataKeyColor: theme.dataKeyColor || chalk4.dim }; this.renderer = new SimpleView({ viewMode, maxInlineDepth: this.config.maxInlineDepth || 4, showLogId: this.config.showLogId || false, timestampFormat: this.config.timestampFormat || "HH:mm:ss.SSS", collapseArrays: this.config.collapseArrays !== false, flattenNestedObjects: this.config.flattenNestedObjects !== false, runtime, includeDataInBrowserConsole: this.config.includeDataInBrowserConsole || false, config: viewConfig }); } /** * Gets the current view mode. * * @returns The current view mode */ getViewMode() { return this.renderer.getViewMode(); } }; // src/index.ts export * from "chalk"; function getSimplePrettyTerminal(config) { return new SimplePrettyTerminalTransport(config); } export { SimplePrettyTerminalTransport, getSimplePrettyTerminal, moonlight, nature, neon, pastel, sunlight }; //# sourceMappingURL=index.js.map