@astreus-ai/astreus
Version:
AI Agent Framework with Chat Management
277 lines (232 loc) • 7.95 kB
text/typescript
/**
* Astreus AI - Logger Utility
* Provides colorful console logging functionality for the framework
*/
// ANSI color codes for terminal output
const colors = {
reset: "\x1b[0m",
bright: "\x1b[1m",
dim: "\x1b[2m",
underscore: "\x1b[4m",
blink: "\x1b[5m",
reverse: "\x1b[7m",
hidden: "\x1b[8m",
// 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",
gray: "\x1b[90m",
// 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",
};
// Framework constants
const FRAMEWORK_NAME = "Astreus";
const FRAMEWORK_VERSION = "0.1.0";
// Log levels
/* eslint-disable no-unused-vars */
export enum LogLevel {
DEBUG = 0,
INFO = 1,
SUCCESS = 2,
WARN = 3,
ERROR = 4,
NONE = 5,
}
/* eslint-enable no-unused-vars */
// Logger options
interface LoggerOptions {
level: LogLevel;
prefix: boolean;
colors: boolean;
lineBreak: boolean;
timestamp: boolean;
}
// Default options
const defaultOptions: LoggerOptions = {
level: LogLevel.INFO,
prefix: true,
colors: true,
lineBreak: false,
timestamp: false
};
// Current logger options
let options: LoggerOptions = { ...defaultOptions };
// Track the last log time to prevent duplicate timestamps
let lastLogTime = 0;
/**
* Create a formatted prefix for log messages
*/
function createPrefix(color: string): string {
if (!options.prefix) return '';
// Simple format: [FRAMEWORK]
return `${color}[${FRAMEWORK_NAME}]${colors.reset} `;
}
/**
* Get a timestamp string
*/
function getTimestamp(): string {
if (!options.timestamp) return '';
const now = Date.now();
// Only show timestamps when they change by at least 1 second
if (Math.abs(now - lastLogTime) < 1000) {
return '';
}
lastLogTime = now;
const date = new Date(now);
return `${colors.gray}[${date.toLocaleTimeString()}]${colors.reset} `;
}
/**
* Internal log function
*/
function log(level: LogLevel, color: string, ...messages: unknown[]): void {
if (level < options.level) return;
const prefix = createPrefix(color);
const timestamp = getTimestamp();
// Add line break before log entry if enabled (reduced usage)
if (options.lineBreak && level >= LogLevel.WARN) {
// Use safeConsole to handle console statements
safeConsole('log');
}
if (options.colors) {
// Apply color to text messages that are strings
const coloredMessages = messages.map(msg =>
typeof msg === 'string' ? `${color}${msg}${colors.reset}` : msg
);
// Use a direct string without extra spaces
safeConsole('log', `${timestamp}${prefix}${coloredMessages.join(' ')}`);
} else {
// Strip color codes using string replace with a function rather than regex with control chars
// This avoids the ESLint 'no-control-regex' error
const stripAnsi = (str: string): string => {
let result = '';
let inEscSeq = false;
for (let i = 0; i < str.length; i++) {
// Start of escape sequence
if (str[i] === '\u001b' && str[i+1] === '[') {
inEscSeq = true;
i++; // Skip the '['
continue;
}
// In escape sequence, wait for 'm' which ends ANSI color codes
if (inEscSeq) {
if (str[i] === 'm') {
inEscSeq = false;
}
continue;
}
// Normal character
result += str[i];
}
return result;
};
const strippedPrefix = stripAnsi(prefix);
safeConsole('log', `${timestamp}${strippedPrefix}${messages.join(' ')}`);
}
}
/**
* Safe console wrapper to avoid ESLint warnings
*/
function safeConsole(method: 'log' | 'info' | 'warn' | 'error', ...args: unknown[]): void {
// This function centralizes console usage and can be disabled by ESLint when needed
// eslint-disable-next-line no-console
if (method === 'log') console.log(...args);
// eslint-disable-next-line no-console
else if (method === 'info') console.info(...args);
// eslint-disable-next-line no-console
else if (method === 'warn') console.warn(...args);
// eslint-disable-next-line no-console
else if (method === 'error') console.error(...args);
}
/**
* Configure the logger
*/
export function configure(newOptions: Partial<LoggerOptions>): void {
options = { ...options, ...newOptions };
}
/**
* Print the framework banner
*/
export function printBanner(): void {
if (options.level > LogLevel.INFO) return;
safeConsole('log');
safeConsole('log', `${colors.cyan}${colors.bright}${FRAMEWORK_NAME} AI Framework v${FRAMEWORK_VERSION}${colors.reset}`);
safeConsole('log');
}
/**
* Public logging functions
*/
export const logger = {
debug: (...messages: unknown[]) => log(LogLevel.DEBUG, colors.gray, ...messages),
info: (...messages: unknown[]) => log(LogLevel.INFO, colors.blue, ...messages),
success: (...messages: unknown[]) => log(LogLevel.SUCCESS, colors.green, ...messages),
warn: (...messages: unknown[]) => log(LogLevel.WARN, colors.yellow, ...messages),
error: (...messages: unknown[]) => log(LogLevel.ERROR, colors.red, ...messages),
// Special formatted logs
task: (taskId: string, message: string, taskName?: string) => {
// If taskName is provided, use it; otherwise, just use "Task"
const prefix = taskName ? `Task [${taskName}]:` : 'Task:';
log(LogLevel.INFO, colors.magenta, `${prefix} ${message}`);
},
agent: (agentName: string, message: string) => {
log(LogLevel.INFO, colors.cyan, `Agent [${agentName}]: ${message}`);
},
database: (operation: string, message: string) => {
log(LogLevel.DEBUG, colors.blue, `${operation}: ${message}`);
},
memory: (operation: string, message: string) => {
log(LogLevel.DEBUG, colors.magenta, `${operation}: ${message}`);
},
session: (sessionId: string, message: string) => {
const shortId = sessionId.substring(0, 8);
log(LogLevel.INFO, colors.green, `${shortId}: ${message}`);
},
plugin: (pluginName: string, message: string) => {
// Capitalize first letter of plugin name and remove brackets
const capitalizedPluginName = pluginName.charAt(0).toUpperCase() + pluginName.slice(1);
log(LogLevel.INFO, colors.yellow, `${capitalizedPluginName}: ${message}`);
},
workflow: (workflowName: string, message: string) => {
log(LogLevel.INFO, colors.cyan, `${workflowName}: ${message}`);
},
// Progress indicators - these don't add line breaks to avoid disrupting the animation
startProgress: (message: string): NodeJS.Timeout => {
if (options.level > LogLevel.INFO) return setInterval(() => {}, 1000);
// Optionally add a line break before starting progress
if (options.lineBreak) {
safeConsole('log');
}
process.stdout.write(`${createPrefix(colors.blue)}${colors.blue}${message}`);
const chars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
let i = 0;
return setInterval(() => {
process.stdout.write(`\r${createPrefix(colors.blue)}${colors.blue}${message} ${colors.cyan}${chars[i]}${colors.reset}`);
i = (i + 1) % chars.length;
}, 100);
},
endProgress: (interval: NodeJS.Timeout, finalMessage?: string) => {
clearInterval(interval);
if (options.level > LogLevel.INFO) return;
if (finalMessage) {
process.stdout.write(`\r${createPrefix(colors.blue)}${colors.green}${finalMessage} ✓${colors.reset}\n`);
} else {
process.stdout.write(`\r${createPrefix(colors.blue)}${colors.green}Done ✓${colors.reset}\n`);
}
},
// Configure line breaks
setLineBreak: (enabled: boolean) => {
configure({ lineBreak: enabled });
},
};
export default logger;