[REQUEST] TracingChannel support for observability
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 5/5
- Thời gian dự kiến
- Hơn một tuần
- Mức phù hợp với người mới
- 38/100
- Loại issue
- Tính năng
- Độ rõ ràng
- Khá rõ ràng
- Mức độ hoạt động
- Ít trao đổi
- Công nghệ
- javascript, node.js
- Lĩnh vực
- backend, observability-sre
Hướng nghiên cứu
Bắt đầu bằng cách đọc các entry point load, loadMany, prime, clear và clearAll của DataLoader, cùng với đường dẫn dispatch theo batch. So sánh hành vi của chúng với các sự kiện diagnostics_channel và TracingChannel được đề xuất, sau đó kiểm tra cách tổ chức test hiện có trước khi xác định phạm vi bao phủ. Hoàn tất có nghĩa là các sự kiện vòng đời và cache được yêu cầu được phát ra với các trường context đã được tài liệu hóa và cơ chế xử lý lỗi bất đồng bộ.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
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?
- Ngôn ngữ chính
- JavaScript
- Star
- 13.4k
- Fork
- 522
- Chỉ số merge pull request
- Không có pull request nào được merge trong 30 ngày
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Không có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của graphql/dataloader
-
[BUG] `maxBatchSize: NaN` is accepted and silently disables batchingCó thể đã có người làm @RaphaelFakhri đã nhận 3 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
graphql/dataloader#405 ·
-
Failed batch evicts a replacement cache entry after clear/primeCó thể đã có người làm @Claire-ovo đã nhận 12 ngày trước. Đang mở
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 57/100
graphql/dataloader#403 ·
-
enhancement
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 25/100
graphql/dataloader#382 ·
-
enhancement
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 35/100
graphql/dataloader#379 · 2 bình luận · 5 reaction ·
-
bug
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 35/100
graphql/dataloader#377 · 1 bình luận · 1 reaction ·
Tất cả issue của graphql/dataloader
Issue tương tự
-
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 75/100
postcss/postcss.org#302 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
ImranR98/apps.obtainium.imranr.dev#2000 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
routing: the tray panel's stage stands still with motion reduced while a request is in flightĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 66/100
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 83/100
microsoft/monaco-editor#5577 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
Ebony-Vinyl/dsh-our-free-model#785 ·
Maintainer thường phản hồi trong vòng 1 ngày