Skip to content

TracingChannel Proposal #29353

Open
Open

Description

@logaretm

Feature Summary

I'd like to propose adding first-class TracingChannel support to @prisma/client, following the pattern established by undici in Node.js core.

I would like propose emitting tracing channel events as part of the current observability/instrumentation story, either as a separate plugin @prisma/tracing or built-in given that tracing channels are a node built-in and it won't add much to existing codebases.

Use Cases & Problem Description

We started advocating for the adoption of tracing channels across the ecosystem since it solves many issues about the observability story in today's JavaScript. The main pain points are the usage of IITM/RITM and monkey patching and it's fragile nature. This makes instrumentations in general fragile in today's ecosystem where we can have multiple runtimes that may not support module hooks properly, and server-side bundling frameworks or single executable packing that escapes the module hooks cycle entirely.

The current @prisma/instrumentation is already ahead in a sense that it does not use IITM/RITM, no prototype patching, no module loader hacks which is great.

However, this architecture is tightly coupled to the OpenTelemetry API. APM tools that don't use the OTel SDK, Datadog's native tracer, Sentry's node-light SDK, custom in-house tracing, cannot subscribe to Prisma's internal operations without either:

  1. Adopting the full OTel SDK just to get Prisma spans.
  2. Reimplementing the TracingHelper contract privately (fragile, undocumented).

Tracing Channels offer a native way to build observable APIs by default, the notable features being:

  • Context propagation is handled by default, all traced calls with tracePromise or traceCallback propagate and correlate the event lifecycle hooks automatically.
  • You can subscribe to them anywhere anytime, so no need to make sure to create the trace helper interface before setting up the client.
  • Zero overhead if no subscribers are present.
  • Backward compatible from node 18, and can be conditionally loaded with process.getBuiltInModule.

Proposed Solution

Adopting Tracing channels can be done in a couple of ways, but first let me outline the channels I think could be a great start:

TracingChannel Tracks Context fields
prisma:client:operation All model operations — findMany, create, update, delete, upsert, aggregate, groupBy, count, etc. model, operation, args, database, serverAddress, serverPort
prisma:client:query Raw SQL execution — $queryRaw, $executeRaw, and engine-level SQL dispatched by model operations query, params, database, serverAddress, serverPort, dbSystem
prisma:client:transaction Transaction lifecycle — $transaction([...]) (batch) and $transaction(async (tx) => { ... }) (interactive) isolationLevel, transactionType, database, serverAddress, serverPort
prisma:client:connect Engine lifecycle — $connect() database, serverAddress, serverPort, dbSystem

Plain diagnostics_channel (fire-and-forget, no async lifecycle):

Channel Tracks Context fields
prisma:client:disconnect Engine teardown — $disconnect() database, serverAddress, serverPort

With that in mind, we can use the tracing channels in these two ways:

Option A

The existing TracingHelper contract continues to power @prisma/instrumentation for OTel users, while TracingChannel provides an independent subscription point for non-OTel APM tools. Both coexist, emitting from the same code paths.

Option B

TracingChannel as the vehicle for @prisma/instrumentation. TracingChannel becomes the underlying emission mechanism. @prisma/client emits events via diagnostics_channel, and @prisma/instrumentation becomes a thin OTel subscriber that translates TracingChannel events into OTel spans, just like @opentelemetry/instrumentation-undici subscribes to undici:request. This eliminates the custom TracingHelper contract entirely in favor of a platform primitive.

@prisma/client  ──TracingChannel──>  @prisma/instrumentation (OTel subscriber)
                                 └>  Datadog subscriber
                                 └>  Sentry subscriber
                                 └>  Custom subscriber

This means users can subscribe to the tracing channel base layer and do whatever they need to, while at the same time getting OTEL data if they wish to continue doing so. I think this is the best of both worlds and provides a cleaner way for context propagation and span relationships implementation.

It would look like this at the end user side:

const dc = require('node:diagnostics_channel');

// Any APM tool: OTel, Sentry, Datadog, or custom subscribes directly
dc.tracingChannel('prisma:client:query').subscribe({
  start(ctx) {
    ctx.span = tracer.startSpan('prisma:query', {
      attributes: {
        'db.system.name': ctx.dbSystem,
        'db.query.text': ctx.query,
        'db.namespace': ctx.database,
        'server.address': ctx.serverAddress,
        'server.port': ctx.serverPort,
      },
    });
  },
  asyncEnd(ctx) { ctx.span?.end(); },
  error(ctx) { ctx.span?.setStatus({ code: SpanStatusCode.ERROR }); },
});

Alternative Solutions

I considered implementing the TracingHelper interface from @prisma/instrumentation-contract and set the global helper itself. I think it could work but it has drawbacks:

  1. It's a completely internal module, not a public API.
  2. Hardwired to OTEL in types shape.
  3. Only one global trace helper can exist, doesn't seem to be possible to have multiple APMs active at the same time.
  4. Global helper must be still created before the client is initialized. Posing a timing/order issue.

TracingChannel solves all of these by providing a vendor-neutral, platform-level primitive. Any APM tool (OTel-based or not) can subscribe to structured lifecycle events without depending on any tracing SDK. Multiple subscribers coexist by design, there's no initialization ordering constraint, and the contract is diagnostics_channel itself, which is a stable Node.js API.

Potential Considerations

Prior Art

This approach follows the same pattern already adopted or in progress by other major libraries:

I have also pitched it to other database drivers and libraries and they are being discussed.


I'm more than happy to help spec this and submit a PR for it as i understand there is a roadmap, but the code change itself is minimal and I'm able to fully work on this and get it to the finish line.

Prisma Version

No response

What part of Prisma does this affect?

Prisma Client

Additional Context

No response

Pre-Submission Checklist

  • I have searched existing issues to make sure this is not a duplicate
  • I have checked the Prisma roadmap to see if this is already planned
  • I have described why this belongs in Prisma Core rather than a solution in application code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind/featureA request for a new feature.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions