[REQUEST] TracingChannel support for observability
还没有人认领这个 Issue。
评估
- 难度
- 5/5
- 预计耗时
- 一周以上
- 新手友好度
- 38/100
- Issue 类型
- 功能
- 描述清晰度
- 基本清楚
- 活跃度
- 冷清
- 技术栈
- javascript, node.js
调研方向
首先阅读 DataLoader 的 load、loadMany、prime、clear 和 clearAll 入口,以及批处理分发路径。将它们的行为与提议的 diagnostics_channel 和 TracingChannel 事件进行比较,然后检查现有的测试组织方式,再确定覆盖范围。当所请求的生命周期事件和缓存事件都以文档中说明的上下文字段及异步错误处理方式发出时,即表示完成。
由索引模型根据 Issue 内容生成。
描述
Overview
I'd like to propose first-class TracingChannel support in dataloader, following undici in Node.js core and its sibling in the GraphQL org, graphql-js.
TracingChannel is built on diagnostics_channel for tracing async operations. It exposes structured lifecycle channels (start, end, error, asyncStart, asyncEnd) and propagates async context correctly.
Motivation
DataLoader is on the hot path of nearly every GraphQL server, and it has no built-in instrumentation. So every APM monkey-patches it: @opentelemetry/instrumentation-dataloader patches load, loadMany, prime, clear, and clearAll on the prototype, plus wraps the DataLoader constructor to intercept the user's batchLoadFn and trace batch dispatch. Datadog and Sentry do the same. The usual fragility applies:
- Runtime lock-in: RITM/IITM rely on Node.js module loader internals (
Module._resolveFilename,module.register()). They don't work on Bun or Deno. - ESM fragility: IITM depends on Node's evolving module hooks, a persistent source of breakage in OTEL JS.
- Initialization ordering: patching must happen before
dataloaderis first imported, or instrumentation silently no-ops. - Bundling: instrumented modules must stay externalized, which is hard when frameworks bundle server code into single files.
There's a DataLoader-specific cost too. Batching decouples load(key) from the eventual batchLoadFn([...keys]) across an async boundary, so the OTel patch wraps the user's batchLoadFn, stashes per-key span contexts on the internal _batch, and rebuilds the load-to-batch link graph by hand. Native emission removes all of it: the engine knows exactly when a batch is scheduled, which keys it holds, and when the promise settles.
With TracingChannel, instrumentation libraries become subscribers, not patches: independent, order-free, and with no dependency on internals like _batch.
Proposed Tracing Channels
Async operations use TracingChannel (start, end, asyncStart, asyncEnd, error). Synchronous cache operations use plain diagnostics_channel point events.
Async operations (TracingChannel, tracePromise)
| TracingChannel | Tracks | Context fields |
|---|---|---|
dataloader:load |
load(key) to per-key resolution/error |
name, key |
dataloader:loadMany |
loadMany(keys) to settled array |
name, keys |
dataloader:batch |
batchLoadFn(keys) dispatch until its promise settles |
name, keys, batchSize |
Cache operations (plain point events)
Synchronous and fire-and-forget. Included for parity with the spans OTel emits today.
| Channel | Tracks | Context fields |
|---|---|---|
dataloader:prime |
prime(key, value) |
name, key |
dataloader:clear |
clear(key) |
name, key |
dataloader:clearAll |
clearAll() |
name |
How APM Tools Use This
Today: patch 5 prototype methods + wrap the user's batchLoadFn
// Simplified from @opentelemetry/instrumentation-dataloader
wrap(DataLoader.prototype, 'constructor', /* intercept the user batchLoadFn */);
wrap(DataLoader.prototype, 'load', original => function patchedLoad(key) {
const span = tracer.startSpan(getSpanName(this, 'load'));
// push spanContext into this._batch.spanLinks so the batch span can link back
return context.with(/* ... */, () => original.call(this, key));
});
wrap(DataLoader.prototype, 'loadMany', /* ... */);
wrap(DataLoader.prototype, 'prime', /* ... */);
wrap(DataLoader.prototype, 'clear', /* ... */);
wrap(DataLoader.prototype, 'clearAll', /* ... */);
// batch span created inside the wrapped batchLoadFn, links rebuilt from captured contexts
Depends on prototype shapes, the internal _batch, and the constructor signature, and must install before first import.
With TracingChannel: subscribe to structured events
const dc = require('node:diagnostics_channel');
dc.tracingChannel('dataloader:batch').subscribe({
start(ctx) {
ctx.span = tracer.startSpan(
ctx.name ? `dataloader.batch ${ctx.name}` : 'dataloader.batch',
{ attributes: { 'dataloader.batch.size': ctx.batchSize } },
);
},
asyncEnd(ctx) { ctx.span?.end(); },
error(ctx) {
ctx.span?.setStatus({ code: SpanStatusCode.ERROR, message: ctx.error?.message });
ctx.span?.recordException(ctx.error);
},
});
dc.tracingChannel('dataloader:load').subscribe({
start(ctx) { ctx.span = tracer.startSpan(ctx.name ? `dataloader.load ${ctx.name}` : 'dataloader.load'); },
asyncEnd(ctx) { ctx.span?.end(); },
error(ctx) { ctx.span?.setStatus({ code: SpanStatusCode.ERROR }); },
});
dc.channel('dataloader:clearAll').subscribe(ctx => { /* counter / annotate active span */ });
Prior Art
This follows the pattern adopted or in progress across the ecosystem:
graphql: graphql/graphql-js#4670 which was released in 17 rc0undici(Node.js core):TracingChannelsince Node 20.12 (undici:request)fastify: native (tracing:fastify.request.handler)node-redis: redis/node-redis#3195ioredis: redis/ioredis#2089mongoose: Automattic/mongoose#16275
Just like graphql/graphql-js#4670, I would be happy to PR this and iterate with the team from there. Would you folks be willing to accept a PR for it?
- 主要语言
- JavaScript
- 星标
- 13.4k
- 派生
- 522
- PR 合并指标
- 30 天内没有已合并 PR
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 没有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
graphql/dataloader 的其他 Issue
-
[BUG] `maxBatchSize: NaN` is accepted and silently disables batching可能已有人在做 @RaphaelFakhri 于 6 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 82/100
graphql/dataloader#405 ·
-
Failed batch evicts a replacement cache entry after clear/prime可能已有人在做 @Claire-ovo 于 15 天前认领。 未关闭
难度 4/5 3-5 天 新手友好度 57/100
graphql/dataloader#403 ·
-
enhancement
难度 5/5 一周以上 新手友好度 25/100
graphql/dataloader#382 ·
-
enhancement
难度 4/5 3-5 天 新手友好度 35/100
graphql/dataloader#379 · 2 条评论 · 5 个 reaction ·
-
bug
难度 4/5 3-5 天 新手友好度 35/100
graphql/dataloader#377 · 1 条评论 · 1 个 reaction ·
查看 graphql/dataloader 的全部 Issue
相似的 Issue
-
needs verification
难度 2/5 1-3 小时 新手友好度 68/100
维护者通常 7 天内回复
-
难度 2/5 1-3 小时 新手友好度 67/100
awesome-dsh-plugin/awesome-dsh-plugin#14322 · 1 条评论 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 75/100
swc-project/swc-ai-issues#414 ·
-
[workshop-sim] Repair: Make Copilot access and billing path decision self-contained in Steps 07/07d未关闭feedback simulation workshop
难度 2/5 1-3 小时 新手友好度 72/100
githubnext/gh-aw-workshop#5597 ·
维护者通常 1 天内回复
-
[Bug]: v0.1.61 fails to initialize preload script due to system memory information error on Linux未关闭bug
难度 2/5 1-3 小时 新手友好度 68/100
CommandCodeAI/desktop#167 ·