11import * as diagnosticsChannel from 'node:diagnostics_channel' ;
22import type { IntegrationFn , SpanAttributes } from '@sentry/core' ;
3- import { debug , defineIntegration , SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN , startInactiveSpan , startSpan } from '@sentry/core' ;
3+ import {
4+ debug ,
5+ defineIntegration ,
6+ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ,
7+ startInactiveSpan ,
8+ startSpan ,
9+ waitForTracingChannelBinding ,
10+ } from '@sentry/core' ;
411import { DEBUG_BUILD } from '../../debug-build' ;
512import { CHANNELS } from '../../orchestrion/channels' ;
613import { bindTracingChannelToSpan } from '../../tracing-channel' ;
@@ -164,6 +171,11 @@ const _nestjsChannelIntegration = (() => {
164171 return {
165172 name : INTEGRATION_NAME ,
166173 setupOnce ( ) {
174+ // `tracingChannel` is unavailable before Node 18.19 so do nothing in that case.
175+ if ( ! diagnosticsChannel . tracingChannel ) {
176+ return ;
177+ }
178+
167179 DEBUG_BUILD && debug . log ( '[orchestrion:nestjs] subscribing to @nestjs channels' ) ;
168180
169181 // App-creation span: `bindTracingChannelToSpan` opens the span on
@@ -172,24 +184,35 @@ const _nestjsChannelIntegration = (() => {
172184 //
173185 // `captureError: false` a failed bootstrap surfaces to the caller.
174186 // We just annotate the span.
175- bindTracingChannelToSpan (
176- diagnosticsChannel . tracingChannel < ChannelContext > ( CHANNELS . NESTJS_APP_CREATION ) ,
177- data => {
178- const moduleCls = data . arguments ?. [ 0 ] as { name ?: string } | undefined ;
179- return startInactiveSpan ( {
180- name : 'Create Nest App' ,
181- op : `${ TYPE_APP_CREATION } .nestjs` ,
182- attributes : {
183- [ ATTR_COMPONENT ] : NESTJS_COMPONENT ,
184- [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : ORIGIN_NESTJS ,
185- [ ATTR_NESTJS_TYPE ] : TYPE_APP_CREATION ,
186- ...( data . moduleVersion ? { [ ATTR_NESTJS_VERSION ] : data . moduleVersion } : { } ) ,
187- ...( moduleCls ?. name ? { [ ATTR_NESTJS_MODULE ] : moduleCls . name } : { } ) ,
188- } ,
189- } ) ;
190- } ,
191- { captureError : false } ,
192- ) ;
187+ //
188+ // `bindTracingChannelToSpan` uses `bindStore`, which needs the
189+ // async-context binding registered after integration `setupOnce`;
190+ // defer until it's available (matches the other channel subscribers).
191+ // Only this bind is deferred: it fires at `NestFactory.create`
192+ // (bootstrap), so a retry tick is fine. The plain `.subscribe` calls
193+ // below stay synchronous. The decorator channels fire at module-load /
194+ // decoration time (right after init), which a deferred subscription
195+ // could miss.
196+ waitForTracingChannelBinding ( ( ) => {
197+ bindTracingChannelToSpan (
198+ diagnosticsChannel . tracingChannel < ChannelContext > ( CHANNELS . NESTJS_APP_CREATION ) ,
199+ data => {
200+ const moduleCls = data . arguments ?. [ 0 ] as { name ?: string } | undefined ;
201+ return startInactiveSpan ( {
202+ name : 'Create Nest App' ,
203+ op : `${ TYPE_APP_CREATION } .nestjs` ,
204+ attributes : {
205+ [ ATTR_COMPONENT ] : NESTJS_COMPONENT ,
206+ [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : ORIGIN_NESTJS ,
207+ [ ATTR_NESTJS_TYPE ] : TYPE_APP_CREATION ,
208+ ...( data . moduleVersion ? { [ ATTR_NESTJS_VERSION ] : data . moduleVersion } : { } ) ,
209+ ...( moduleCls ?. name ? { [ ATTR_NESTJS_MODULE ] : moduleCls . name } : { } ) ,
210+ } ,
211+ } ) ;
212+ } ,
213+ { captureError : false } ,
214+ ) ;
215+ } ) ;
193216
194217 // request_context + request_handler. `RouterExecutionContext.create`
195218 // runs once per route at setup: it receives `(instance, callback, ...)`
0 commit comments