| |
| |
| |
|
|
|
|
| import { appConfig } from '../config/app'
|
|
|
| |
| |
|
|
| export enum LogLevel {
|
| DEBUG = 0,
|
| INFO = 1,
|
| WARN = 2,
|
| ERROR = 3
|
| }
|
|
|
| |
| |
|
|
| const LOG_LEVEL_MAP: Record<string, LogLevel> = { |
| debug: LogLevel.DEBUG, |
| info: LogLevel.INFO, |
| warn: LogLevel.WARN, |
| error: LogLevel.ERROR |
| } |
|
|
| function parseBooleanEnv(value: string | undefined): boolean | undefined { |
| if (typeof value !== 'string') { |
| return undefined |
| } |
|
|
| const normalized = value.trim().toLowerCase() |
| if (['1', 'true', 'yes', 'on'].includes(normalized)) { |
| return true |
| } |
| if (['0', 'false', 'no', 'off'].includes(normalized)) { |
| return false |
| } |
| return undefined |
| } |
|
|
| |
| |
| |
| const currentLogLevel = LOG_LEVEL_MAP[appConfig.logging.level] || LogLevel.INFO |
| const prodSummaryLogOnly = |
| appConfig.nodeEnv === 'production' && |
| (parseBooleanEnv(process.env.PROD_SUMMARY_LOG_ONLY) ?? true) |
|
|
| |
| |
|
|
| function formatTimestamp(): string {
|
| return new Date().toISOString()
|
| }
|
|
|
| |
| |
|
|
| function safeStringify(obj: any, maxLength?: number): string {
|
| try {
|
| const seen = new WeakSet()
|
| const json = JSON.stringify(obj, (key, value) => {
|
|
|
| if (typeof value === 'object' && value !== null) {
|
| if (seen.has(value)) {
|
| return '[Circular]'
|
| }
|
| seen.add(value)
|
| }
|
| return value
|
| }, 2)
|
|
|
|
|
| if (maxLength && json.length > maxLength) {
|
| return json.slice(0, maxLength) + `...(truncated, total ${json.length} chars)`
|
| }
|
|
|
| return json
|
| } catch (error) {
|
| return '[Serialization Error]'
|
| }
|
| }
|
|
|
| |
| |
|
|
| function formatMessage(level: string, message: string, meta?: any): string {
|
| const timestamp = formatTimestamp()
|
|
|
|
|
| let metaStr = ''
|
| if (meta) {
|
|
|
| const largeTextFields = ['content', 'code', 'response', 'aiResponse', 'stdout', 'stderr', 'output']
|
| const hasLargeText = largeTextFields.some(field => field in meta)
|
|
|
| if (hasLargeText) {
|
|
|
| metaStr = ` ${safeStringify(meta)}`
|
| } else {
|
|
|
| metaStr = ` ${safeStringify(meta, 5000)}`
|
| }
|
| }
|
|
|
| if (appConfig.logging.pretty) {
|
|
|
| const colors: Record<string, string> = {
|
| DEBUG: '\x1b[36m',
|
| INFO: '\x1b[32m',
|
| WARN: '\x1b[33m',
|
| ERROR: '\x1b[31m'
|
| }
|
| const reset = '\x1b[0m'
|
| const color = colors[level] || ''
|
|
|
| return `${color}[${timestamp}] ${level}${reset}: ${message}${metaStr}`
|
| } else {
|
|
|
| return JSON.stringify({
|
| timestamp,
|
| level,
|
| message,
|
| ...meta
|
| })
|
| }
|
| }
|
|
|
| |
| |
|
|
| class Logger { |
| private context?: string |
|
|
| constructor(context?: string) {
|
| this.context = context
|
| }
|
|
|
| |
| |
|
|
| private addContext(meta?: any): any { |
| if (!this.context) return meta |
| return { ...meta, context: this.context } |
| } |
|
|
| private shouldEmit(level: LogLevel, meta?: any): boolean { |
| if (!prodSummaryLogOnly) { |
| return true |
| } |
|
|
| |
| if (level >= LogLevel.WARN) { |
| return true |
| } |
|
|
| return meta && typeof meta === 'object' && meta._logType === 'job_summary' |
| } |
|
|
| |
| |
|
|
| debug(message: string, meta?: any): void { |
| const contextMeta = this.addContext(meta) |
| if (currentLogLevel <= LogLevel.DEBUG && this.shouldEmit(LogLevel.DEBUG, contextMeta)) { |
| console.debug(formatMessage('DEBUG', message, contextMeta)) |
| } |
| } |
|
|
| |
| |
|
|
| info(message: string, meta?: any): void { |
| const contextMeta = this.addContext(meta) |
| if (currentLogLevel <= LogLevel.INFO && this.shouldEmit(LogLevel.INFO, contextMeta)) { |
| console.log(formatMessage('INFO', message, contextMeta)) |
| } |
| } |
|
|
| |
| |
|
|
| warn(message: string, meta?: any): void { |
| const contextMeta = this.addContext(meta) |
| if (currentLogLevel <= LogLevel.WARN && this.shouldEmit(LogLevel.WARN, contextMeta)) { |
| console.warn(formatMessage('WARN', message, contextMeta)) |
| } |
| } |
|
|
| |
| |
|
|
| error(message: string, meta?: any): void { |
| const contextMeta = this.addContext(meta) |
| if (currentLogLevel <= LogLevel.ERROR && this.shouldEmit(LogLevel.ERROR, contextMeta)) { |
| console.error(formatMessage('ERROR', message, contextMeta)) |
| } |
| } |
|
|
| |
| |
|
|
| child(context: string): Logger {
|
| const childContext = this.context ? `${this.context}:${context}` : context
|
| return new Logger(childContext)
|
| }
|
| }
|
|
|
| |
| |
|
|
| export const logger = new Logger()
|
|
|
| |
| |
|
|
| export function createLogger(context: string): Logger {
|
| return new Logger(context)
|
| } |
|
|
|
|