All files / modules/tts tts-logger.ts

0% Statements 0/101
0% Branches 0/1
0% Functions 0/1
0% Lines 0/101

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143                                                                                                                                                                                                                                                                                             
/**
 * TTS 模块专用日志
 *
 * 解决分散日志问题:
 * 1. console.log / logToFile 混用 → 统一到 logger
 * 2. tts-debug.log 只在本地开发有效 → 统一到 logs/tts.log
 * 3. 没有按 provider 分类 → 按 name 字段区分
 */
 
// 简易 console logger(logger.service 暂未创建)
const logger = {
  info: (msg: string) => console.log(msg),
  warn: (msg: string) => console.warn(msg),
  error: (msg: string) => console.error(msg),
  debug: (msg: string) => console.debug(msg),
};
import path from 'path';
import fs from 'fs';
 
const LOG_FILE = path.join(process.cwd(), 'logs', 'tts.log');
 
function ensureLogDir() {
  const dir = path.dirname(LOG_FILE);
  if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir, { recursive: true });
  }
}
 
function writeToFile(msg: string) {
  const timestamp = new Date().toISOString();
  const line = `[${timestamp}] ${msg}\n`;
  fs.appendFileSync(LOG_FILE, line, 'utf-8');
}
 
export const ttsLogger = {
  /** Provider 注册/注销 */
  registry(action: 'register' | 'unregister' | 'skip', providerName: string, detail?: string) {
    const msg = `[Registry] ${action === 'skip' ? '跳过' : action === 'register' ? '注册' : '注销'} ${providerName}${detail ? `: ${detail}` : ''}`;
    if (action === 'skip') {
      logger.warn(msg);
    } else {
      logger.info(msg);
    }
    writeToFile(msg);
  },
 
  /** 熔断器状态变化 */
  circuitBreaker(providerName: string, state: 'open' | 'half_open' | 'closed', detail?: string) {
    const icons = { open: '🔴', half_open: '🟡', closed: '🟢' };
    const msg = `[CircuitBreaker] ${icons[state]} ${providerName} 熔断器: ${state}${detail ? ` (${detail})` : ''}`;
    logger.warn(msg);
    writeToFile(msg);
  },
 
  /** 额度耗尽/恢复 */
  quota(providerName: string, action: 'exhausted' | 'recovered', reason?: string) {
    const msg = `[Quota] ${action === 'exhausted' ? '⚠️ 额度耗尽' : '✅ 额度恢复'} ${providerName}${reason ? `: ${reason}` : ''}`;
    if (action === 'exhausted') {
      logger.warn(msg);
    } else {
      logger.info(msg);
    }
    writeToFile(msg);
  },
 
  /** 音频生成开始 */
  synthesisStart(audioId: string, providerName: string, textLength: number, segmentCount: number) {
    const msg = `[Synth] 📝 开始合成 audioId=${audioId}, provider=${providerName}, 文本=${textLength}字, 分段=${segmentCount}`;
    logger.info(msg);
    writeToFile(msg);
  },
 
  /** 单段合成完成 */
  segmentDone(audioId: string, providerName: string, segmentIndex: number, filePath: string, ms: number) {
    const msg = `[Synth]   段落[${segmentIndex}] ✅ ${providerName} ${ms}ms → ${path.basename(filePath)}`;
    logger.debug(msg);
    writeToFile(msg);
  },
 
  /** 音频生成完成 */
  synthesisDone(audioId: string, providerName: string, duration: number, audioUrl: string, ms: number) {
    const msg = `[Synth] ✅ 完成 audioId=${audioId}, provider=${providerName}, 时长=${duration}s, 耗时=${(ms / 1000).toFixed(1)}s, url=${audioUrl.substring(0, 60)}...`;
    logger.info(msg);
    writeToFile(msg);
  },
 
  /** 音频生成失败 */
  synthesisFailed(audioId: string, providerName: string, error: string, willRetry: boolean) {
    const msg = `[Synth] ❌ 失败 audioId=${audioId}, provider=${providerName}, error=${error.substring(0, 100)}${willRetry ? ' (将重试)' : ' (不重试)'}`;
    logger.error(msg);
    writeToFile(msg);
  },
 
  /** Provider 降级切换 */
  fallback(fromProvider: string, toProvider: string, reason: string) {
    const msg = `[Fallback] 🔄 ${fromProvider} → ${toProvider}: ${reason}`;
    logger.warn(msg);
    writeToFile(msg);
  },
 
  /** 健康检查 */
  healthCheck(providerName: string, state: 'recovered' | 'still_down', detail?: string) {
    const msg = `[HealthCheck] ${state === 'recovered' ? '✅' : '❌'} ${providerName}${detail ? `: ${detail}` : ''}`;
    if (state === 'recovered') {
      logger.info(msg);
    } else {
      logger.warn(msg);
    }
    writeToFile(msg);
  },
 
  /** 错误日志 */
  error(label: string, error: string | Error, context?: Record<string, any>) {
    const errorMsg = error instanceof Error ? error.message : error;
    const msg = `[Error] ${label}: ${errorMsg}${context ? ` | ${JSON.stringify(context)}` : ''}`;
    logger.error(msg);
    writeToFile(msg);
  },
 
  /** 通用信息 */
  info(message: string) {
    logger.info(`[TTS] ${message}`);
    writeToFile(`[Info] ${message}`);
  },
 
  /** 通用警告 */
  warn(message: string) {
    logger.warn(`[TTS] ${message}`);
    writeToFile(`[Warn] ${message}`);
  },
 
  /** LRC 歌词生成(调试级) */
  lrc(message: string) {
    logger.debug(`[LRC] ${message}`);
    writeToFile(`[LRC] ${message}`);
  },
 
  /** Provider 信息(调试级) */
  debug(message: string) {
    logger.debug(`[TTS] ${message}`);
    writeToFile(`[Debug] ${message}`);
  },
};