Commit a50f59d5153a2b98845378b4ced1829c5e7ee582

Authored by yibo
1 parent e287643c

perf: billing.log date-sharding - write to billing-YYYY-MM-DD.log, read with 30-…

…day window, skip old files
Showing 1 changed file with 36 additions and 21 deletions
server/routes/agent-chat.js
... ... @@ -791,28 +791,42 @@ router.get('/quota', (req, res) => {
791 791 try {
792 792 const DATA_ROOT = path.resolve(__dirname, '../../data');
793 793 const records = [];
  794 + const daysBack = parseInt(String(req.query.days || '30')) || 30; // 默认读最近 30 天
  795 + const cutoff = new Date(Date.now() - daysBack * 86400000).toISOString();
794 796  
795   - // 读取所有 billing.log
  797 + // 读取 billing 日志(兼容旧 billing.log + 新 billing-YYYY-MM-DD.log)
796 798 const readBilling = (dir) => {
797   - const p = path.join(dir, 'billing.log');
798   - if (fs.existsSync(p)) {
799   - fs.readFileSync(p, 'utf-8').split('\n').forEach(line => {
800   - line = line.trim();
801   - if (line.startsWith('{')) {
802   - try { records.push(JSON.parse(line)); } catch(e) {}
803   - } else if (line) {
804   - // 旧的纯文本兼容: 2026-03-28T18:00:00.000Z | Agent: master | Conv: 123 | Cost: $0.123 | Duration: 123ms
805   - try {
806   - const parts = line.split('|').map(p => p.trim());
807   - const ts = parts[0];
808   - const ai = parts[1].replace('Agent:', '').trim();
809   - const ci = parts[2].replace('Conv:', '').trim();
810   - const cost = parseFloat(parts[3].replace('Cost: $', '').trim());
811   - const dur = parseInt(parts[4].replace('Duration:', '').replace('ms', '').trim());
812   - records.push({ timestamp: ts, agentId: ai, convId: ci, costUsd: cost || 0, durationMs: dur || 0, usage: { input: 0, output: 0 } });
813   - } catch(e) {}
814   - }
815   - });
  799 + if (!fs.existsSync(dir)) return;
  800 + const files = fs.readdirSync(dir).filter(f => f.startsWith('billing'));
  801 + for (const file of files) {
  802 + // 按日分片优化:跳过超出时间范围的文件
  803 + const dateMatch = file.match(/billing-(\d{4}-\d{2}-\d{2})\.log/);
  804 + if (dateMatch && dateMatch[1] < cutoff.substring(0, 10)) continue;
  805 +
  806 + const p = path.join(dir, file);
  807 + try {
  808 + fs.readFileSync(p, 'utf-8').split('\n').forEach(line => {
  809 + line = line.trim();
  810 + if (line.startsWith('{')) {
  811 + try {
  812 + const rec = JSON.parse(line);
  813 + if (rec.timestamp >= cutoff) records.push(rec);
  814 + } catch(e) {}
  815 + } else if (line) {
  816 + // 旧的纯文本兼容
  817 + try {
  818 + const parts = line.split('|').map(p => p.trim());
  819 + const ts = parts[0];
  820 + if (ts < cutoff) return;
  821 + const ai = parts[1].replace('Agent:', '').trim();
  822 + const ci = parts[2].replace('Conv:', '').trim();
  823 + const cost = parseFloat(parts[3].replace('Cost: $', '').trim());
  824 + const dur = parseInt(parts[4].replace('Duration:', '').replace('ms', '').trim());
  825 + records.push({ timestamp: ts, agentId: ai, convId: ci, costUsd: cost || 0, durationMs: dur || 0, usage: { input: 0, output: 0 } });
  826 + } catch(e) {}
  827 + }
  828 + });
  829 + } catch(e) {}
816 830 }
817 831 };
818 832  
... ... @@ -2226,7 +2240,8 @@ router.post(&#39;/:agentId/chat&#39;, async (req, res) =&gt; {
2226 2240 durationMs: event.params.duration_ms,
2227 2241 usage: event.params.usage || { input: 0, output: 0 }
2228 2242 };
2229   - fs.appendFileSync(path.join(billingDir, 'billing.log'), JSON.stringify(logEntry) + '\n', 'utf-8');
  2243 + const billingDate = new Date().toISOString().substring(0, 10);
  2244 + fs.appendFileSync(path.join(billingDir, `billing-${billingDate}.log`), JSON.stringify(logEntry) + '\n', 'utf-8');
2230 2245 } catch (err) {}
2231 2246 }
2232 2247 if (!fullOutput && event.params?.result) {
... ...