Advanced (更新: 2026/6/2)

用 Claude Code 设计日志与监控:生产可观测性实战

用Claude Code落地结构化日志、OpenTelemetry、告警、健康检查和安全事故交接。

用 Claude Code 设计日志与监控:生产可观测性实战

先定义运行契约

如果只对 Claude Code 说“加日志”,它通常会增加一些 console.log。本地调试时这看起来有用,但生产故障中远远不够。真正的日志与监控基线要能回答:哪个用户动作触发了请求,哪个 requestId 贯穿了调用,哪个服务或依赖变慢,哪些信息可以安全交给下一位值班工程师。

OpenTelemetry Observability primer 把可观测性定义为从系统外部理解系统,并能回答未知问题的能力。What is OpenTelemetry? 也强调,OpenTelemetry 是供应商中立的计装层,不是监控后台本身。本文把这个原则转换成 Claude Code 提示词、可复制代码、仪表盘复查、告警规则和事故交接模板。

Masa 在一个小型结账 API 上试过,最初的提示词只是“让日志更详细”。Claude Code 为了保留上下文,建议在支付失败时记录整个 request body。它没有包含卡号,但可能泄露邮箱、优惠码、地址和客户备注。后来把禁止字段写在前面,只允许 requestIdtraceparent 和必要业务字段,审查重点就从“代码像不像”变成了“能不能处理真实生产数据”。

信号回答的问题给 Claude Code 的约束
日志发生了什么JSON、固定字段、PII 脱敏
指标严重到什么程度rate、p95、错误率
Trace时间花在哪里传递 traceparent,命名 span
健康检查依赖是否可用每个依赖返回状态和 latency

安全提示词与 CLAUDE.md

Claude Code 的 memory 文档说明,CLAUDE.md 会在会话开始时提供项目、个人或组织级指令。日志监控中,应该把日志级别、字段名、禁止字段、测试命令、仪表盘名称和事故交接格式写进去。站内可继续参考 CLAUDE.md 最佳实践,权限边界则要看官方 Claude Code permissionshooks

Claude Code task:
- Add observability to the checkout API only.
- Keep all changes inside src/checkout and tests/checkout.
- Use structured JSON logs with requestId and traceparent.
- Never log passwords, tokens, cookies, email, phone, address,
  raw prompt text, or full request/response bodies.
- Add tests proving redaction and requestId propagation.
- Add a /healthz report with database and cache latency.
- Add alert rules for 5xx rate, p95 latency, and redaction failure.
- Show a diff summary and remaining manual checks at the end.

这个提示词故意很窄。它不让 Claude Code 发明整套平台,不让它改无关路由,也不要求粘贴生产原始日志。它要求的是可审查、可回滚、可验证的运维差异。

结构化日志与请求关联 ID

OWASP Logging Cheat Sheet 把日志当成安全能力:要做代码审查、测试、访问控制和失败验证。Claude Code 的任务也应该包含脱敏测试、字段 schema 和日志失败时的行为,而不是只添加文字消息。

下面是无依赖的 JSON logger。保存为 structured-logger.mjs,用 Node.js 18 以上执行 node structured-logger.mjs

import { randomUUID } from "node:crypto";

const rank = { debug: 10, info: 20, warn: 30, error: 40 };
const current = process.env.LOG_LEVEL || "info";
const threshold = rank[current] ?? rank.info;

const secretKeys = [
  "password",
  "token",
  "authorization",
  "cookie",
  "set-cookie",
  "apikey",
];

function cleanText(value) {
  return String(value).replace(/[\r\n\t]/g, " ").slice(0, 500);
}

function redact(value) {
  if (Array.isArray(value)) return value.map(redact);
  if (!value || typeof value !== "object") return value;

  return Object.fromEntries(
    Object.entries(value).map(([key, item]) => {
      if (secretKeys.includes(key.toLowerCase())) {
        return [key, "[REDACTED]"];
      }
      return [key, redact(item)];
    }),
  );
}

export function log(level, message, fields = {}) {
  if ((rank[level] ?? 99) < threshold) return;

  const entry = {
    ts: new Date().toISOString(),
    level,
    service: process.env.SERVICE_NAME || "checkout-api",
    env: process.env.NODE_ENV || "development",
    requestId: fields.requestId || randomUUID(),
    msg: cleanText(message),
    ...redact(fields),
  };

  process.stdout.write(`${JSON.stringify(entry)}\n`);
}

log("info", "payment accepted", {
  requestId: "req_demo_001",
  userId: "user_123",
  amount: 4980,
  token: "sk_live_should_not_leak",
});

Web 应用中,还要把 request ID 写回响应头,并保存在异步上下文里。W3C Trace Context 说明了 traceparent 不存在时如何创建,存在时如何继续传递。建议把 x-request-id 作为应用层关联 ID,把 traceparent 作为分布式 trace 载体,不要混用。

import { AsyncLocalStorage } from "node:async_hooks";
import { randomUUID } from "node:crypto";
import type { Request, Response, NextFunction } from "express";
import { log } from "./structured-logger";

type RequestContext = {
  requestId: string;
  traceparent?: string;
  userId?: string;
};

const storage = new AsyncLocalStorage<RequestContext>();

export function getRequestContext() {
  return storage.getStore();
}

export function requestContext(
  req: Request,
  res: Response,
  next: NextFunction,
) {
  const started = performance.now();
  const user = (req as Request & { user?: { id?: string } }).user;
  const requestId =
    req.get("x-request-id") ||
    req.get("cf-ray") ||
    randomUUID();

  const context = {
    requestId,
    traceparent: req.get("traceparent"),
    userId: user?.id,
  };

  res.setHeader("x-request-id", requestId);

  storage.run(context, () => {
    res.on("finish", () => {
      const durationMs = Math.round(performance.now() - started);
      const level = res.statusCode >= 500
        ? "error"
        : res.statusCode >= 400
          ? "warn"
          : "info";

      log(level, "http request completed", {
        requestId,
        method: req.method,
        path: req.path,
        statusCode: res.statusCode,
        durationMs,
      });
    });

    next();
  });
}

日志级别要保持朴素:debug 用于本地临时细节,info 记录重要正常事件,warn 表示可恢复风险,error 表示需要调查。还要要求 Claude Code 不要因为日志失败改变业务流程。

OpenTelemetry 基础

OpenTelemetry 用来统一应用发出的 trace、metrics 和 logs,然后交给团队已有的后端。Node.js 设置请以官方 JavaScript Node.js guideJavaScript exporters guide 为准。

npm install @opentelemetry/sdk-node \
  @opentelemetry/auto-instrumentations-node \
  @opentelemetry/exporter-trace-otlp-proto \
  @opentelemetry/exporter-metrics-otlp-proto \
  @opentelemetry/sdk-metrics
const opentelemetry = require("@opentelemetry/sdk-node");
const {
  getNodeAutoInstrumentations,
} = require("@opentelemetry/auto-instrumentations-node");
const {
  OTLPTraceExporter,
} = require("@opentelemetry/exporter-trace-otlp-proto");
const {
  OTLPMetricExporter,
} = require("@opentelemetry/exporter-metrics-otlp-proto");
const {
  PeriodicExportingMetricReader,
} = require("@opentelemetry/sdk-metrics");

process.env.OTEL_SERVICE_NAME ||= "checkout-api";

const endpoint =
  process.env.OTEL_EXPORTER_OTLP_ENDPOINT ||
  "http://localhost:4318";

const sdk = new opentelemetry.NodeSDK({
  traceExporter: new OTLPTraceExporter({
    url: `${endpoint}/v1/traces`,
  }),
  metricReader: new PeriodicExportingMetricReader({
    exporter: new OTLPMetricExporter({
      url: `${endpoint}/v1/metrics`,
    }),
    exportIntervalMillis: 30000,
  }),
  instrumentations: [getNodeAutoInstrumentations()],
});

sdk.start();

process.on("SIGTERM", () => {
  sdk.shutdown().finally(() => process.exit(0));
});
flowchart LR
  A["用户操作"] --> B["应用"]
  B --> C["结构化日志"]
  B --> D["指标"]
  B --> E["Trace span"]
  C --> F["日志平台"]
  D --> G["告警规则"]
  E --> H["Trace 后端"]
  F --> I["事故交接"]
  G --> I
  H --> I

健康检查与告警

好的健康检查不只是 200 OK。它应该分别检查数据库、缓存、队列和关键外部 API,并返回每个依赖的 latency。不要返回连接字符串、token 或内部拓扑细节。

function timeout(ms) {
  return new Promise((_, reject) => {
    setTimeout(() => reject(new Error("timeout")), ms);
  });
}

export async function buildHealthReport(checks) {
  const started = Date.now();
  const results = {};

  for (const [name, check] of Object.entries(checks)) {
    const before = Date.now();
    try {
      await Promise.race([check(), timeout(800)]);
      results[name] = {
        status: "ok",
        latencyMs: Date.now() - before,
      };
    } catch (error) {
      const message =
        error instanceof Error ? error.message : String(error);
      results[name] = {
        status: "fail",
        latencyMs: Date.now() - before,
        reason: message.slice(0, 120),
      };
    }
  }

  const failed = Object.values(results)
    .filter((item) => item.status === "fail")
    .length;

  return {
    status: failed ? "degraded" : "ok",
    uptimeSec: Math.round(process.uptime()),
    totalLatencyMs: Date.now() - started,
    checks: results,
  };
}

告警最好基于时间窗口内的比例和分位点,不要因为单条 error 日志就叫醒人。下面是 Prometheus 风格的例子。

groups:
  - name: checkout-api
    rules:
      - alert: CheckoutHigh5xxRate
        expr: |
          sum(rate(http_requests_total{
            service="checkout-api",
            status_code=~"5.."
          }[5m]))
          /
          sum(rate(http_requests_total{
            service="checkout-api"
          }[5m])) > 0.02
        for: 10m
        labels:
          severity: page
        annotations:
          summary: "Checkout 5xx rate is above 2%"

      - alert: CheckoutP95LatencyHigh
        expr: |
          histogram_quantile(
            0.95,
            sum by (le) (
              rate(http_request_duration_seconds_bucket{
                service="checkout-api"
              }[5m])
            )
          ) > 1.5
        for: 15m
        labels:
          severity: ticket
        annotations:
          summary: "Checkout p95 latency is above 1.5s"

三个具体场景

第一个场景是电商结账 API。保留 orderIdrequestIdpaymentProvideramount,但不要记录卡信息、邮箱、地址和访问 token。告警要区分 5xx 率、支付失败率和支付服务商 p95 延迟。

第二个场景是 SaaS 管理后台。登录、权限变更、成员邀请、套餐变更应该进入审计日志;邀请邮件正文和个人备注不应该进入日志。要求 Claude Code 把审计日志和应用日志分开,并添加 RBAC 测试。

第三个场景是媒体站或博客 CMS。需要追踪发布、CTA 点击、咨询表单成功、图片生成失败和翻译缺失。只看 PV 不会改善收入。把 cta_clickgenerate_lead 分开,并结合 Claude Code 分析实现 复查仪表盘。

如果你的系统是微服务,也要阅读 Claude Code 微服务设计service.name、路由名和环境标签不一致时,OpenTelemetry 数据会很难搜索。

失败模式与事故交接

常见失败包括:为了上下文记录整个 request body;日志消息全是自由文本;requestIdcorrelationIdtraceIdtransactionId 同时存在却没有主键;/healthz 不检查依赖却永远返回 ok;告警建好了但没人知道该看哪个 dashboard。

Claude Code 还有一个额外风险:生产原始日志会变成 prompt 输入。请求它帮忙排查事故前,先脱敏日志、汇总指标,只分享短 request ID 和 trace ID。安全调查流程可以配合 Claude Code permissions guide

{
  "incident_id": "INC-2026-06-02-001",
  "severity": "SEV2",
  "owner": "oncall-api",
  "customer_impact": "Checkout errors for some card payments",
  "first_seen": "2026-06-02T09:15:00+09:00",
  "request_ids": ["req_7f3a", "req_8b21"],
  "trace_ids": ["7bba9f33312b3dbb8b2c2c62bb7abe2d"],
  "dashboards": ["Checkout API overview"],
  "current_hypothesis": "Payment provider latency spike",
  "actions_taken": ["Disabled checkout_v2 feature flag"],
  "next_checks": ["Compare p95 latency by region"],
  "do_not_do": ["Do not paste raw customer data into prompts"]
}

仪表盘复查、CTA 与验证结果

每周复查仪表盘:前五类错误、p95 延迟、日志量增长、误报、脱敏失败和未关闭事故。每月选一个真实事故回看,确认新值班人员能否从日志、指标和 trace 更快找到原因。

个人可以先用 免费 Claude Code cheatsheet 固定日常检查。需要可复用提示词和设置材料时看 产品页。团队要把日志标准、CLAUDE.md、权限、CI 检查和事故流程一起落地,可以从 Claude Code 培训与咨询 开始。

按本文流程试过后,最有效的是先写禁止字段。structured-logger.mjs 会把 token 变成 [REDACTED],也会把换行压成一行。模拟缓存失败时,健康检查变成 degraded;事故交接里只放 requestIdtraceId,审查对话明显更短。

#Claude Code #日志监控 #OpenTelemetry #结构化日志 #可观测性
免费

免费 PDF: Claude Code 速查表

输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。

我们会妥善保护你的信息,不发送垃圾邮件。

把 Claude Code 变成真正能带来结果的工作流

先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。

Masa

关于作者

Masa

专注 Claude Code 实务流程、团队导入和内容转化的工程师。