Skip to content

日志系统设计

没有日志,一切改进都是盲的。日志是自修复、自迭代、自进化的数据基础。

为什么日志从第一天就要有

没有日志时有日志时
"感觉最近质量下降了""过去 7 天平均分从 72 降到 64,主要失分在 typography"
"Pipeline 好像变慢了""content_gen phase 平均耗时从 12s 涨到 18s,因为切换了模型"
"这个 bug 又出现了""429 错误本月出现 8 次,其中 6 次在 10:00-11:00 时段"
"不知道花了多少钱""本周消耗 240K tokens,$3.2,平均每页 $0.16"

日志不是"有了再说"的东西。缺少早期日志意味着缺少基线,后续无法判断改进还是退化。


三级日志架构

L1: Phase 级日志(实时)

每次 pipeline 运行,每个 phase 产生一条记录。

typescript
interface PhaseLog {
  phase: string;          // 'research' | 'kit' | 'content' | 'judge' | 'fix'
  status: 'success' | 'failed' | 'skipped';
  startTime: string;      // ISO 8601
  duration: number;       // ms
  input: {
    summary: string;      // 1-2 sentence,不是完整输入
  };
  output: {
    summary: string;
    metrics: Record<string, number>;
  };
  error?: {
    type: 'api_error' | 'quality_failure' | 'timeout' | 'validation';
    message: string;
    retryable: boolean;
    solutionMatched?: string;  // 指向 solutions/ 的文件名
  };
  llmCalls: LLMCallLog[];
}

interface LLMCallLog {
  model: string;
  purpose: string;        // 'content_gen' | 'judge' | 'site_analysis'
  inputTokens: number;
  outputTokens: number;
  latency: number;        // ms
  cached: boolean;
}

存储: docs/harness/logs/runs/<date>-<topic>.jsonl,每行一个 PhaseLog。

L2: Run 级日志(汇总)

每次 pipeline 完成,追加一行到 metrics/quality-tracker.jsonl

typescript
interface RunSummary {
  runId: string;
  date: string;            // YYYY-MM-DD
  topic: string;
  outcome: 'success' | 'partial' | 'failed';
  qualityScore: number;
  duration: number;        // ms, total
  cost: {
    tokens: number;
    usd: number;
  };
  iterations: number;      // judge-fix 循环次数
  config: {
    mode: string;
    model: string;
    preset: string;
  };
  phases: Record<string, {  // phase name → summary
    duration: number;
    status: string;
  }>;
}

存储: docs/harness/metrics/quality-tracker.jsonl,长期追加,不清理。

L3: 趋势洞察(周期性)

非自动产生,由人或 agent 定期分析 L1+L2 数据后总结。

分析维度:

  • 质量趋势: 近 N 次运行的平均分是在上升还是下降
  • 性能趋势: 各 phase 耗时变化
  • 成本趋势: token 消耗和美元成本
  • 故障模式: 最常见的错误类型和重复频率
  • 模型对比: 不同模型在相同任务上的性价比

日志的三个用途

用途 1: 自修复

Pipeline phase 失败
  → Logger 记录错误类型 + 消息
  → 自修复模块搜索 solutions/,匹配 error.type + keywords
    → 匹配成功 → 应用 solution 中的修复步骤 → 重试
    → 匹配失败 → 报告给人类 → 事后创建新 solution

关键:匹配不需要精确,模糊匹配(error type + 关键词)就够了。Solution 文件的 ## Symptoms 部分就是用来匹配的。

用途 2: 自迭代

积累了 20+ 次运行日志后
  → 人/Agent 分析 quality-tracker.jsonl
  → 发现规律:
    - "content_gen phase 失败率 30%,主要原因是 char limit violation"
      → 改进: 在 prompt 中加入更明确的字数限制
      → 产出: 新 ADR 或 prompt 调整
    - "judge phase 平均给分偏低,多次 fix 后仍然 < 60"
      → 改进: 校准 judge 的评分标准
      → 产出: 新 Solution

用途 3: 自进化

模型从 v3 升级到 v4 后
  → 对比相同任务在新旧模型下的日志
  → 发现: "旧模型需要 3 次 fix 循环,新模型 1 次就达标"
  → 推论: 某些 fix 约束可能不再需要
  → 行动: 衰减检测实验 → 退休过时约束

实现参考

最小可用的 Logger(TypeScript)

typescript
import { appendFileSync, mkdirSync } from 'fs';
import { join } from 'path';

const LOGS_DIR = 'docs/harness/logs/runs';
const METRICS_FILE = 'docs/harness/metrics/quality-tracker.jsonl';

export function createRunLogger(topic: string) {
  const runId = crypto.randomUUID();
  const date = new Date().toISOString().slice(0, 10);
  const logFile = join(LOGS_DIR, `${date}-${topic}.jsonl`);
  
  mkdirSync(LOGS_DIR, { recursive: true });

  const phases: Array<Record<string, unknown>> = [];
  let runStart = Date.now();

  return {
    logPhase(log: Record<string, unknown>) {
      const entry = { runId, ...log };
      appendFileSync(logFile, JSON.stringify(entry) + '\n');
      phases.push(entry);
    },

    logLLMCall(call: Record<string, unknown>) {
      // 可以附加到当前 phase 或独立记录
      appendFileSync(logFile, JSON.stringify({ runId, type: 'llm_call', ...call }) + '\n');
    },

    finalize(outcome: string, qualityScore: number) {
      const summary = {
        runId,
        date,
        topic,
        outcome,
        qualityScore,
        duration: Date.now() - runStart,
        phases: phases.map(p => ({
          phase: p.phase,
          duration: p.duration,
          status: p.status,
        })),
      };
      
      mkdirSync(join('docs/harness/metrics'), { recursive: true });
      appendFileSync(METRICS_FILE, JSON.stringify(summary) + '\n');
      
      return summary;
    },
  };
}

使用示例

typescript
const logger = createRunLogger('ai-code-review');

// Phase 开始
const start = Date.now();
const result = await runResearch(topic);
logger.logPhase({
  phase: 'research',
  status: 'success',
  duration: Date.now() - start,
  output: { summary: `Found ${result.sources.length} sources` },
});

// LLM 调用
logger.logLLMCall({
  model: 'gemini-3.1-flash-lite',
  purpose: 'content_gen',
  inputTokens: 1200,
  outputTokens: 3400,
  latency: 8200,
});

// Pipeline 完成
logger.finalize('success', 72);

日志文件管理

文件类型保留策略大小预估
runs/*.jsonl保留最近 30 天,之后归档~5KB/次运行
quality-tracker.jsonl永久保留~200B/次运行
L3 洞察报告永久保留手动产出

查询示例

bash
# 最近 10 次运行的质量分
tail -10 docs/harness/metrics/quality-tracker.jsonl | jq '.qualityScore'

# 所有失败的 phase
cat docs/harness/logs/runs/*.jsonl | jq 'select(.status == "failed")'

# 某个 topic 的所有 LLM 调用成本
cat docs/harness/logs/runs/*-ai-code-review.jsonl | \
  jq 'select(.type == "llm_call") | .inputTokens + .outputTokens'

# 本周总成本
cat docs/harness/metrics/quality-tracker.jsonl | \
  jq 'select(.date >= "2026-04-01") | .cost.usd' | \
  paste -sd+ | bc

观测工具选择

如果需要更专业的观测平台:

场景推荐理由
个人项目 / 起步阶段JSONL 文件(本方案)零依赖,够用
需要可视化 dashboardLangfuse (self-hosted)开源免费,支持 trace
快速集成、不想自建Helicone15 分钟 proxy 集成
复杂多 agent 系统AgentOpsSession replay 能力
企业级、已有 DatadogDatadog LLM Observability集成已有监控

本方案(JSONL 文件)的优势:

  • 零依赖,Git 可追踪
  • Agent 可以直接 cat + jq 分析
  • 不需要额外的服务或 API key
  • 随时可以迁移到更专业的平台(JSONL 是通用格式)