- Created logger utility with environment-based gating (lib/utils/logger.ts) - Replaced 517 console.log statements with logger.log (71% reduction) - Fixed import paths in 15 files (resolved comment-trapped imports) - Added DEBUG_LOGS=false to .env - Achieves 71% immediate log reduction (517/731 statements) - Expected 90% reduction in production when deployed Impact: Reduced I/O blocking, lower log volume in production Risk: LOW (easy rollback, non-invasive) Phase: Phase 1, Task 1.1 (Quick Wins - Console.log Production Gating) Files changed: - NEW: lib/utils/logger.ts (production-safe logging) - NEW: scripts/replace-console-logs.js (automation tool) - Modified: 15 lib/*.ts files (console.log → logger.log) - Modified: .env (DEBUG_LOGS=false) Next: Task 1.2 (Image Size Optimization)
152 lines
4.1 KiB
TypeScript
152 lines
4.1 KiB
TypeScript
/**
|
|
* Persistent File Logger - Survives Container Restarts
|
|
* Critical for debugging database save failures and system issues
|
|
*/
|
|
|
|
import * as fs from 'fs'
|
|
import { logger } from '../utils/logger'
|
|
import * as path from 'path'
|
|
|
|
const LOG_DIR = '/app/logs'
|
|
const ERROR_LOG = path.join(LOG_DIR, 'errors.log')
|
|
const TRADE_LOG = path.join(LOG_DIR, 'trades.log')
|
|
const MAX_LOG_SIZE = 10 * 1024 * 1024 // 10MB per log file
|
|
|
|
// Ensure log directory exists
|
|
function ensureLogDir() {
|
|
try {
|
|
if (!fs.existsSync(LOG_DIR)) {
|
|
fs.mkdirSync(LOG_DIR, { recursive: true })
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to create log directory:', error)
|
|
}
|
|
}
|
|
|
|
// Rotate log if too large
|
|
function rotateLogIfNeeded(logPath: string) {
|
|
try {
|
|
if (fs.existsSync(logPath)) {
|
|
const stats = fs.statSync(logPath)
|
|
if (stats.size > MAX_LOG_SIZE) {
|
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
|
|
const rotatedPath = `${logPath}.${timestamp}`
|
|
fs.renameSync(logPath, rotatedPath)
|
|
logger.log(`📦 Rotated log: ${rotatedPath}`)
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to rotate log:', error)
|
|
}
|
|
}
|
|
|
|
// Format log entry
|
|
function formatLogEntry(level: string, message: string, details?: any): string {
|
|
const timestamp = new Date().toISOString()
|
|
const detailsStr = details ? `\n${JSON.stringify(details, null, 2)}` : ''
|
|
return `[${timestamp}] ${level}: ${message}${detailsStr}\n`
|
|
}
|
|
|
|
// Append to log file
|
|
function appendToLog(logPath: string, entry: string) {
|
|
try {
|
|
ensureLogDir()
|
|
rotateLogIfNeeded(logPath)
|
|
fs.appendFileSync(logPath, entry, 'utf8')
|
|
} catch (error) {
|
|
console.error(`Failed to write to ${logPath}:`, error)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Log critical error (database failures, position issues)
|
|
*/
|
|
export function logCriticalError(message: string, details?: any) {
|
|
const entry = formatLogEntry('CRITICAL', message, details)
|
|
console.error('🔴 CRITICAL:', message, details || '')
|
|
appendToLog(ERROR_LOG, entry)
|
|
}
|
|
|
|
/**
|
|
* Log trade execution (success or failure)
|
|
*/
|
|
export function logTradeExecution(
|
|
success: boolean,
|
|
tradeDetails: {
|
|
symbol: string
|
|
direction: string
|
|
entryPrice: number
|
|
positionSize: number
|
|
transactionSignature?: string
|
|
error?: string
|
|
}
|
|
) {
|
|
const level = success ? 'SUCCESS' : 'FAILURE'
|
|
const message = success
|
|
? `Trade opened: ${tradeDetails.symbol} ${tradeDetails.direction} @ $${tradeDetails.entryPrice}`
|
|
: `Trade failed: ${tradeDetails.symbol} ${tradeDetails.direction} - ${tradeDetails.error}`
|
|
|
|
const entry = formatLogEntry(level, message, tradeDetails)
|
|
logger.log(success ? '✅' : '❌', message)
|
|
appendToLog(TRADE_LOG, entry)
|
|
}
|
|
|
|
/**
|
|
* Log database operation (create, update, query)
|
|
*/
|
|
export function logDatabaseOperation(
|
|
operation: string,
|
|
success: boolean,
|
|
details: {
|
|
table?: string
|
|
recordId?: string
|
|
error?: any
|
|
retryAttempt?: number
|
|
}
|
|
) {
|
|
const level = success ? 'DB_SUCCESS' : 'DB_FAILURE'
|
|
const message = success
|
|
? `${operation} succeeded: ${details.table || 'unknown'}`
|
|
: `${operation} failed: ${details.error?.message || 'Unknown error'}`
|
|
|
|
const entry = formatLogEntry(level, message, details)
|
|
logger.log(success ? '💾' : '❌', message)
|
|
appendToLog(ERROR_LOG, entry)
|
|
}
|
|
|
|
/**
|
|
* Read recent error logs (for debugging)
|
|
*/
|
|
export function getRecentErrors(lines: number = 50): string[] {
|
|
try {
|
|
if (!fs.existsSync(ERROR_LOG)) {
|
|
return []
|
|
}
|
|
|
|
const content = fs.readFileSync(ERROR_LOG, 'utf8')
|
|
const allLines = content.split('\n').filter(line => line.trim())
|
|
return allLines.slice(-lines)
|
|
} catch (error) {
|
|
console.error('Failed to read error log:', error)
|
|
return []
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read recent trade logs
|
|
*/
|
|
export function getRecentTrades(lines: number = 50): string[] {
|
|
try {
|
|
if (!fs.existsSync(TRADE_LOG)) {
|
|
return []
|
|
}
|
|
|
|
const content = fs.readFileSync(TRADE_LOG, 'utf8')
|
|
const allLines = content.split('\n').filter(line => line.trim())
|
|
return allLines.slice(-lines)
|
|
} catch (error) {
|
|
console.error('Failed to read trade log:', error)
|
|
return []
|
|
}
|
|
}
|