mirror of
https://github.com/simstudioai/sim.git
synced 2026-02-02 10:45:17 -05:00
310 lines
8.1 KiB
TypeScript
310 lines
8.1 KiB
TypeScript
/**
|
|
* @sim/logger
|
|
*
|
|
* Framework-agnostic logging utilities for the Sim platform.
|
|
* Provides standardized console logging with environment-aware configuration.
|
|
*/
|
|
import chalk from 'chalk'
|
|
|
|
/**
|
|
* LogLevel enum defines the severity levels for logging
|
|
*
|
|
* DEBUG: Detailed information, typically useful only for diagnosing problems
|
|
* INFO: Confirmation that things are working as expected
|
|
* WARN: Indication that something unexpected happened
|
|
* ERROR: Error events that might still allow the application to continue running
|
|
*/
|
|
export enum LogLevel {
|
|
DEBUG = 'DEBUG',
|
|
INFO = 'INFO',
|
|
WARN = 'WARN',
|
|
ERROR = 'ERROR',
|
|
}
|
|
|
|
/**
|
|
* Logger configuration options
|
|
*/
|
|
export interface LoggerConfig {
|
|
/** Minimum log level to display */
|
|
logLevel?: LogLevel | string
|
|
/** Whether to colorize output */
|
|
colorize?: boolean
|
|
/** Whether logging is enabled */
|
|
enabled?: boolean
|
|
}
|
|
|
|
const getNodeEnv = (): string => {
|
|
if (typeof process !== 'undefined' && process.env) {
|
|
return process.env.NODE_ENV || 'development'
|
|
}
|
|
return 'development'
|
|
}
|
|
|
|
const getLogLevel = (): string | undefined => {
|
|
if (typeof process !== 'undefined' && process.env) {
|
|
return process.env.LOG_LEVEL
|
|
}
|
|
return undefined
|
|
}
|
|
|
|
/**
|
|
* Get the minimum log level from environment variable or use defaults
|
|
* - Development: DEBUG (show all logs)
|
|
* - Production: ERROR (only show errors, but can be overridden by LOG_LEVEL env var)
|
|
* - Test: ERROR (only show errors in tests)
|
|
*/
|
|
const getMinLogLevel = (): LogLevel => {
|
|
const logLevelEnv = getLogLevel()
|
|
if (logLevelEnv && Object.values(LogLevel).includes(logLevelEnv as LogLevel)) {
|
|
return logLevelEnv as LogLevel
|
|
}
|
|
|
|
const nodeEnv = getNodeEnv()
|
|
switch (nodeEnv) {
|
|
case 'development':
|
|
return LogLevel.DEBUG
|
|
case 'production':
|
|
return LogLevel.ERROR
|
|
case 'test':
|
|
return LogLevel.ERROR
|
|
default:
|
|
return LogLevel.DEBUG
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Configuration for different environments
|
|
*/
|
|
const getLogConfig = () => {
|
|
const nodeEnv = getNodeEnv()
|
|
const minLevel = getMinLogLevel()
|
|
|
|
switch (nodeEnv) {
|
|
case 'development':
|
|
return {
|
|
enabled: true,
|
|
minLevel,
|
|
colorize: true,
|
|
}
|
|
case 'production':
|
|
return {
|
|
enabled: true,
|
|
minLevel,
|
|
colorize: false,
|
|
}
|
|
case 'test':
|
|
return {
|
|
enabled: false,
|
|
minLevel,
|
|
colorize: false,
|
|
}
|
|
default:
|
|
return {
|
|
enabled: true,
|
|
minLevel,
|
|
colorize: true,
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Format objects for logging
|
|
*/
|
|
const formatObject = (obj: unknown, isDev: boolean): string => {
|
|
try {
|
|
if (obj instanceof Error) {
|
|
const errorObj: Record<string, unknown> = {
|
|
message: obj.message,
|
|
stack: isDev ? obj.stack : undefined,
|
|
name: obj.name,
|
|
}
|
|
for (const key of Object.keys(obj)) {
|
|
if (!(key in errorObj)) {
|
|
errorObj[key] = (obj as unknown as Record<string, unknown>)[key]
|
|
}
|
|
}
|
|
return JSON.stringify(errorObj, null, isDev ? 2 : 0)
|
|
}
|
|
return JSON.stringify(obj, null, isDev ? 2 : 0)
|
|
} catch {
|
|
return '[Circular or Non-Serializable Object]'
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Logger class for standardized console logging
|
|
*
|
|
* Provides methods for logging at different severity levels
|
|
* and handles formatting, colorization, and environment-specific behavior.
|
|
*/
|
|
export class Logger {
|
|
private module: string
|
|
private config: ReturnType<typeof getLogConfig>
|
|
private isDev: boolean
|
|
|
|
/**
|
|
* Create a new logger for a specific module
|
|
* @param module The name of the module (e.g., 'OpenAIProvider', 'AgentBlockHandler')
|
|
* @param overrideConfig Optional configuration overrides
|
|
*/
|
|
constructor(module: string, overrideConfig?: LoggerConfig) {
|
|
this.module = module
|
|
this.config = getLogConfig()
|
|
this.isDev = getNodeEnv() === 'development'
|
|
|
|
// Apply overrides if provided
|
|
if (overrideConfig) {
|
|
if (overrideConfig.logLevel !== undefined) {
|
|
const level =
|
|
typeof overrideConfig.logLevel === 'string'
|
|
? (overrideConfig.logLevel as LogLevel)
|
|
: overrideConfig.logLevel
|
|
if (Object.values(LogLevel).includes(level)) {
|
|
this.config.minLevel = level
|
|
}
|
|
}
|
|
if (overrideConfig.colorize !== undefined) {
|
|
this.config.colorize = overrideConfig.colorize
|
|
}
|
|
if (overrideConfig.enabled !== undefined) {
|
|
this.config.enabled = overrideConfig.enabled
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determines if a log at the given level should be displayed
|
|
*/
|
|
private shouldLog(level: LogLevel): boolean {
|
|
if (!this.config.enabled) return false
|
|
|
|
if (getNodeEnv() === 'production' && typeof window !== 'undefined') {
|
|
return false
|
|
}
|
|
|
|
const levels = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR]
|
|
const minLevelIndex = levels.indexOf(this.config.minLevel)
|
|
const currentLevelIndex = levels.indexOf(level)
|
|
|
|
return currentLevelIndex >= minLevelIndex
|
|
}
|
|
|
|
/**
|
|
* Format arguments for logging, converting objects to JSON strings
|
|
*/
|
|
private formatArgs(args: unknown[]): unknown[] {
|
|
return args.map((arg) => {
|
|
if (arg === null || arg === undefined) return arg
|
|
if (typeof arg === 'object') return formatObject(arg, this.isDev)
|
|
return arg
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Internal method to log a message with the specified level
|
|
*/
|
|
private log(level: LogLevel, message: string, ...args: unknown[]) {
|
|
if (!this.shouldLog(level)) return
|
|
|
|
const timestamp = new Date().toISOString()
|
|
const formattedArgs = this.formatArgs(args)
|
|
|
|
if (this.config.colorize) {
|
|
let levelColor: (text: string) => string
|
|
const moduleColor = chalk.cyan
|
|
const timestampColor = chalk.gray
|
|
|
|
switch (level) {
|
|
case LogLevel.DEBUG:
|
|
levelColor = chalk.blue
|
|
break
|
|
case LogLevel.INFO:
|
|
levelColor = chalk.green
|
|
break
|
|
case LogLevel.WARN:
|
|
levelColor = chalk.yellow
|
|
break
|
|
case LogLevel.ERROR:
|
|
levelColor = chalk.red
|
|
break
|
|
}
|
|
|
|
const coloredPrefix = `${timestampColor(`[${timestamp}]`)} ${levelColor(`[${level}]`)} ${moduleColor(`[${this.module}]`)}`
|
|
|
|
if (level === LogLevel.ERROR) {
|
|
console.error(coloredPrefix, message, ...formattedArgs)
|
|
} else {
|
|
console.log(coloredPrefix, message, ...formattedArgs)
|
|
}
|
|
} else {
|
|
const prefix = `[${timestamp}] [${level}] [${this.module}]`
|
|
|
|
if (level === LogLevel.ERROR) {
|
|
console.error(prefix, message, ...formattedArgs)
|
|
} else {
|
|
console.log(prefix, message, ...formattedArgs)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Log a debug message
|
|
*
|
|
* Use for detailed information useful during development and debugging.
|
|
* These logs are only shown in development environment by default.
|
|
*/
|
|
debug(message: string, ...args: unknown[]) {
|
|
this.log(LogLevel.DEBUG, message, ...args)
|
|
}
|
|
|
|
/**
|
|
* Log an info message
|
|
*
|
|
* Use for general information about application operation.
|
|
*/
|
|
info(message: string, ...args: unknown[]) {
|
|
this.log(LogLevel.INFO, message, ...args)
|
|
}
|
|
|
|
/**
|
|
* Log a warning message
|
|
*
|
|
* Use for potentially problematic situations that don't cause operation failure.
|
|
*/
|
|
warn(message: string, ...args: unknown[]) {
|
|
this.log(LogLevel.WARN, message, ...args)
|
|
}
|
|
|
|
/**
|
|
* Log an error message
|
|
*
|
|
* Use for error events that might still allow the application to continue.
|
|
*/
|
|
error(message: string, ...args: unknown[]) {
|
|
this.log(LogLevel.ERROR, message, ...args)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a logger for a specific module
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* import { createLogger } from '@sim/logger'
|
|
*
|
|
* const logger = createLogger('MyComponent')
|
|
*
|
|
* logger.debug('Initializing component', { props })
|
|
* logger.info('Component mounted')
|
|
* logger.warn('Deprecated prop used', { propName })
|
|
* logger.error('Failed to fetch data', error)
|
|
* ```
|
|
*
|
|
* @param module The name of the module
|
|
* @param config Optional configuration overrides
|
|
* @returns A Logger instance
|
|
*/
|
|
export function createLogger(module: string, config?: LoggerConfig): Logger {
|
|
return new Logger(module, config)
|
|
}
|