[REQUEST] TracingChannel support for observability
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Idoneità per principianti
- 38/100
- Tipo di issue
- Funzionalità
- Chiarezza
- Abbastanza chiara
- Stato di attività
- Tranquilla
- Stack tecnologico
- javascript, node.js
- Ambito
- backend, observability-sre
Direzione di ricerca
Inizia leggendo i punti di ingresso di DataLoader load, loadMany, prime, clear e clearAll e il percorso di dispatch dei batch. Confronta il loro comportamento con gli eventi proposti di diagnostics_channel e TracingChannel, quindi esamina l'organizzazione esistente dei test prima di definire la copertura. Il lavoro è completato quando vengono emessi gli eventi richiesti del ciclo di vita e della cache con i campi di contesto documentati e la gestione degli errori asincroni.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
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?
- Lingua principale
- JavaScript
- Stelle
- 13.4k
- Fork
- 522
- Metriche di merge delle PR
- Nessuna PR unita negli ultimi 30g
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di graphql/dataloader
-
[BUG] `maxBatchSize: NaN` is accepted and silently disables batchingForse già presa @RaphaelFakhri l’ha presa 2 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
graphql/dataloader#405 ·
-
Failed batch evicts a replacement cache entry after clear/primeForse già presa @Claire-ovo l’ha presa 11 giorni fa. Aperta
Difficoltà 4/5 3-5 giorni Idoneità per principianti 57/100
graphql/dataloader#403 ·
-
enhancement
Difficoltà 5/5 Più di una settimana Idoneità per principianti 25/100
graphql/dataloader#382 ·
-
enhancement
Difficoltà 4/5 3-5 giorni Idoneità per principianti 35/100
graphql/dataloader#379 · 2 commenti · 5 reazioni ·
-
bug
Difficoltà 4/5 3-5 giorni Idoneità per principianti 35/100
graphql/dataloader#377 · 1 commento · 1 reazione ·
Tutte le issue di graphql/dataloader
Issue simili
-
bug needs-triage potential-regression
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
aws/aws-sdk-js-v3#8408 ·
I maintainer di solito rispondono entro 1 giorno
-
[v3] KeyBindingManager calls (Add/Remove) are not converted into a case-insensitive accelerator.KeyApertaBug
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
I maintainer di solito rispondono entro 4 giorni
-
fix(docs): align git__gh troubleshooting with its actual failure pathsForse già presa @akhil9tiet l’ha presa oggi. Apertabug docs p2
Difficoltà 1/5 1-3 ore Idoneità per principianti 78/100
vercel/eve#6130 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100