@kansnpms/console-log-pipe-cli
Version:
Global CLI tool for Console Log Pipe - Real-time log streaming server and management
333 lines (285 loc) • 8.54 kB
JavaScript
/**
* LogFormatter - Formats log entries for CLI display
*/
const chalk = require('chalk');
class LogFormatter {
/**
* Format a log entry for display
*/
static formatLog(logData, options = {}) {
const { format = 'text', color = true, timestamp = true } = options;
switch (format) {
case 'json':
return JSON.stringify(logData, null, 2);
case 'table':
return this.formatLogAsTable(logData, { color, timestamp });
case 'text':
default:
return this.formatLogAsText(logData, { color, timestamp });
}
}
/**
* Format an error entry for display
*/
static formatError(errorData, options = {}) {
const { format = 'text', color = true, timestamp = true } = options;
switch (format) {
case 'json':
return JSON.stringify(errorData, null, 2);
case 'text':
default:
return this.formatErrorAsText(errorData, { color, timestamp });
}
}
/**
* Format a network entry for display
*/
static formatNetwork(networkData, options = {}) {
const { format = 'text', color = true, timestamp = true } = options;
switch (format) {
case 'json':
return JSON.stringify(networkData, null, 2);
case 'text':
default:
return this.formatNetworkAsText(networkData, { color, timestamp });
}
}
/**
* Format log as text
*/
static formatLogAsText(logData, options) {
const { color, timestamp } = options;
const parts = [];
// Timestamp
if (timestamp && logData.timestamp) {
const time = new Date(logData.timestamp).toLocaleTimeString();
parts.push(color ? chalk.gray(time) : time);
}
// Log level
const level = (logData.level || 'log').toUpperCase();
const levelFormatted = color ? this.colorizeLevel(level) : `[${level}]`;
parts.push(levelFormatted);
// Application name
if (logData.appName) {
const appName = color
? chalk.cyan(`[${logData.appName}]`)
: `[${logData.appName}]`;
parts.push(appName);
}
// Message
let message = logData.message || '';
if (logData.args && logData.args.length > 0) {
const argsStr = logData.args
.map(arg =>
typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
)
.join(' ');
message = message ? `${message} ${argsStr}` : argsStr;
}
parts.push(message);
// Stack trace for errors
if (logData.stack && logData.level === 'error') {
parts.push(`\n${color ? chalk.gray(logData.stack) : logData.stack}`);
}
return parts.join(' ');
}
/**
* Format error as text
*/
static formatErrorAsText(errorData, options) {
const { color, timestamp } = options;
const parts = [];
// Timestamp
if (timestamp && errorData.timestamp) {
const time = new Date(errorData.timestamp).toLocaleTimeString();
parts.push(color ? chalk.gray(time) : time);
}
// Error indicator
const errorLabel = color ? chalk.red.bold('[ERROR]') : '[ERROR]';
parts.push(errorLabel);
// Application name
if (errorData.appName) {
const appName = color
? chalk.cyan(`[${errorData.appName}]`)
: `[${errorData.appName}]`;
parts.push(appName);
}
// Error message
const message = errorData.message || errorData.error || 'Unknown error';
parts.push(color ? chalk.red(message) : message);
// Error details
if (errorData.filename) {
parts.push(
color
? chalk.gray(
`at ${errorData.filename}:${errorData.lineno}:${errorData.colno}`
)
: `at ${errorData.filename}:${errorData.lineno}:${errorData.colno}`
);
}
// Stack trace
if (errorData.stack) {
parts.push(`\n${color ? chalk.gray(errorData.stack) : errorData.stack}`);
}
// Error category (AI-friendly)
if (errorData.category) {
parts.push(
color
? chalk.yellow(`[${errorData.category}]`)
: `[${errorData.category}]`
);
}
return parts.join(' ');
}
/**
* Format network request as text
*/
static formatNetworkAsText(networkData, options) {
const { color, timestamp } = options;
const parts = [];
// Timestamp
if (timestamp && networkData.timestamp) {
const time = new Date(networkData.timestamp).toLocaleTimeString();
parts.push(color ? chalk.gray(time) : time);
}
// Network indicator
const networkLabel = color ? chalk.blue.bold('[NET]') : '[NET]';
parts.push(networkLabel);
// Application name
if (networkData.appName) {
const appName = color
? chalk.cyan(`[${networkData.appName}]`)
: `[${networkData.appName}]`;
parts.push(appName);
}
// HTTP method and URL
const method = networkData.method || 'GET';
const url = networkData.url || 'unknown';
const methodFormatted = color ? this.colorizeHttpMethod(method) : method;
parts.push(`${methodFormatted} ${url}`);
// Status code
if (networkData.status) {
const statusFormatted = color
? this.colorizeStatusCode(networkData.status)
: networkData.status;
parts.push(statusFormatted);
}
// Duration
if (networkData.duration !== undefined) {
const duration = `${networkData.duration}ms`;
parts.push(color ? chalk.gray(duration) : duration);
}
// Size
if (networkData.size !== undefined) {
const size = this.formatBytes(networkData.size);
parts.push(color ? chalk.gray(size) : size);
}
return parts.join(' ');
}
/**
* Format log as table row
*/
static formatLogAsTable(logData, options) {
const { color, timestamp } = options;
return [
timestamp && logData.timestamp
? new Date(logData.timestamp).toLocaleTimeString()
: '',
color
? this.colorizeLevel(logData.level || 'log')
: (logData.level || 'log').toUpperCase(),
logData.appName || '',
logData.message || '',
];
}
/**
* Colorize log level
*/
static colorizeLevel(level) {
const levelUpper = level.toUpperCase();
switch (levelUpper) {
case 'ERROR':
return chalk.red.bold(`[${levelUpper}]`);
case 'WARN':
return chalk.yellow.bold(`[${levelUpper}]`);
case 'INFO':
return chalk.blue.bold(`[${levelUpper}]`);
case 'DEBUG':
return chalk.gray.bold(`[${levelUpper}]`);
case 'LOG':
default:
return chalk.white.bold(`[${levelUpper}]`);
}
}
/**
* Colorize HTTP method
*/
static colorizeHttpMethod(method) {
const methodUpper = method.toUpperCase();
switch (methodUpper) {
case 'GET':
return chalk.green(methodUpper);
case 'POST':
return chalk.blue(methodUpper);
case 'PUT':
return chalk.yellow(methodUpper);
case 'DELETE':
return chalk.red(methodUpper);
case 'PATCH':
return chalk.magenta(methodUpper);
default:
return chalk.white(methodUpper);
}
}
/**
* Colorize HTTP status code
*/
static colorizeStatusCode(status) {
const statusNum = parseInt(status, 10);
if (statusNum >= 200 && statusNum < 300) {
return chalk.green(status);
} else if (statusNum >= 300 && statusNum < 400) {
return chalk.yellow(status);
} else if (statusNum >= 400 && statusNum < 500) {
return chalk.red(status);
} else if (statusNum >= 500) {
return chalk.red.bold(status);
} else {
return chalk.white(status);
}
}
/**
* Format bytes to human readable format
*/
static formatBytes(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
}
/**
* Truncate text to specified length
*/
static truncate(text, maxLength = 100) {
if (text.length <= maxLength) {
return text;
}
return `${text.substring(0, maxLength - 3)}...`;
}
/**
* Format duration in milliseconds to human readable format
*/
static formatDuration(ms) {
if (ms < 1000) {
return `${ms}ms`;
} else if (ms < 60000) {
return `${(ms / 1000).toFixed(1)}s`;
} else if (ms < 3600000) {
return `${(ms / 60000).toFixed(1)}m`;
} else {
return `${(ms / 3600000).toFixed(1)}h`;
}
}
}
module.exports = LogFormatter;