[REQUEST] TracingChannel support for observability

Abierto
#393 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
5/5
Tiempo estimado
Más de una semana
Aptitud para principiantes
38/100
Tipo de issue
Nueva funcionalidad
Claridad
Bastante claro
Estado de actividad
Tranquilo
Stack tecnológico
javascript, node.js

Línea de trabajo

Empieza leyendo los puntos de entrada de DataLoader load, loadMany, prime, clear y clearAll, así como la ruta de dispatch por lotes. Compara su comportamiento con los eventos propuestos de diagnostics_channel y TracingChannel, y después inspecciona la organización existente de las pruebas antes de definir la cobertura. El trabajo estará terminado cuando se emitan los eventos de ciclo de vida y de caché solicitados con los campos de contexto documentados y el manejo de errores asíncronos.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

enhancement

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 dataloader is 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:


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?

Lenguaje dominante
JavaScript
Estrellas
13.4k
Forks
522
Métricas de merge de PR
Sin PR fusionados en 30 d

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de graphql/dataloader

Todos los issues de graphql/dataloader

Issues similares

Más issues de JavaScript

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.