@@ -24,8 +24,8 @@ const LogPropagator = require('./propagation/log')
2424const SpanContext = require ( './span_context' )
2525
2626// Lazy-loaded so the libdatadog initialization cost is only paid the first
27- // time native spans are selected. A missing optional libdatadog install still
28- // fails through `require('../native')` instead of falling back silently .
27+ // time native spans are selected. A corrupt native install still fails hard;
28+ // an omitted optional @datadog /libdatadog can fall back to JS agent export .
2929let nativeModule
3030function getNativeModule ( ) {
3131 if ( nativeModule === undefined ) {
@@ -34,6 +34,11 @@ function getNativeModule () {
3434 return nativeModule
3535}
3636
37+ function isMissingLibdatadog ( error ) {
38+ return error ?. code === 'MODULE_NOT_FOUND' &&
39+ / ^ C a n n o t f i n d m o d u l e [ ' " ] @ d a t a d o g \/ l i b d a t a d o g [ ' " ] / . test ( String ( error . message ) )
40+ }
41+
3742const REFERENCE_CHILD_OF = 'child_of'
3843const REFERENCE_FOLLOWS_FROM = 'follows_from'
3944
@@ -94,63 +99,90 @@ class DatadogTracer {
9499 )
95100 }
96101 this . _useJsSpans = false
97- // Native spans are the only supported APM pipeline. libdatadog is a
98- // required dependency; if NativeSpansInterface construction fails, that's
99- // a hard error and we let it propagate to the caller.
100- const NativeSpansInterface = getNativeModule ( ) . NativeSpansInterface
101-
102- const { url, hostname = defaults . hostname , port } = config
103- const agentUrl = url || new URL ( format ( {
104- protocol : 'http:' ,
105- hostname,
106- port,
107- } ) )
108-
109- this . _nativeSpans = new NativeSpansInterface ( {
110- agentUrl : agentUrl . toString ( ) ,
111- tracerVersion : pkg . version ,
112- lang : 'nodejs' ,
113- langVersion : process . version ,
114- // Bun runs on JavaScriptCore; match the legacy agent writer's
115- // Datadog-Meta-Lang-Interpreter (process.versions.bun ? 'JavaScriptCore' : 'v8').
116- langInterpreter : process . versions . bun ? 'JavaScriptCore' : ( process . jsEngine || 'v8' ) ,
117- pid : process . pid ,
118- tracerService : config . service ,
119- // Native v0.6 client stats and OTLP trace metrics are mutually exclusive
120- // (system-tests FR02): when OTLP trace metrics are enabled, config forces
121- // DD_TRACE_STATS_COMPUTATION_ENABLED=true so the OTLP stats exporter runs,
122- // but the native concentrator must NOT also ship v0.6 stats. Route stats
123- // to OTLP only in that case by leaving the native concentrator disabled.
124- statsEnabled : ( config . stats ?. DD_TRACE_STATS_COMPUTATION_ENABLED &&
125- ! config . OTEL_TRACES_SPAN_METRICS_ENABLED ) || false ,
126- hostname : config . hostname || os . hostname ( ) ,
127- env : config . env || '' ,
128- appVersion : config . version || '' ,
129- runtimeId : config . tags ?. [ 'runtime-id' ] || '' ,
130- otelSemanticsEnabled : config . DD_TRACE_OTEL_SEMANTICS_ENABLED || false ,
131- // Advertise Datadog-Client-Computed-Stats when we compute stats
132- // client-side or run in APM-standalone (apmTracingEnabled=false), so the
133- // agent skips its own APM stats/sampling for these traces.
134- clientComputedStats : config . stats ?. DD_TRACE_STATS_COMPUTATION_ENABLED || config . apmTracingEnabled === false ,
135- } )
136-
137- let otlpStatsExporter
138- if ( config . OTEL_TRACES_SPAN_METRICS_ENABLED ) {
139- const { createOtlpSpanStatsExporter } = require ( '../opentelemetry/metrics' )
140- otlpStatsExporter = createOtlpSpanStatsExporter ( config )
102+ let NativeSpansInterface
103+ try {
104+ NativeSpansInterface = getNativeModule ( ) . NativeSpansInterface
105+ } catch ( e ) {
106+ if ( isMissingLibdatadog ( e ) && config . OTEL_TRACES_EXPORTER !== 'otlp' ) {
107+ this . _useJsSpans = true
108+ this . _isCiVisibility = false
109+ let otlpStatsExporter
110+ if ( config . OTEL_TRACES_SPAN_METRICS_ENABLED ) {
111+ const { createOtlpSpanStatsExporter } = require ( '../opentelemetry/metrics' )
112+ otlpStatsExporter = createOtlpSpanStatsExporter ( config )
113+ }
114+ const Exporter = require ( '../exporters/agent' )
115+ this . _exporter = new Exporter ( config , this . _prioritySampler )
116+ this . _processor = new JsSpanProcessor (
117+ this . _exporter ,
118+ this . _prioritySampler ,
119+ config ,
120+ otlpStatsExporter
121+ )
122+ this . _url = this . _exporter . _url
123+ log . warn (
124+ 'Native spans unavailable because optional dependency %s is not installed; using JS span pipeline' ,
125+ '@datadog/libdatadog'
126+ )
127+ } else {
128+ throw e
129+ }
141130 }
142131
143- this . _exporter = new NativeExporter ( config , this . _prioritySampler , this . _nativeSpans )
144- this . _processor = new SpanProcessor (
145- this . _exporter ,
146- this . _prioritySampler ,
147- config ,
148- this . _nativeSpans ,
149- otlpStatsExporter
150- )
151- this . _url = agentUrl
132+ if ( ! this . _useJsSpans ) {
133+ const { url, hostname = defaults . hostname , port } = config
134+ const agentUrl = url || new URL ( format ( {
135+ protocol : 'http:' ,
136+ hostname,
137+ port,
138+ } ) )
152139
153- log . debug ( 'Native spans mode enabled' )
140+ this . _nativeSpans = new NativeSpansInterface ( {
141+ agentUrl : agentUrl . toString ( ) ,
142+ tracerVersion : pkg . version ,
143+ lang : 'nodejs' ,
144+ langVersion : process . version ,
145+ // Bun runs on JavaScriptCore; match the legacy agent writer's
146+ // Datadog-Meta-Lang-Interpreter (process.versions.bun ? 'JavaScriptCore' : 'v8').
147+ langInterpreter : process . versions . bun ? 'JavaScriptCore' : ( process . jsEngine || 'v8' ) ,
148+ pid : process . pid ,
149+ tracerService : config . service ,
150+ // Native v0.6 client stats and OTLP trace metrics are mutually exclusive
151+ // (system-tests FR02): when OTLP trace metrics are enabled, config forces
152+ // DD_TRACE_STATS_COMPUTATION_ENABLED=true so the OTLP stats exporter runs,
153+ // but the native concentrator must NOT also ship v0.6 stats. Route stats
154+ // to OTLP only in that case by leaving the native concentrator disabled.
155+ statsEnabled : ( config . stats ?. DD_TRACE_STATS_COMPUTATION_ENABLED &&
156+ ! config . OTEL_TRACES_SPAN_METRICS_ENABLED ) || false ,
157+ hostname : config . hostname || os . hostname ( ) ,
158+ env : config . env || '' ,
159+ appVersion : config . version || '' ,
160+ runtimeId : config . tags ?. [ 'runtime-id' ] || '' ,
161+ otelSemanticsEnabled : config . DD_TRACE_OTEL_SEMANTICS_ENABLED || false ,
162+ // Advertise Datadog-Client-Computed-Stats when we compute stats
163+ // client-side or run in APM-standalone (apmTracingEnabled=false), so the
164+ // agent skips its own APM stats/sampling for these traces.
165+ clientComputedStats : config . stats ?. DD_TRACE_STATS_COMPUTATION_ENABLED || config . apmTracingEnabled === false ,
166+ } )
167+
168+ let otlpStatsExporter
169+ if ( config . OTEL_TRACES_SPAN_METRICS_ENABLED ) {
170+ const { createOtlpSpanStatsExporter } = require ( '../opentelemetry/metrics' )
171+ otlpStatsExporter = createOtlpSpanStatsExporter ( config )
172+ }
173+
174+ this . _exporter = new NativeExporter ( config , this . _prioritySampler , this . _nativeSpans )
175+ this . _processor = new SpanProcessor (
176+ this . _exporter ,
177+ this . _prioritySampler ,
178+ config ,
179+ this . _nativeSpans ,
180+ otlpStatsExporter
181+ )
182+ this . _url = agentUrl
183+
184+ log . debug ( 'Native spans mode enabled' )
185+ }
154186 }
155187
156188 this . _propagators = {
0 commit comments