Skip to content

Observability through TracingChannels API #11347

@sergical

Description

@sergical

What is the feature you are proposing?

I'd like to propose adding first-class TracingChannel support to Remix 3, following the pattern established by undici in Node.js core and adopted by framework peers like h3, fastify, and srvx.

TracingChannel is a higher-level API built on top of diagnostics_channel, specifically designed for tracing async operations. It provides structured lifecycle channels (start, end, error, asyncStart, asyncEnd) and handles async context propagation correctly -- the missing piece that makes monkey-patching approaches fragile in real-world async applications.

We (at Sentry) are working on instrumentation for Remix 3 and would love to avoid the monkey-patching path entirely. Remix 3's closure-based router has no prototype chain to intercept, making external instrumentation especially difficult. TracingChannel gives APM tools a stable, first-party surface to subscribe to.

Since Remix 3 is a ground-up rewrite with no existing APM instrumentation, this is a rare opportunity to ship TracingChannel from day one -- so APM vendors never need to build monkey-patching infrastructure for this framework at all.

Proposed Channels

Three channels covering the three server-side subsystems:

TracingChannel Subsystem Tracks Context fields
remix:request fetch-router Middleware and route handler executions type, name, context
remix:render ui/server Server-side rendering of component trees frameSrc
remix:asset assets On-demand script/style compilation filePath, assetType

remix:request traces individual middleware and handler executions. The type field ("middleware" or "handler") distinguishes them. The context field is the RequestContext object, from which APMs extract HTTP method, URL, params, and headers.

remix:render traces renderToStream(). Frame resolution sub-requests naturally appear as nested remix:request spans because resolveFrame() calls router.fetch().

remix:asset traces on-demand TypeScript/CSS compilation in the asset server.

Insertion Points

The implementation is minimal:

  1. runMiddleware() in fetch-router/src/lib/middleware.ts -- wraps each middleware call with tracePromise
  2. dispatchMatches() in fetch-router/src/lib/router.ts -- wraps matched handler calls with tracePromise
  3. renderToStream() in ui/src/server/stream.ts -- wraps the render work
  4. assetServer.fetch() in assets/src/lib/asset-server.ts -- wraps compilation paths

Each uses the standard hasSubscribers gating for zero cost when no APM tool is listening.

APM Subscriber Example

import { tracingChannel } from 'otel-tracing-channel';
import * as Sentry from '@sentry/node';

tracingChannel('remix:request', (ctx) => {
  return Sentry.startSpanManual({
    name: ctx.name,
    op: ctx.type === 'handler' ? 'remix.handler' : 'middleware.remix',
    attributes: {
      'http.request.method': ctx.context.method,
      'url.path': ctx.context.url.pathname,
    },
  }, (span) => span);
}).subscribe({
  asyncEnd(ctx) { ctx.span?.end(); },
  error(ctx) {
    ctx.span?.setStatus({ code: 2, message: ctx.error?.message });
    ctx.span?.end();
  },
});

Working Implementation

We have a working proof-of-concept with a demo app that sends traces to Sentry:

Prior Art

This follows the same pattern adopted by other frameworks:

Happy to put together a PR if there's interest.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions