structured-logging
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseStructured Logging Skill
结构化日志技能
Two tiers. Tier 1 is non-negotiable. Tier 2 requires judgment. Rule 0 overrides both.
分为两个层级。第一层级为强制要求,第二层级需酌情判断。规则0优先级高于两者。
Rule 0 — Follow the project first, always
规则0 — 始终优先遵循项目现有规范
Before adding any log, check the codebase: existing logger (Winston/Pino,
structlog/loguru, Serilog/zap…)? Existing field naming ( vs
)? Existing redaction/PII-scrubbing utility? Existing audit-log
pipeline (e.g. a dedicated table/service instead of stdout)?
If the project already has a pattern for something below, use it — it wins
over every default in this skill. Only apply the defaults below on a new
project, or where the project has no established pattern yet. Never rename an
established field ( → ); reuse it.
user_iduserIdaudit_logsstatus_codestatusCodePreserve the pipeline: never bypass, replace, or wrap existing logging
infrastructure — transports, formatters, tracing integrations, audit
pipelines. Don't into a logger that already accepts
structured objects, don't introduce a second logger, don't fall back to
// "just this once."
JSON.stringify()console.logprintfmt.PrintlnThis skill covers backend/service code (APIs, jobs, workers). Client-side/UI
logging is out of scope.
添加任何日志前,请先检查代码库:是否已有日志工具(Winston/Pino、structlog/loguru、Serilog/zap…)?是否已有字段命名规范( 还是 )?是否已有脱敏/PII清理工具?是否已有审计日志流水线(例如专用的表/服务而非标准输出)?**如果项目已针对以下内容制定了规范,请遵循该规范——它优先于本技能中的所有默认规则。**仅在新项目或项目无既定规范时,才应用以下默认规则。切勿重命名已有的字段(如改为),请直接复用。
user_iduserIdaudit_logsstatus_codestatusCode**保留现有流水线:**切勿绕过、替换或包装现有的日志基础设施——包括传输器、格式化器、追踪集成、审计流水线。若日志工具已支持结构化对象,请勿使用;切勿引入第二个日志工具;切勿“仅此一次”退而使用//。
JSON.stringify()console.logprintfmt.Println本技能适用于后端/服务代码(API、任务、工作进程)。客户端/UI日志不在本技能范围内。
Tier 1 — Mandatory: catch and log every exception
第一层级 — 强制要求:捕获并记录所有异常
- Every exception that can occur MUST be caught (locally, or at a global unhandled-exception boundary) and logged. Never let it disappear silently or be re-thrown without a log.
- Exception log must include: (operation/function name),
op(error,message,type),stack, and acorrelation_idof the key variables present at failure time — redacted per the internal-state rule below (blocklist + auto-mask), not the external-input allowlist. The goal: explain why it failed, not just that it failed.state_dump - Level: for unhandled/system failures (5xx, DB/queue down, panics).
ERRORfor expected/recoverable failures (validation errors, 4xx, failed-then-retried). Never log expected user-input errors asWARN— it drowns real alerts.ERROR - This tier applies everywhere, always. No judgment call needed.
- 所有可能发生的异常必须被捕获(本地捕获或通过全局未处理异常边界捕获)并记录。切勿让异常无声消失,或未记录就重新抛出。
- 异常日志必须包含:(操作/函数名称)、
op(error、message、type)、stack,以及故障发生时关键变量的correlation_id——需遵循下文内部状态规则进行脱敏(黑名单+自动掩码),而非外部输入的白名单规则。目标是解释为何失败,而非仅记录发生了失败。state_dump - 日志级别:未处理/系统故障(5xx、数据库/队列宕机、程序崩溃)使用;预期可恢复故障(验证错误、4xx、失败后重试)使用
ERROR。切勿将预期的用户输入错误记录为WARN——这会淹没真正的告警信息。ERROR - 本层级适用于所有场景,无需判断。
Tier 2 — Judgment-based: log what could actually cause hard-to-find bugs
第二层级 — 酌情判断:记录可能导致难以排查的Bug的内容
Categories below are hints about where risk tends to live, not a checklist
to log every instance of. Before adding a non-error log line, ask: "If
this specific piece of code produces a wrong result, will I need this log
line to figure out why?" Only add it if the honest answer is yes.
Lean toward logging: untrusted/unvalidated input, logic that can silently
produce a wrong result without throwing, non-trivial multi-condition
branching, an external call that can fail/be slow, irreversible or costly side
effects (money, hard-to-undo state changes).
Lean toward not logging: getters/setters/simple mappers, a branch so simple
the code already makes it obvious, per-iteration logging inside a loop.
When you do log, capture decision-relevant values, not the fact code ran
( not );
for state mutations, capture before → after → trigger.
branch="loyalty_bonus", discount=15"entering calculateDiscount"Loop policy: never log / per iteration. / per
item IS allowed when that item has its own try/catch and needs independent
investigation (e.g. batch import). Always end the loop with one summary log
(, , up to 5 ).
INFODEBUGWARNERRORtotal_processedtotal_failedsample_failed_idsAudit vs operational: business actions with legal/compliance weight
(money moved, permission changed, record deleted, admin action) are audit
events — emit immediately, /, never sampled, never truncated
beyond secret-masking. Everything else is operational and may be sampled/
truncated/batched into a wide event as below.
INFOWARNSelf-check on log count, not a hard cap: if a function ends up with more
than ~5 log lines, stop and re-justify each one against the question above —
don't cut a legitimately-needed log just to hit a number, and don't keep a
weak one just because you're under budget.
以下分类是风险高发区域的提示,而非需要逐一记录的清单。**添加非错误日志行前,请自问:“如果这段代码产生错误结果,我是否需要这条日志来排查原因?”**只有当答案为是时,才添加该日志。
倾向于记录:不可信/未验证的输入、可能无声产生错误结果而不抛出异常的逻辑、复杂的多条件分支、可能失败/缓慢的外部调用、不可逆或高成本的副作用(资金变动、难以撤销的状态变更)。
倾向于不记录:getter/setter/简单映射器、逻辑简单到代码已清晰体现的分支、循环内的逐次日志。
记录时,请捕获与决策相关的值,而非代码执行的事实(如记录而非“进入calculateDiscount函数”);对于状态变更,请捕获变更前→变更后→触发条件。
branch="loyalty_bonus", discount=15**循环规则:**切勿在循环体内记录/日志。当循环内的每个条目有独立的try/catch且需要单独排查时(如批量导入),允许为每个条目记录/。循环结束后必须记录一条汇总日志(包含、,最多5个)。
INFODEBUGWARNERRORtotal_processedtotal_failedsample_failed_ids审计日志 vs 操作日志:具有法律/合规权重的业务操作(资金转移、权限变更、记录删除、管理员操作)属于审计事件——需立即记录,级别为/,不得采样,除敏感信息掩码外不得截断。其他所有日志均为操作日志,可按下文所述进行采样/截断/合并为宽事件。
INFOWARN**日志数量自查(非硬性限制):**若一个函数最终包含超过约5条日志,请停止并重新根据上述问题验证每条日志的必要性——不要为了达标而删除真正需要的日志,也不要保留不必要的日志。
How to log (mechanics)
日志记录实操规范
- Correlation & trace propagation: if OpenTelemetry/APM context already
exists (/
trace_id), reuse it — don't create a parallelspan_id. Otherwise extract in priority order: W3Ccorrelation_id→traceparent/X-Request-IDheader → generate a UUID. Every entry point (HTTP middleware, queue consumer, cron/CLI main) sets this once and passes it through the whole call chain, including outgoing HTTP/DB/queue calls.X-Correlation-ID - Structured, not string-concatenated: one JSON object per line, fixed
field names (project convention, default ).
snake_case - Emit timing: normal successful flow accumulates into one "wide event" at exit. Errors, irreversible state changes, audit events, and failed external calls emit immediately with everything accumulated so far — don't wait, the process may crash first.
- Streaming / long-lived connections (WebSocket, SSE, gRPC stream):
"wide event at exit" doesn't apply — a connection can live for hours. Emit
at start and
connectedat end; WARN/ERROR immediately per message/segment failure; keepalive = metric, not log. Full pattern indisconnected.reference/domain-specific.md - Redaction — hybrid, not one rule for everything:
- External input / request payload / user-facing data → allowlist (default to not logging; explicitly list safe fields).
- Internal state / exception context / local variables at crash time →
blocklist + auto-mask (, case-insensitive) + truncate strings > ~400 chars with
password|token|secret|authorization| credit_card|ssn|api_key|private_key|otp|pin. This is what makes Tier 1'soriginal_lengthuseful instead of empty.state_dump - Never dump a whole object () — extract fields.
logger.info(user)
- Collections: log + ≤5 sample ids, never the full array.
count - Logger must be injectable (parameter/DI), not a hardcoded global, so tests can swap in a no-op logger.
- Performance-critical path (marked ): only
# performance-critical+ minimal fields.ERROR - High-frequency INFO (health checks, polling): respect a configurable sampling rate — 100% of WARN/ERROR/audit, sampled success.
- Metrics vs logging: high-frequency numeric/boolean observations (cache hit/miss, queue depth, heartbeat, per-request counters) belong in metrics, not logs — unless folded as one field into a single wide event already being emitted. Logging these at high frequency is a common way AI silently 10x's log volume.
- Non-blocking: logging must never block the hot path — serialize/truncate
large before emission.
state_dump - High-cardinality: don't use raw URLs with ids, full emails, filenames,
prompts, or UUIDs as index/search labels — see
for the rule and safe alternatives.
reference/field-dictionary.md - Targeted debug (optional): prefer enabling only for a specific
DEBUG/correlation_idallow-list rather than globally in production.user_id - Business events: name (
domain.entity.action); add a short human-readableorder.payment.completedfield so logs stay skimmable by eye.msg - Output: single-line JSON to stdout (stderr for ERROR). Append
ERROR/FATAL to only when
logs.err.txt(local non-containerized dev) — never a hard requirement.LOG_TO_FILE=true - Domain-specific fields (LLM calls, idempotency keys, retry/backoff,
transaction boundaries, feature flags, concurrency ids): see
— don't inline these into every function by default, apply only when that domain is actually in play.
reference/domain-specific.md
- 关联与追踪传播:若已存在OpenTelemetry/APM上下文(/
trace_id),请复用该上下文——不要创建并行的span_id。否则按以下优先级提取:W3Ccorrelation_id→traceparent/X-Request-ID请求头 → 生成UUID。每个入口点(HTTP中间件、队列消费者、定时任务/CLI主程序)只需设置一次,并在整个调用链中传递,包括对外的HTTP/数据库/队列调用。X-Correlation-ID - **结构化而非字符串拼接:**每行一条JSON对象,字段名固定(遵循项目规范,默认使用)。
snake_case - 记录时机:正常成功流程在退出时合并为一条“宽事件”。错误、不可逆状态变更、审计事件、失败的外部调用需立即记录已累积的所有信息——切勿等待,进程可能提前崩溃。
- 流/长连接(WebSocket、SSE、gRPC流):“退出时记录宽事件”规则不适用——连接可能持续数小时。连接建立时记录,断开时记录
connected;消息/段失败时立即记录disconnected/WARN;心跳属于指标,而非日志。完整模式请参考ERROR。reference/domain-specific.md - 脱敏——混合规则,而非一刀切:
- 外部输入/请求负载/面向用户的数据 → 白名单规则(默认不记录,明确列出安全字段)。
- 内部状态/异常上下文/崩溃时的局部变量 → 黑名单+自动掩码(匹配,不区分大小写)+ 截断超过约400字符的字符串并记录
password|token|secret|authorization|credit_card|ssn|api_key|private_key|otp|pin。这能让第一层级的original_length发挥作用,而非为空。state_dump - 切勿直接记录整个对象(如)——请提取所需字段。
logger.info(user)
- **集合类型:**记录+ 最多5个样本ID,切勿记录完整数组。
count - 日志工具必须可注入(通过参数/依赖注入),而非硬编码的全局实例,以便测试时可替换为无操作日志工具。
- 性能关键路径(标记为):仅记录
# performance-critical级别及最少字段。ERROR - 高频INFO日志(健康检查、轮询):遵循可配置的采样率——/
WARN/审计事件保持100%采样,成功事件可采样。ERROR - 指标 vs 日志:高频数值/布尔型观测数据(缓存命中/未命中、队列深度、心跳、逐请求计数器)属于指标,而非日志——除非作为一个字段合并到已有的单条宽事件中。高频记录此类数据是AI悄悄让日志量增加10倍的常见原因。
- **非阻塞:**日志记录不得阻塞核心路径——在记录前序列化/截断大型。
state_dump - **高基数:**切勿将包含ID的原始URL、完整邮箱、文件名、提示词或UUID用作索引/搜索标签——规则及安全替代方案请参考。
reference/field-dictionary.md - 定向调试(可选):在生产环境中,优先针对特定/
correlation_id白名单启用user_id级别,而非全局启用。DEBUG - **业务事件:**命名格式为(如
domain.entity.action);添加简短的人类可读order.payment.completed字段,以便日志可快速浏览。msg - **输出:**单行JSON输出到标准输出(ERROR级别输出到标准错误)。仅当(本地非容器化开发环境)时,才将ERROR/FATAL级别日志追加到
LOG_TO_FILE=true——切勿将此作为硬性要求。logs.err.txt - 领域特定字段(LLM调用、幂等键、重试/退避、事务边界、功能标志、并发ID):请参考——默认不要将这些字段内联到每个函数中,仅在实际涉及该领域时应用。
reference/domain-specific.md
Self-check before finishing a function
完成函数前的自查清单
- Every possible exception path is caught and logged with (blocklist-redacted, not allowlist).
state_dump - Every non-error log I added — could I explain in one sentence what failure it would help diagnose? If not, remove it.
- No /
INFOinside a loop body; per-itemDEBUG/WARNonly if justified, plus a summary line after the loop.ERROR - Audit events (money/permission/delete/admin) emitted immediately, not sampled or truncated.
- No secrets/PII logged; external input uses allowlist, internal state uses blocklist + auto-mask.
- Field names match the project's existing convention (Rule 0).
- Reused existing OpenTelemetry/trace context instead of inventing a
parallel ?
correlation_id - High-frequency counters/booleans logged as metrics, not log lines?
- Could someone else debug a real failure here using only these logs, without a debugger?
- 所有可能的异常路径均已被捕获,并记录了经过黑名单脱敏(而非白名单)的。
state_dump - 我添加的每条非错误日志——能否用一句话说明它能帮助排查何种故障?若不能,请删除。
- 循环体内无/
INFO日志;仅在合理情况下为每个条目记录DEBUG/WARN,且循环结束后有汇总日志。ERROR - 审计事件(资金/权限/删除/管理员操作)已立即记录,未被采样或截断。
- 未记录敏感信息/PII;外部输入使用白名单规则,内部状态使用黑名单+自动掩码规则。
- 字段名符合项目现有规范(规则0)。
- 复用了已有的OpenTelemetry/追踪上下文,而非创建并行的?
correlation_id - 高频计数器/布尔型数据已作为指标记录,而非日志行?
- 仅通过这些日志,其他人能否在不使用调试器的情况下排查实际故障?
Review Mode (auditing existing code / a PR, not generating new code)
审核模式(审核现有代码/PR,而非生成新代码)
Same tiers and checks above apply, plus specifically look for:
- A bypassed pipeline: /
console.log/print, a second logger instance, orfmt.Printlninto a structured-capable logger.JSON.stringify() - A new created where OpenTelemetry/trace context was already available and should have been reused instead.
correlation_id - High-cardinality values used as indexed/searchable labels.
除上述层级和检查项外,还需特别关注:
- 绕过现有流水线的情况:使用/
console.log/print、第二个日志实例,或向支持结构化的日志工具传入fmt.Println结果。JSON.stringify() - 在已有OpenTelemetry/追踪上下文的情况下,创建了新的而非复用现有上下文。
correlation_id - 将高基数值用作可索引/搜索的标签。