Files
Sankofa/src/lib/logger.ts

95 lines
2.1 KiB
TypeScript
Raw Normal View History

/**
* Structured logging utility
*/
type LogLevel = 'debug' | 'info' | 'warn' | 'error'
interface LogEntry {
level: LogLevel
message: string
timestamp: string
context?: Record<string, unknown>
error?: Error
}
class Logger {
private logLevel: LogLevel
constructor() {
this.logLevel = (process.env.NODE_ENV === 'development' ? 'debug' : 'info') as LogLevel
}
private shouldLog(level: LogLevel): boolean {
const levels: LogLevel[] = ['debug', 'info', 'warn', 'error']
return levels.indexOf(level) >= levels.indexOf(this.logLevel)
}
private formatEntry(entry: LogEntry): string {
const { level, message, timestamp, context, error } = entry
const parts = [`[${timestamp}]`, `[${level.toUpperCase()}]`, message]
if (context && Object.keys(context).length > 0) {
parts.push(JSON.stringify(context))
}
if (error) {
parts.push(`\nError: ${error.message}`)
if (error.stack) {
parts.push(`\nStack: ${error.stack}`)
}
}
return parts.join(' ')
}
private log(level: LogLevel, message: string, context?: Record<string, unknown>, error?: Error) {
if (!this.shouldLog(level)) {
return
}
const entry: LogEntry = {
level,
message,
timestamp: new Date().toISOString(),
context,
error,
}
const formatted = this.formatEntry(entry)
switch (level) {
case 'debug':
console.debug(formatted)
break
case 'info':
console.info(formatted)
break
case 'warn':
console.warn(formatted)
break
case 'error':
console.error(formatted)
break
}
}
debug(message: string, context?: Record<string, unknown>) {
this.log('debug', message, context)
}
info(message: string, context?: Record<string, unknown>) {
this.log('info', message, context)
}
warn(message: string, context?: Record<string, unknown>) {
this.log('warn', message, context)
}
error(message: string, error?: Error, context?: Record<string, unknown>) {
this.log('error', message, context, error)
}
}
export const logger = new Logger()