免费且源码可见 · BSL 1.1 · TypeScript · Java · .NET · Python

由你的代码生成的日志。

每一次调用、它的参数和返回结果,都会通过你已在用的日志库变成可读的日志行。敏感信息已脱敏。你的 AI 代理读取同一份追踪,用来验证和调试。

代码即日志。

TypeScript(npm)· Java(Maven Central)· .NET(NuGet)· Python(PyPI)

实时 · 正在这个标签页里运行

你执 O。电脑为了选出下一步而调用的每个方法,都会在发生的当下出现在右侧——来自真实库的真实追踪, 没有录像,没有服务器。试试 trace level 菜单,点一个已被占用的格子,再点 复制追踪。 它是如何工作的 →

它的开局是整盘棋里最宽的一次搜索——九个候选、五十万个局面。你的回应通常是最窄的:一条战术命中, 搜索根本不会运行。

narrativetrace 0 calls
控制台里也有——打开 DevTools,输入 nt.last()

和你的代理一起从这里开始

将这段提示词粘贴到你的编程代理中。它会在你的项目里配置 NarrativeTrace,并向你展示第一条追踪。

在你的项目里,把这段粘贴到 Claude Code、Cursor 或 Codex 中。

Set up NarrativeTrace in this project and show me its first trace.

1. Read https://narrativetrace.ai/typescript/llms.txt first. It carries the install block and the known traps. Do not guess versions or artifact names.

2. If this directory has no project yet, create the smallest console app that llms.txt's "Install and first trace" block describes. Otherwise work inside the existing project and trace one real service boundary.

3. Add `@narrativetrace/cli` as a dev dependency the way llms.txt shows, then run `npx @narrativetrace/cli init --dry-run` and show me the diff. It installs the NarrativeTrace agent skills into this project and adds a marked section to AGENTS.md. Run it for real only after I have seen the diff.

4. If the `add-narrative-tracing` skill is now available, follow it. Otherwise follow the "Install and first trace (copy this)" block in llms.txt.

5. Add one test that traces a call with a deny-listed parameter and asserts the trace shows `[REDACTED]` for it.

6. Run the program, then run the doctor (`npx @narrativetrace/cli doctor`). Paste the trace and the doctor report, explain the trace in two sentences, and list exactly what changed in the project.

Rules: never disable redaction; do not commit `.received.nt` files; pass parameter names to `traceObject` explicitly so arguments never render as `arg0`; run everything in the foreground and read the output before you report; if you cannot fetch URLs, say so and I will paste llms.txt.

开发者指南 →

AI 演示:从零构建一个双服务应用

提示词使用英语,代理会用简体中文解释结果。

Read https://narrativetrace.ai/typescript/llms.txt

Create a TypeScript console app in a new folder. Keep all application code in main.ts: two service classes, OrderService calling InventoryService, and the entry point. Use NarrativeTrace to capture both services without handwritten log statements.

Set module and moduleResolution to NodeNext. Install @types/node and set types to ["node"]. Typecheck before running.

Create demo.sh (macOS/Linux) and demo.ps1 (Windows) launchers that work from any directory, install project dependencies if needed, and print Markdown traces to the terminal.

Run the launcher supported on this machine for one successful order and one out-of-stock failure. Show the actual generated traces, briefly explain what happened, and give me both launch commands.

Explain the setup, results, and launch commands in Simplified Chinese.

三项最新研究

AI 代理写的日志比人类少,而且在被要求时大多不照做。

代理不写日志。

在研究覆盖的 81 个仓库中,有 58.4% 的仓库里,代理触及日志的 pull request 占比低于人类;当评审者明确要求补上日志时,代理有 67% 没有照做。 — Do AI Coding Agents Log Like Humans?,2026

原始追踪无法解释。

在一项小型用户研究中,工程师依靠原始代理追踪找到根因的成功率为 42%,换成结构化解释后升至 89%,而且耗时几乎是三倍。 — XAI for Coding Agent Failures,2026

可信的解释来自结构,而非模型本身。

一套基于追踪的流水线在 548 条追踪中实现零次解释失败;自由生成的 LLM 叙事则有 18% 的失败率。每一条结论都必须指向真实发生过的事。 — Explaining AI Agents Through Execution Traces,罗马 Sapienza 大学,2026

NarrativeTrace 把第三项发现应用到代理交付的代码上:追踪源自结构,以散文呈现,且从不包含运行时的值。 其余数字,以及 NarrativeTrace 的回应,见下文。

代码即日志

删掉或充实日志语句。留下故事——也留下日志栈。

下面这个面板是谨慎的日志记录,不是稻草人:进入时一行,成功时一行,catch 里一行——没有对象转储, 这正是一个谨慎的团队本来就会写的方式。NarrativeTrace 反对的不是草率的日志记录,它同样取代谨慎 的日志记录,因为固定的几行日志只能报告有人提前想到要写下来的那些调用。事件去往你的日志本来就 去的地方——经由 SLF4J、ILogger、pino 或 winston,再进入任何汇聚它们的系统——而你 还没删掉的日志语句照常工作。日志桥接 →

2026 年的一项研究分析了 AI 代理提交的 4,550 个拉取请求与人类提交的 3,276 个拉取请求,覆盖 81 个开源仓库,发现代理只在 20.7% 的 PR 中触碰日志记录;当审阅者明确要求添加日志时,代理有 67% 的情况没有照做;而当代理 PR 上的一条日志语句确实被修正时,72.5% 的修正是人类自己动手 完成的,悄悄地放进后续的提交里,而不是在代码评审中提出(arXiv 2604.09409)。这项研究衡量的是:代理既不会主动写日志,被要求时也不能可靠地照做,而人类则 在评审之外默默补上这个缺口。NarrativeTrace 的回应是:代码即日志,代理没有什么需要写、也没 有什么需要遵从的;而叙事审批追踪把可观测性变成一道确定性的关卡——这正是该研究自身建议所呼 唤的那类防护机制。

之前 — 三行谨慎的日志

placeOrder(req: OrderRequest): Order {
  logger.info({ orderId: req.id }, 'placing order');
  try {
    const customer = this.customers.find(req.id);
    const price = this.catalog.price(req.sku);
    this.inventory.reserve(req.sku, req.qty);
    const payment = this.payments.charge(price);
    const order = this.orders.save(customer, payment);
    logger.info({ orderId: order.id }, 'order succeeded');
    return order;
  } catch (err) {
    logger.error({ orderId: req.id, err }, 'placing order failed');
    throw err;
  }
}

之后 — 代码即日志

placeOrder(req: OrderRequest): Order {
  const customer = this.customers.find(req.id);
  const price = this.catalog.price(req.sku);
  this.inventory.reserve(req.sku, req.qty);
  const payment = this.payments.charge(price);
  return this.orders.save(customer, payment);
}
// 零行日志。
// NarrativeTrace 自动
// 捕获叙事。

凌晨三点,支付被拒

app.log — 值班工程师唯一的线索
INFO  Placing order {customerId: "C-1234", productId: "SKU-KB", quantity: 2}
ERROR Order failed {customerId: "C-1234", productId: "SKU-KB", err: PaymentDeclinedError}
context.captureTrace() — 没有一行是手写的
OrderService.placeOrder(customerId: "C-1234", productId: "SKU-KB", quantity: 2)
  CustomerService.findCustomer(customerId: "C-1234") → Customer{tier: "gold"} — 4ms
  ProductCatalogService.lookupPrice(productId: "SKU-KB") → 24.99 — 2ms
  InventoryService.reserve(productId: "SKU-KB", quantity: 2) → Reservation{productId: "SKU-KB", quantity: 2} — 6ms
  PaymentService.charge(customerId: "C-1234", amount: 49.98, cardToken: [REDACTED]) !! PaymentDeclinedError: card declined — 640ms
!! PaymentDeclinedError: card declined — 652ms total

这两份记录来自同一次失败的调用。catch 块包住了整个方法,所以错误日志只能给出客户 ID、商品 ID 和一个异常类型——说不出到底是四次调用中的哪一次抛出的。追踪知道:PaymentService.charge, 耗时 640ms。它还给出了日志从未捕获的信息——价格、脱敏后的卡片令牌,以及两行之前运行过的 InventoryService.reserve,而两份记录里都找不到与它对应的 release。 右边这一切都不是手写的,所以下次有人加一个步骤,它也不会过时。

一次运行,两种读者

new ProseRenderer().render(trace) — 给人看
The order service places an order for customer id "C-42", quantity 5:
  Customers verify good standing for "C-42", returning true.
  The pricing service calculates for quantity 5, returning 49.95.
  The payment service charges card [REDACTED], returning PaymentConfirmation(txId="T-9").
  The order repository saves the order, returning Order{id=ORD-1001, total=49.95}.
customer_places_order.nt — 无值,给代理
- OrderService.placeOrder(customerId, qty) → value
  - Customers.verifyGoodStanding(customerId) → value
  - PricingService.calculate(qty) → value
  - PaymentService.charge(card) → value
  - OrderRepository.save(order) → value
// 没有数据,没有 PII,没有东西可以借道注入

散文的可读性,恰好等于你命名的质量。这正是清晰度评分存在的理由:它根据运行时叙事给每个名字打分—— 让代码学会讲自己的故事。 清晰度诊断 →

代理能拿到什么

代理真正会用到的四样东西

可读的 llms.txt

一份为代理而不是搜索引擎准备的结构化索引——本站自己的一份, 每个运行时也在自己的仓库里带了一份。

Java 的 llms.txt

可比对的 .nt 追踪

一棵去掉了所有运行时的值的调用树——体积小、纯结构化,代理可以放心读取和比对。

结构化追踪格式

作为确定性关卡的审批追踪

行为一旦改变,构建就会失败并给出可读的 diff,而不是悄无声息——直到有人审批为止。

审批模式是如何工作的

帮你接入的技能

add-narrative-tracing 安装这个库并带你完成第一条追踪; narrativetrace-doctor 诊断已经装好库的项目——两者都可以作为技能, 在 Claude Code 和 Codex 里直接运行。

代理技能

自述,非基准测试

AI 代理是这么说的

The trace of a failed payment made missing compensation immediately visible, and approval testing caught an extra call that ordinary value assertions accepted.
GPT-6 · Java
…the release call is present, generated from a real run, not asserted from inside the process being tested.
Claude Sonnet · Java
The always-on name-based deny-list is a genuinely nice piece of design: naming a parameter paymentToken gets you redaction for free, and I could prove it in a test.
Claude Sonnet · Java
Wrapping a service interface with NarrativeTraceProxy.trace() automatically captures all calls, parameters, return values, and durations without any code changes to the service itself. This is elegant.
Claude Haiku · Java
…the clarity report told me ClaimProcessor.process scored 0.10 for a “generic verb” the very first time I ran the suite, and it was right — I renamed it to adjudicate … and the suite average from ~0.78 to ~0.81.
Claude Sonnet · Java
Yes, for agent-driven development of Java applications with meaningful service boundaries. Getting useful traces required little configuration, and the combination of values, exception paths, and structural approvals gave me evidence I could act on.
GPT-6 · Java
The rendered Markdown trace was legitimately useful while building this, not just as a deliverable … which is how I caught that my first draft of scenario 6’s numbers didn’t actually trigger InsufficientCoverageError … well before any test told me.
Claude Sonnet · Python
That’s not a library gap so much as a library doing exactly its job: it rendered what was actually being passed around, and what was actually being passed around was more than any of those services needed.
Claude Sonnet · TypeScript

我们请 AI 代理用 NarrativeTrace 构建一个 Java 服务,并把这段经历如实写给另一位工程师 看——哪里别扭也不隐瞒。以下就是它们自己的原话,未经编辑,并标出了具体模型。这不是基准测试: 每个只跑了一次,而且这个库还是我们自己写的。任务说明和提示词都在按钮下面,下一份报告可以出自 你的代理之手。

自己试试看

这些报告就是从这个练习里来的。有一处不同:我们跑的时候是把文档副本直接交给了代理;你跑 的时候,则是让它指向公开仓库。两个文件,三个步骤。

  1. 保存说明文档,存为空目录中的 TASK.md。
  2. 启动你的代理,在这个目录里把提示词交给它。
  3. 阅读它的 FINDINGS.md——如果你把它发到所用运行时的 Discussions(Java · TypeScript · .NET · Python),我们也会读。

提示词

You are building a Java application from a written specification.

The specification is in `TASK.md` in the current directory. Read it, then build the
application it describes. Build it properly: it should compile, its tests should
pass, and the demo should run.

A library called **NarrativeTrace** is available to you for this project. Its
documentation is in its public repository:

- https://github.com/narrativetrace/narrativetrace-java — start with the README
- https://github.com/narrativetrace/narrativetrace-java/blob/main/documentation/llms.txt
  — a short orientation written for AI agents
- https://narrativetrace.ai/java/llms-full.txt — the full documentation written for AI agents

Read that documentation first, then use the library in the application you build.
Its artifacts are published on Maven Central under the group `ai.narrativetrace`;
the Gradle plugin id is `ai.narrativetrace`. Take the version from the install
block in llms.txt; the documentation explains which artifacts you need and how to
wire them up.

Rules for this exercise:

- Work **only** inside the current directory. Do not read, search, or modify
  anything outside it, and do not clone the library's source code — the
  documentation is what you have.
- You may use the network to read that documentation and to resolve build
  dependencies.
- You choose the design, the libraries, the project layout, and the testing
  approach. There is no house style to match and no reviewer to please.
- Work in the foreground. Do not background the build or the tests and then end
  your turn — run them, wait for them, and read the output.
- When the build and tests are green and the demo runs, write `FINDINGS.md` as
  the specification describes. In addition to what the specification asks for,
  cover what using NarrativeTrace was actually like: what the documentation got
  right or left you guessing about, what integration cost you, and whether the
  output it produced was of any use to you while you worked.

Finish by reporting, in your final message: whether the build and tests are
green, what you built, and where things stand.

说明文档 — TASK.md

# Build task — Claims intake service (Java 17)

Build a small, self-contained Java 17 application called **ClaimFlow** that processes
insurance claims through a chain of collaborating services. It must build and test with
Gradle, and run as an executable demo.

This is a business-logic exercise, not an infrastructure one: everything is in-process
and in-memory. No database, no HTTP server, no external service, no network access.

## The domain

A claim is submitted and passes through five collaborating services. Each is its own
interface with its own implementation — do not collapse them into one class.

1. **PolicyService** — looks up a policy by `policyId`. A policy has a holder name, a
   coverage limit in whole cents, a deductible in whole cents, and an active flag.
2. **ClaimValidator** — rejects a claim that names an unknown policy, an inactive
   policy, a non-positive amount, or an incident date in the future.
3. **FraudScreen** — scores a claim from 0–100. Any claim scoring above 70 is referred
   rather than paid. Scoring may be simple and deterministic, but it must consider at
   least the claim amount relative to the coverage limit, and whether the same policy
   has had a prior claim in this run.
4. **ReserveLedger** — reserves funds against a policy's remaining coverage before
   payment, and can release a reservation. Reserving more than the remaining coverage
   fails.
5. **PayoutService** — pays an approved claim, given a payment token. Payment can fail;
   your demo must exercise at least one failing payment.

A **ClaimProcessor** orchestrates them and returns a `ClaimOutcome` describing what
happened: paid (with amount and transaction id), referred, or rejected (with a reason).

## Required behaviour

- Payout amount is `min(claimAmount, coverageLimit) - deductible`, never below zero.
- A claim must be validated, then screened, then reserved, then paid — in that order.
- **If payment fails after a reservation was made, the reservation must be released.**
- A referred claim is never paid and never reserves funds.
- Two claims against the same policy must respect the remaining coverage.
- The payment token is a credential and must never appear in any output your program
  produces.
- The claim holder's name is personal data; treat it accordingly in whatever your
  program writes out.

## Deliverables

1. A Gradle project that compiles under Java 17 and runs `./gradlew test` green.
2. Unit tests covering the behaviour above, including the failure paths.
3. A runnable demo (`./gradlew run` or a `main`) that exercises at least five scenarios:
   a straightforward paid claim, a claim above the coverage limit, a referred (high
   fraud score) claim, a rejected claim, and a claim whose payment fails after a
   reservation.
4. A short `FINDINGS.md` in the project root — see "What to report" below.

## What to report in FINDINGS.md

Write it for another engineer, not for us. Cover:

- What you built and how you verified it works.
- Anything that surprised you, went wrong, or took longer than expected.
- How you satisfied yourself that the compensation rule (release the reservation when
  payment fails) actually holds — what evidence do you have?
- How you satisfied yourself that the payment token never leaks into output.
- What you would want before running this in production.
- Anything about the tools or libraries you used that helped or got in your way.

Be honest and specific. A report that says everything went perfectly is less useful
than one that names what was awkward.

给人看的

  • 散文,读起来像一份 bug 报告,用的是你团队的语言
  • Markdown 和时序图,每个测试都会写出一份
  • 每次运行后一行话:自上一次绿色构建以来变了什么

给 AI 代理的

  • 少 15–30% 的 token,相比带着日志语句的代码
  • Markdown,为上下文窗口而生——运行时的事实,而不是猜测
  • 无值的 .nt:任何运行时的值都到不了它那里,所以没有东西可以借道注入
15–30%代理阅读你的代码时少用的 token —— 为什么
0个运行时的值出现在 .nt 追踪里——注入无处可依附
.md每个测试都会写出一份 Markdown 叙事
~1.9 µsJVM agent 每次被追踪调用 · 未激活约 40 ns · 实测

为 AI 辅助开发而生

你的代理能读代码。
现在它还能安全地看代码运行。

只靠源码调试的编码代理会猜测运行时行为——而且猜错。NarrativeTrace 把事实交给它——真实的调用树—— 用模型最擅长阅读的形态,只花一小部分 token,而且它绝不该看到的数据早已不在其中。

更少的 token,更多的信号

日志语句是噪音 token。删掉它们,代理阅读一个服务类的成本就降低 15–30%——而追踪的每一行都是信号, 因为它是从名字生成的,不是从散文生成的。重复出现的值会去重为一个引用;重复的循环会折叠成一行。

Markdown,为上下文窗口而生

每个测试都把它的叙事写成一个 .md 文件——一份由调用、参数和结果组成的嵌套列表, 任何代理本来就会读。贴进去、附上去,或者直接把代理指向你的 CI 已经在填充的那个目录。散文、JSON 和时序图都来自同一条追踪。

输出格式

无法被提示注入

每个测试还会写出一个 .nt 结构化追踪:调用树,去掉了所有运行时的值。没有任何用户输入能到达它, 所以也没有任何用户输入能操纵读它的模型。零 PII、只用一小部分 token、可以安全提交到仓库。安全来自构造, 而不是过滤。

分离是如何实现的

接下来会有什么 规划中

假名化输出:真实的值被替换成合成 token,代理依然能看出同一位顾客流经了三次调用, 却永远看不到是谁。MCP 服务器:Claude Code、Cursor 和 Copilot 直接查询追踪和依赖图——只读, 默认只给结构。

预览 MCP 设计

这个网站对代理同样友好:把你的助手指向 llms.txt(它会把助手引到你所用运行时自己的 llms 文件),它就能替你接入 NarrativeTrace。

工作原理

两步得到你的第一条叙事

1

包装一个服务 — 或用装饰器

const context = new AsyncNarrativeContext(
    new NarrativeTraceConfig());
const service = traceObject(orderService, context, {
  placeOrder: ["customerId", "productId", "quantity"],
});

// 或者直接写在类上:
@traced("customerId", "productId", "quantity")
placeOrder(cId: string, pId: string, qty: number) {}
2

读故事

service.placeOrder("C-1234", "SKU-KB", 2);

console.log(
  renderIndentedText(context.captureTrace()));

// Vitest:每个测试都会写出
// 自己的追踪文件。

全部 @narrativetrace/* 包已在 npm 上线当前发布版本。 Node 20+;用装饰器写法需要 TypeScript 5.0+。

面向遗留系统现代化

迁移那个没人懂的系统 — 而且有凭有据。

AI 代理让重写变得便宜;昂贵的是验证。给遗留系统套上追踪,你就得到了它真实的运行时行为——在改动之前, 以及改动之后的等价性证明。

给你真正拥有的架构拍一张 X 光

在 JVM 上,用一个 -javaagent 参数把 Agent 挂到一个未经修改的应用上, 或者直接跑你已有的 JUnit 4 套件。在 .NET 上,NarrativeTrace.Legacy 一直向下兼容到 .NET Framework 4.8。不改源码,不做文档考古。

零代码 Agent

证明行为没有改变

在动手之前,先把每个场景固定在一份无值基线里。然后比较移植或 AI 重写前后的整套追踪, 每一处差异都按风险分类。

迁移差异

找出最难读的代码

如果追踪读不通,说明代码在撒谎。清晰度评分根据运行时叙事给命名打分,并解释每一个分数。 让你的重构——或你的代理的重构——先从最糟的角落开始。

清晰度诊断

同样包含:从真实运行中采集的领域术语表,以及把同一条追踪渲染成中文、西班牙语或葡萄牙语的散文。 术语表与翻译后的追踪 →

AI 重构的安全网

测试能抓住错误的值。
叙事能抓住错误的行为。

每次运行之后,NarrativeTrace 都会把每个场景的调用结构与上一次绿色运行比较,用一行话说明变了什么。 打开审批模式,未经审批的变化(多了一次调用、少了一次调用、结果不同)会以一份可读的 diff 让构建失败,直到有人审批为止。测试是绿的,行为却变了。现在你知道了。

./gradlew test — 每次运行,无需配置
NarrativeTrace — Suite complete
  5 scenarios recorded
  Clarity: 100% high | 0% moderate | 0% low
  Reports: build/narrativetrace
  Since last green: 4 scenarios unchanged · 1 changed:
    "Customer places order" (−1 call Customers.verifyGoodStanding)
approval.set(true) — 重构必须经过审批
Narrative changed against the approved baseline
  src/test/narratives/OrderServiceTest/customer_places_order.approved.nt

  - OrderService.placeOrder(customerId, qty) → value
-   - Customers.verifyGoodStanding(customerId) → value
    - PricingService.calculate(qty) → value
    - PaymentService.charge(card) → value
    - OrderRepository.save(order) → value

  审阅 .received.nt,然后执行:./gradlew approveNarratives

每次运行后一行话

无需任何设置:每个测试本来就会写出的 .nt 文件就是上一次绿色运行的基线, 测试失败时只打印发生了什么变化。

审批的是行为,不只是代码

任何改变调用结构的重构——你的,或者你的代理的——都会失败,直到有人审阅 diff 并运行 approveNarratives。

天生不含值

基线只包含名字和结构,因此可以安全提交到仓库,也可以安全地交给 AI 代理作为权威规格。

审批模式是如何工作的

各运行时的现状,因为构建门禁是一种承诺:TypeScript、Java、.NET 和 Python 如今都提供审批追踪;无值的 .nt 结构化构件已在 TypeScript、Java、.NET 和 Python 中提供。

你的日志有了新读者。
它们不睡觉,也不会走马观花。

客服、值班、审计、安全:每一个角色都在变成一个有人类监督的代理。它们需要的是同一样东西—— 一份完整、可读、不含 PII 的记录,记下代码真正做了什么,而且不需要任何人手写。

同一条追踪,五位读者

客服

回复工单的那一个

读懂这次请求的故事,告诉顾客发生了什么。为什么可以:整条路径都以平实 语言呈现,也没有惊动任何工程师。

输出格式

值班

决定要不要叫醒你的那一个

看到哪里失败、每一步花了多长时间,并判断严重程度。为什么可以:失败的 那一步,以及它之上的一切,始终会被捕获。

捕获级别

审计

组装证据包的那一个

把带注解的操作汇总起来,每一条都带着自己的操作码,串成一条防篡改的链条——审计人员和 代理都能读懂的证据。为什么可以:证据是在代码运行时记录下来的,不是事后 重建的。

审计与 SecOps 事件

安全

确认控制是否执行的那一个

确认控制触发了,并且敏感值始终没有逃过脱敏。为什么可以:记录只保留 发生了什么的结构,值在写入之前就已经被剥离。

分离是如何实现的

编码代理

昨晚重写了你的服务的那一个

对比修改前后的故事,一旦变了就让构建失败。为什么可以:审批追踪把 故事变成了一道关卡。

审批模式是如何工作的

带上你自己的代理:Claude、Codex、Bits、Seer,或者凌晨 3 点的一个人。NarrativeTrace 自带 OpenTelemetry,所以同一个故事也能带着追踪一起,落到 Datadog、Sentry 或 Honeycomb。

上线前的问题

一次生产环境评估真正会问的问题

这是一位集成方向的工程师在评估 NarrativeTrace 时问出的三个问题,随后点名了这些 会阻止他把它用到生产环境的地方。我们宁愿在这里用数字和明确写出的限制来回答,也不愿让评估者 自己去发现这些缺口。

这在高并发下,性能和内存上到底要付出多少代价?

我们在任何一个运行时里都不会说"零开销"——追踪要做实际的工作,工作就有成本。 NarrativeTrace 本身增加的只是捕获:拦截调用、读取参数、构建追踪树。 捕获之后的一切——写入你现有的目的地、collector、磁盘或网络——都是你的日志系统已经在 付出的同一份成本;NarrativeTrace 不会再加一个新的写入目的地。对于正在替换手写日志语句 的团队来说,目的地这一侧基本是打平的:每个方法原本的多条日志写入,变成一次追踪写入, 而那些日志语句本身也不再需要被编写和维护。

Java:约 1.9 µsagent 激活状态 · 未激活约 40 ns · 2026-09-01 实测
TypeScript:约 10 µs完整细节级别 · level:'off' 下约增加 0.1 µs · 2026-09-07 实测
.NET:约 2.5 µs完整进入/退出周期,Detail 级别 · Off 级别尚未测量 · 2026-08-12 实测
Python:尚未公开发布OFF 级别的零工作路径已验证;目前还没有带日期的公开数字

在并发场景下,持久路径(一个接入你现有 logger 的同步 listener)会同步写入——和它 替换的那条日志调用一样能扛住崩溃。尽力而为的分析路径是一个有边界的环,在持续过载时会 主动丢弃,而不是阻塞调用方,并且每一个运行时都会统计丢弃了多少、并 把这个数字报告出来,而不是悄悄丢失。

诚实地说明这个缺口: 目前没有任何一个 NarrativeTrace 运行时发布了 采样(sampling)功能。按百分比或按速率限流的采样器在规划中,但还没有构建。如果你现在 就需要限制捕获量,可以收窄追踪范围,或者在热路径上调低追踪级别。

我怎么知道带有 PII 或凭据的参数不会泄漏到追踪里?

四层独立的保护,而不是一句笼统的承诺。1. 显式脱敏——参数、字段或 属性上的 @NotTraced / [NotTraced] / @not_traced; 它始终优先,哪怕配置已经关闭了其他几层。2. 始终开启、覆盖多语言的按 名称拒绝列表——针对标识符名称(password、token、 ssn,加上西班牙语、葡萄牙语、法语和中文的对应词)进行匹配,在每个运行时 里都默认开启,从不是可选项。3. 不依赖字段名的值形状匹配——一个 JWT 形状的字符串、一个通过 Luhn 校验的卡号,或者一个国家身份证号校验位,即使出现在 data 这样无害的名字下面,也会被捕获。4. 不携带值的结构化 模式 .nt——范畴性的保证。没有任何运行时值能到达它,所以也就没有什么是过滤 器可能漏掉的。

要把边界说清楚:按名称和按形状匹配(第 1–3 层)是启发式的、可扩展的——发现 新的缺口就会扩大覆盖范围,但也总可能漏掉一个还没有人加进来的名字或形状。结构化模式 (第 4 层)是唯一具有范畴性的答案。如果你的威胁模型要求任何值都不可能离开 进程,那就应该用它,每个运行时都有。

当一个流程跨越多个服务时,这能和标准的关联 ID(correlation ID)对上吗,还是只能在本地用?

可以,在每一个运行时里都可以,通过和 OpenTelemetry 自己一样的机制实现:W3C traceparent。入站请求头会被采纳,NarrativeTrace 自己的追踪 ID 会直接 变成那个请求头里的追踪 ID——而不是另一个只是形状相似的独立标识符——出站调用则会在发出 时把当前的追踪 ID 盖上去。每一个运行时也都会把 NarrativeTrace 的 span 导出给 OpenTelemetry,所以你现有的 collector、Jaeger,或者关联 ID 中间件不需要做 任何协调就能理解这个 ID。

在所有运行时里都留在本地的部分:叙事树本身——嵌套的调用、参数和叙述文本——是按 进程捕获的,不会发送给其他服务;跨越边界的只有追踪 ID。下游服务会生成自己的、和这个 ID 关联的树,而不是一棵合并起来的跨服务树。

版本

各版本包含什么

运行时免费且源码可见,采用 BSL 1.1,并在每个版本发布四年后转为 Apache 2.0;API 与输出格式采用 Apache 2.0。Pro 与 Enterprise 为商业版本。

Free 现已可用

可免费用于生产,源码可见。BSL 1.1;每个版本在四年后转为 Apache 2.0。API 与输出格式采用 Apache 2.0。许可说明 →

  • 核心零运行时依赖
  • 自动追踪:代理、叙事装饰器/属性与 HTTP 中间件在每个运行时都有;DI 容器自动包装(Java、.NET、NestJS)与零代码字节码 Agent(Java)
  • 全部五个捕获级别,可在运行时切换
  • 方法与字段上的叙事、错误上下文与不追踪标记——命名约定由各运行时自行决定——加上自动脱敏
  • JUnit 5/4、xUnit、NUnit、Vitest 和 pytest 的逐测试追踪文件:Markdown、JSON、散文、时序图
  • 清晰度评分、清晰度 CLI、CI 清晰度门禁与通往你的日志器的桥接
  • 无值 .nt 追踪(TypeScript、Java、.NET、Python);审批模式(TypeScript、Java、.NET、Python)
  • 领域术语表、翻译后的追踪、OpenTelemetry
开始使用

Enterprise 即将推出

托管后端,汇集每个服务、每个环境、每次运行的叙事。

  • 托管的 OTLP 追踪接入
  • 多租户存储与搜索
  • 团队仪表盘与保留策略
  • 组织级清晰度与审计报告
加入候补名单

免费的运行时发布在 Maven Central 的 ai.narrativetrace 之下,发布在 nuget.org 上的 NarrativeTrace.*,也以源码形式发布在 GitHub。许可条款写在许可页上。

集成

与你现有的技术栈无缝对接

Vitest 已发布
Express 已发布
Hono 已发布
NestJS 已发布
React 已发布
React Router 已发布
Angular 已发布
pino 已发布
winston 已发布
OpenTelemetry 已发布
浏览器构建版 已发布
Clarity CLI 已发布

每个运行时

一套架构,每个运行时

叙事捕获、双消费者输出和清晰度诊断:一套架构,按同一份共享的输出规范,在每种语言里各自原生实现。无论是哪个运行时产出的,追踪读起来都一样——无值 .nt 构件因此可以在它们之间通用。

TypeScriptnpm
JavaMaven Central
.NETNuGet
Swift开发中
PythonPyPI

narrativetrace-typescript · narrativetrace-java · narrativetrace-dotnet · narrativetrace-python。 全部 @narrativetrace/* 包已在 npm 上线当前发布版本。全部 narrativetrace 包已在 PyPI 上线当前发布版本。Swift 仍在开发中。

让你的代码开口说话——也让你的代理竖起耳朵。

60 秒得到你的第一条追踪。这个库的诞生过程中没有写过一行日志语句。