lsh-framework
Version:
A powerful, extensible shell with advanced job management, database persistence, and modern CLI features
275 lines (274 loc) • 7.71 kB
JavaScript
/**
* Logging Framework
* Centralized logging utility with support for different log levels,
* structured logging, and environment-based configuration.
*/
export var LogLevel;
(function (LogLevel) {
LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
LogLevel[LogLevel["INFO"] = 1] = "INFO";
LogLevel[LogLevel["WARN"] = 2] = "WARN";
LogLevel[LogLevel["ERROR"] = 3] = "ERROR";
LogLevel[LogLevel["NONE"] = 4] = "NONE";
})(LogLevel || (LogLevel = {}));
/**
* ANSI color codes for terminal output
*/
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
dim: '\x1b[2m',
// Foreground colors
black: '\x1b[30m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
cyan: '\x1b[36m',
white: '\x1b[37m',
// Background colors
bgBlack: '\x1b[40m',
bgRed: '\x1b[41m',
bgGreen: '\x1b[42m',
bgYellow: '\x1b[43m',
bgBlue: '\x1b[44m',
bgMagenta: '\x1b[45m',
bgCyan: '\x1b[46m',
bgWhite: '\x1b[47m',
};
/**
* Get log level from environment variable
*/
function getLogLevelFromEnv() {
const level = process.env.LSH_LOG_LEVEL?.toUpperCase();
switch (level) {
case 'DEBUG':
return LogLevel.DEBUG;
case 'INFO':
return LogLevel.INFO;
case 'WARN':
return LogLevel.WARN;
case 'ERROR':
return LogLevel.ERROR;
case 'NONE':
return LogLevel.NONE;
default:
return process.env.NODE_ENV === 'production' ? LogLevel.INFO : LogLevel.DEBUG;
}
}
/**
* Logger class for structured logging
*/
export class Logger {
config;
constructor(config) {
this.config = {
level: config?.level ?? getLogLevelFromEnv(),
enableTimestamp: config?.enableTimestamp ?? true,
enableColors: config?.enableColors ?? (!process.env.NO_COLOR && process.stdout.isTTY),
enableJSON: config?.enableJSON ?? (process.env.LSH_LOG_FORMAT === 'json'),
context: config?.context,
};
}
/**
* Create a child logger with a specific context
*/
child(context) {
return new Logger({
...this.config,
context: this.config.context ? `${this.config.context}:${context}` : context,
});
}
/**
* Set log level dynamically
*/
setLevel(level) {
this.config.level = level;
}
/**
* Check if a log level is enabled
*/
isLevelEnabled(level) {
return level >= this.config.level;
}
/**
* Format timestamp
*/
formatTimestamp() {
const now = new Date();
return now.toISOString();
}
/**
* Get level name string
*/
getLevelName(level) {
switch (level) {
case LogLevel.DEBUG:
return 'DEBUG';
case LogLevel.INFO:
return 'INFO';
case LogLevel.WARN:
return 'WARN';
case LogLevel.ERROR:
return 'ERROR';
default:
return 'UNKNOWN';
}
}
/**
* Get color for log level
*/
getLevelColor(level) {
switch (level) {
case LogLevel.DEBUG:
return colors.cyan;
case LogLevel.INFO:
return colors.green;
case LogLevel.WARN:
return colors.yellow;
case LogLevel.ERROR:
return colors.red;
default:
return colors.white;
}
}
/**
* Format log entry as JSON
*/
formatJSON(entry) {
const obj = {
timestamp: entry.timestamp.toISOString(),
level: this.getLevelName(entry.level),
message: entry.message,
};
if (entry.context) {
obj.context = entry.context;
}
if (entry.metadata && Object.keys(entry.metadata).length > 0) {
obj.metadata = entry.metadata;
}
if (entry.error) {
obj.error = {
name: entry.error.name,
message: entry.error.message,
stack: entry.error.stack,
};
}
return JSON.stringify(obj);
}
/**
* Format log entry as text
*/
formatText(entry) {
const parts = [];
// Timestamp
if (this.config.enableTimestamp) {
const timestamp = this.formatTimestamp();
parts.push(this.config.enableColors ? `${colors.dim}${timestamp}${colors.reset}` : timestamp);
}
// Level
const levelName = this.getLevelName(entry.level);
const levelColor = this.getLevelColor(entry.level);
const formattedLevel = this.config.enableColors
? `${levelColor}${levelName.padEnd(5)}${colors.reset}`
: levelName.padEnd(5);
parts.push(formattedLevel);
// Context
if (entry.context) {
const formattedContext = this.config.enableColors
? `${colors.magenta}[${entry.context}]${colors.reset}`
: `[${entry.context}]`;
parts.push(formattedContext);
}
// Message
parts.push(entry.message);
// Metadata
if (entry.metadata && Object.keys(entry.metadata).length > 0) {
const metadataStr = JSON.stringify(entry.metadata);
const formattedMetadata = this.config.enableColors
? `${colors.dim}${metadataStr}${colors.reset}`
: metadataStr;
parts.push(formattedMetadata);
}
return parts.join(' ');
}
/**
* Core logging method
*/
log(level, message, metadata, error) {
if (!this.isLevelEnabled(level)) {
return;
}
const entry = {
timestamp: new Date(),
level,
message,
context: this.config.context,
metadata,
error,
};
const output = this.config.enableJSON
? this.formatJSON(entry)
: this.formatText(entry);
// Output to appropriate stream
if (level >= LogLevel.ERROR) {
console.error(output);
if (error?.stack) {
console.error(error.stack);
}
}
else if (level >= LogLevel.WARN) {
console.warn(output);
}
else {
// INFO and DEBUG go to stdout
// Using console.log here is intentional for the logger itself
// eslint-disable-next-line no-console
console.log(output);
}
}
/**
* Debug level logging
*/
debug(message, metadata) {
this.log(LogLevel.DEBUG, message, metadata);
}
/**
* Info level logging
*/
info(message, metadata) {
this.log(LogLevel.INFO, message, metadata);
}
/**
* Warning level logging
*/
warn(message, metadata) {
this.log(LogLevel.WARN, message, metadata);
}
/**
* Error level logging
*/
error(message, error, metadata) {
const err = error instanceof Error ? error : undefined;
const errorMetadata = error && !(error instanceof Error) ? { error } : undefined;
this.log(LogLevel.ERROR, message, { ...metadata, ...errorMetadata }, err);
}
}
/**
* Default logger instance
*/
export const logger = new Logger();
/**
* Create a logger with a specific context
*/
export function createLogger(context, config) {
return new Logger({
...config,
context,
});
}
/**
* Export for default usage
*/
export default logger;