@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
JavaScript
// 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