dd-trace 4.45.0 → 4.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/LICENSE-3rdparty.csv +2 -0
  2. package/index.d.ts +20 -8
  3. package/package.json +11 -5
  4. package/packages/datadog-instrumentations/src/aerospike.js +1 -1
  5. package/packages/datadog-instrumentations/src/apollo-server.js +1 -1
  6. package/packages/datadog-instrumentations/src/aws-sdk.js +4 -4
  7. package/packages/datadog-instrumentations/src/body-parser.js +4 -4
  8. package/packages/datadog-instrumentations/src/cassandra-driver.js +2 -2
  9. package/packages/datadog-instrumentations/src/child_process.js +2 -2
  10. package/packages/datadog-instrumentations/src/connect.js +4 -4
  11. package/packages/datadog-instrumentations/src/cookie-parser.js +4 -4
  12. package/packages/datadog-instrumentations/src/couchbase.js +12 -12
  13. package/packages/datadog-instrumentations/src/cucumber.js +294 -56
  14. package/packages/datadog-instrumentations/src/dns.js +10 -10
  15. package/packages/datadog-instrumentations/src/elasticsearch.js +4 -4
  16. package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +3 -3
  17. package/packages/datadog-instrumentations/src/express.js +4 -4
  18. package/packages/datadog-instrumentations/src/fastify.js +6 -6
  19. package/packages/datadog-instrumentations/src/fetch.js +1 -1
  20. package/packages/datadog-instrumentations/src/find-my-way.js +2 -2
  21. package/packages/datadog-instrumentations/src/fs.js +2 -2
  22. package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +2 -2
  23. package/packages/datadog-instrumentations/src/grpc/client.js +4 -6
  24. package/packages/datadog-instrumentations/src/grpc/server.js +2 -2
  25. package/packages/datadog-instrumentations/src/hapi.js +10 -13
  26. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  27. package/packages/datadog-instrumentations/src/http/client.js +3 -3
  28. package/packages/datadog-instrumentations/src/jest.js +8 -5
  29. package/packages/datadog-instrumentations/src/kafkajs.js +67 -31
  30. package/packages/datadog-instrumentations/src/knex.js +2 -2
  31. package/packages/datadog-instrumentations/src/koa.js +5 -5
  32. package/packages/datadog-instrumentations/src/ldapjs.js +1 -1
  33. package/packages/datadog-instrumentations/src/mariadb.js +8 -8
  34. package/packages/datadog-instrumentations/src/memcached.js +2 -2
  35. package/packages/datadog-instrumentations/src/microgateway-core.js +7 -5
  36. package/packages/datadog-instrumentations/src/mocha/common.js +1 -1
  37. package/packages/datadog-instrumentations/src/mocha/main.js +139 -53
  38. package/packages/datadog-instrumentations/src/mocha/utils.js +37 -18
  39. package/packages/datadog-instrumentations/src/mocha/worker.js +29 -1
  40. package/packages/datadog-instrumentations/src/mocha.js +4 -0
  41. package/packages/datadog-instrumentations/src/moleculer/server.js +2 -2
  42. package/packages/datadog-instrumentations/src/mongodb-core.js +7 -7
  43. package/packages/datadog-instrumentations/src/mongoose.js +5 -6
  44. package/packages/datadog-instrumentations/src/mysql.js +3 -3
  45. package/packages/datadog-instrumentations/src/mysql2.js +6 -6
  46. package/packages/datadog-instrumentations/src/net.js +2 -2
  47. package/packages/datadog-instrumentations/src/next.js +5 -5
  48. package/packages/datadog-instrumentations/src/openai.js +62 -71
  49. package/packages/datadog-instrumentations/src/oracledb.js +8 -8
  50. package/packages/datadog-instrumentations/src/passport-http.js +1 -1
  51. package/packages/datadog-instrumentations/src/passport-local.js +1 -1
  52. package/packages/datadog-instrumentations/src/passport-utils.js +1 -1
  53. package/packages/datadog-instrumentations/src/pg.js +60 -5
  54. package/packages/datadog-instrumentations/src/pino.js +4 -4
  55. package/packages/datadog-instrumentations/src/playwright.js +6 -4
  56. package/packages/datadog-instrumentations/src/redis.js +2 -2
  57. package/packages/datadog-instrumentations/src/restify.js +4 -4
  58. package/packages/datadog-instrumentations/src/rhea.js +4 -4
  59. package/packages/datadog-instrumentations/src/router.js +5 -5
  60. package/packages/datadog-instrumentations/src/sharedb.js +2 -2
  61. package/packages/datadog-instrumentations/src/vitest.js +188 -12
  62. package/packages/datadog-instrumentations/src/winston.js +2 -3
  63. package/packages/datadog-plugin-amqplib/src/consumer.js +1 -3
  64. package/packages/datadog-plugin-aws-sdk/src/base.js +33 -0
  65. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
  66. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +2 -0
  67. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -1
  68. package/packages/datadog-plugin-cucumber/src/index.js +24 -1
  69. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +39 -10
  70. package/packages/datadog-plugin-cypress/src/support.js +4 -1
  71. package/packages/datadog-plugin-hapi/src/index.js +2 -2
  72. package/packages/datadog-plugin-http/src/client.js +1 -42
  73. package/packages/datadog-plugin-http2/src/client.js +1 -26
  74. package/packages/datadog-plugin-jest/src/index.js +18 -1
  75. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +20 -0
  76. package/packages/datadog-plugin-kafkajs/src/consumer.js +1 -2
  77. package/packages/datadog-plugin-kafkajs/src/index.js +3 -1
  78. package/packages/datadog-plugin-mocha/src/index.js +18 -0
  79. package/packages/datadog-plugin-openai/src/index.js +85 -65
  80. package/packages/datadog-plugin-playwright/src/index.js +9 -0
  81. package/packages/datadog-plugin-rhea/src/consumer.js +1 -3
  82. package/packages/datadog-plugin-vitest/src/index.js +68 -3
  83. package/packages/datadog-shimmer/src/shimmer.js +144 -10
  84. package/packages/dd-trace/src/appsec/addresses.js +3 -1
  85. package/packages/dd-trace/src/appsec/blocking.js +23 -17
  86. package/packages/dd-trace/src/appsec/channels.js +4 -2
  87. package/packages/dd-trace/src/appsec/graphql.js +3 -1
  88. package/packages/dd-trace/src/appsec/iast/iast-log.js +2 -1
  89. package/packages/dd-trace/src/appsec/rasp/index.js +103 -0
  90. package/packages/dd-trace/src/appsec/rasp/sql_injection.js +86 -0
  91. package/packages/dd-trace/src/appsec/rasp/ssrf.js +37 -0
  92. package/packages/dd-trace/src/appsec/rasp/utils.js +63 -0
  93. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -0
  94. package/packages/dd-trace/src/appsec/remote_config/index.js +16 -7
  95. package/packages/dd-trace/src/appsec/remote_config/manager.js +93 -52
  96. package/packages/dd-trace/src/appsec/rule_manager.js +8 -0
  97. package/packages/dd-trace/src/appsec/telemetry.js +3 -3
  98. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +33 -14
  99. package/packages/dd-trace/src/appsec/waf/waf_manager.js +2 -1
  100. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +4 -0
  101. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +15 -1
  102. package/packages/dd-trace/src/config.js +100 -40
  103. package/packages/dd-trace/src/constants.js +11 -1
  104. package/packages/dd-trace/src/data_streams_context.js +3 -0
  105. package/packages/dd-trace/src/datastreams/fnv.js +23 -0
  106. package/packages/dd-trace/src/datastreams/pathway.js +12 -5
  107. package/packages/dd-trace/src/datastreams/processor.js +35 -0
  108. package/packages/dd-trace/src/datastreams/schemas/schema.js +8 -0
  109. package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +125 -0
  110. package/packages/dd-trace/src/datastreams/schemas/schema_sampler.js +29 -0
  111. package/packages/dd-trace/src/debugger/devtools_client/config.js +24 -0
  112. package/packages/dd-trace/src/debugger/devtools_client/index.js +57 -0
  113. package/packages/dd-trace/src/debugger/devtools_client/inspector_promises_polyfill.js +23 -0
  114. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +164 -0
  115. package/packages/dd-trace/src/debugger/devtools_client/send.js +28 -0
  116. package/packages/dd-trace/src/debugger/devtools_client/session.js +7 -0
  117. package/packages/dd-trace/src/debugger/devtools_client/state.js +47 -0
  118. package/packages/dd-trace/src/debugger/devtools_client/status.js +109 -0
  119. package/packages/dd-trace/src/debugger/index.js +92 -0
  120. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +29 -2
  121. package/packages/dd-trace/src/exporters/common/request.js +1 -1
  122. package/packages/dd-trace/src/lambda/handler.js +1 -0
  123. package/packages/dd-trace/src/lambda/index.js +12 -1
  124. package/packages/dd-trace/src/opentracing/propagation/text_map.js +1 -6
  125. package/packages/dd-trace/src/payload-tagging/config/aws.json +30 -0
  126. package/packages/dd-trace/src/payload-tagging/config/index.js +30 -0
  127. package/packages/dd-trace/src/payload-tagging/index.js +93 -0
  128. package/packages/dd-trace/src/payload-tagging/tagging.js +83 -0
  129. package/packages/dd-trace/src/plugin_manager.js +11 -10
  130. package/packages/dd-trace/src/plugins/ci_plugin.js +33 -8
  131. package/packages/dd-trace/src/plugins/util/env.js +5 -2
  132. package/packages/dd-trace/src/plugins/util/test.js +24 -4
  133. package/packages/dd-trace/src/profiler.js +15 -5
  134. package/packages/dd-trace/src/profiling/config.js +7 -4
  135. package/packages/dd-trace/src/profiling/exporter_cli.js +13 -1
  136. package/packages/dd-trace/src/profiling/exporters/agent.js +8 -2
  137. package/packages/dd-trace/src/profiling/profiler.js +0 -9
  138. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns.js +13 -0
  139. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookup.js +16 -0
  140. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookupservice.js +16 -0
  141. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_resolve.js +24 -0
  142. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_reverse.js +16 -0
  143. package/packages/dd-trace/src/profiling/profilers/event_plugins/event.js +48 -0
  144. package/packages/dd-trace/src/profiling/profilers/event_plugins/net.js +24 -0
  145. package/packages/dd-trace/src/profiling/profilers/events.js +108 -32
  146. package/packages/dd-trace/src/profiling/profilers/shared.js +5 -0
  147. package/packages/dd-trace/src/profiling/profilers/wall.js +9 -3
  148. package/packages/dd-trace/src/profiling/ssi-heuristics.js +59 -60
  149. package/packages/dd-trace/src/proxy.js +31 -24
  150. package/packages/dd-trace/src/span_stats.js +4 -2
  151. package/packages/dd-trace/src/telemetry/index.js +23 -6
  152. package/packages/dd-trace/src/telemetry/logs/index.js +20 -0
  153. package/packages/dd-trace/src/appsec/rasp.js +0 -176
@@ -0,0 +1,48 @@
1
+ const { AsyncLocalStorage } = require('async_hooks')
2
+ const TracingPlugin = require('../../../plugins/tracing')
3
+ const { performance } = require('perf_hooks')
4
+
5
+ // We are leveraging the TracingPlugin class for its functionality to bind
6
+ // start/error/finish methods to the appropriate diagnostic channels.
7
+ class EventPlugin extends TracingPlugin {
8
+ constructor (eventHandler) {
9
+ super()
10
+ this.eventHandler = eventHandler
11
+ this.store = new AsyncLocalStorage()
12
+ this.entryType = this.constructor.entryType
13
+ }
14
+
15
+ start (startEvent) {
16
+ this.store.enterWith({
17
+ startEvent,
18
+ startTime: performance.now()
19
+ })
20
+ }
21
+
22
+ error () {
23
+ this.store.getStore().error = true
24
+ }
25
+
26
+ finish () {
27
+ const { startEvent, startTime, error } = this.store.getStore()
28
+ if (error) {
29
+ return // don't emit perf events for failed operations
30
+ }
31
+ const duration = performance.now() - startTime
32
+
33
+ const context = this.activeSpan?.context()
34
+ const _ddSpanId = context?.toSpanId()
35
+ const _ddRootSpanId = context?._trace.started[0]?.context().toSpanId() || _ddSpanId
36
+
37
+ const event = {
38
+ entryType: this.entryType,
39
+ startTime,
40
+ duration,
41
+ _ddSpanId,
42
+ _ddRootSpanId
43
+ }
44
+ this.eventHandler(this.extendEvent(event, startEvent))
45
+ }
46
+ }
47
+
48
+ module.exports = EventPlugin
@@ -0,0 +1,24 @@
1
+ const EventPlugin = require('./event')
2
+
3
+ class NetPlugin extends EventPlugin {
4
+ static get id () {
5
+ return 'net'
6
+ }
7
+
8
+ static get operation () {
9
+ return 'tcp'
10
+ }
11
+
12
+ static get entryType () {
13
+ return 'net'
14
+ }
15
+
16
+ extendEvent (event, { options }) {
17
+ event.name = 'connect'
18
+ event.detail = options
19
+
20
+ return event
21
+ }
22
+ }
23
+
24
+ module.exports = NetPlugin
@@ -1,12 +1,8 @@
1
1
  const { performance, constants, PerformanceObserver } = require('perf_hooks')
2
- const { END_TIMESTAMP_LABEL } = require('./shared')
3
- const semver = require('semver')
2
+ const { END_TIMESTAMP_LABEL, SPAN_ID_LABEL, LOCAL_ROOT_SPAN_ID_LABEL } = require('./shared')
4
3
  const { Function, Label, Line, Location, Profile, Sample, StringTable, ValueType } = require('pprof-format')
5
4
  const pprof = require('@datadog/pprof/')
6
5
 
7
- // Format of perf_hooks events changed with Node 16, we need to be mindful of it.
8
- const node16 = semver.gte(process.version, '16.0.0')
9
-
10
6
  // perf_hooks uses millis, with fractional part representing nanos. We emit nanos into the pprof file.
11
7
  const MS_TO_NS = 1000000
12
8
 
@@ -48,7 +44,7 @@ class GCDecorator {
48
44
  }
49
45
 
50
46
  decorateSample (sampleInput, item) {
51
- const { kind, flags } = node16 ? item.detail : item
47
+ const { kind, flags } = item.detail
52
48
  sampleInput.label.push(this.kindLabels[kind])
53
49
  const reasonLabel = this.getReasonLabel(flags)
54
50
  if (reasonLabel) {
@@ -140,12 +136,9 @@ class NetDecorator {
140
136
  // Keys correspond to PerformanceEntry.entryType, values are constructor
141
137
  // functions for type-specific decorators.
142
138
  const decoratorTypes = {
143
- gc: GCDecorator
144
- }
145
- // Needs at least node 16 for DNS and Net
146
- if (node16) {
147
- decoratorTypes.dns = DNSDecorator
148
- decoratorTypes.net = NetDecorator
139
+ gc: GCDecorator,
140
+ dns: DNSDecorator,
141
+ net: NetDecorator
149
142
  }
150
143
 
151
144
  // Translates performance entries into pprof samples.
@@ -168,10 +161,12 @@ class EventSerializer {
168
161
  this.locationId = [location.id]
169
162
 
170
163
  this.timestampLabelKey = this.stringTable.dedup(END_TIMESTAMP_LABEL)
164
+ this.spanIdKey = this.stringTable.dedup(SPAN_ID_LABEL)
165
+ this.rootSpanIdKey = this.stringTable.dedup(LOCAL_ROOT_SPAN_ID_LABEL)
171
166
  }
172
167
 
173
168
  addEvent (item) {
174
- const { entryType, startTime, duration } = item
169
+ const { entryType, startTime, duration, _ddSpanId, _ddRootSpanId } = item
175
170
  let decorator = this.decorators[entryType]
176
171
  if (!decorator) {
177
172
  const DecoratorCtor = decoratorTypes[entryType]
@@ -186,13 +181,21 @@ class EventSerializer {
186
181
  }
187
182
  }
188
183
  const endTime = startTime + duration
184
+ const label = [
185
+ decorator.eventTypeLabel,
186
+ new Label({ key: this.timestampLabelKey, num: dateOffset + BigInt(Math.round(endTime * MS_TO_NS)) })
187
+ ]
188
+ if (_ddSpanId) {
189
+ label.push(labelFromStr(this.stringTable, this.spanIdKey, _ddSpanId))
190
+ }
191
+ if (_ddRootSpanId) {
192
+ label.push(labelFromStr(this.stringTable, this.rootSpanIdKey, _ddRootSpanId))
193
+ }
194
+
189
195
  const sampleInput = {
190
196
  value: [Math.round(duration * MS_TO_NS)],
191
197
  locationId: this.locationId,
192
- label: [
193
- decorator.eventTypeLabel,
194
- new Label({ key: this.timestampLabelKey, num: dateOffset + BigInt(Math.round(endTime * MS_TO_NS)) })
195
- ]
198
+ label
196
199
  }
197
200
  decorator.decorateSample(sampleInput, item)
198
201
  this.samples.push(new Sample(sampleInput))
@@ -219,36 +222,109 @@ class EventSerializer {
219
222
  }
220
223
 
221
224
  /**
222
- * This class generates pprof files with timeline events sourced from Node.js
223
- * performance measurement APIs.
225
+ * Class that sources timeline events through Node.js performance measurement APIs.
224
226
  */
225
- class EventsProfiler {
226
- constructor (options = {}) {
227
- this.type = 'events'
228
- this._flushIntervalNanos = (options.flushInterval || 60000) * 1e6 // 60 sec
229
- this._observer = undefined
230
- this.eventSerializer = new EventSerializer()
227
+ class NodeApiEventSource {
228
+ constructor (eventHandler, entryTypes) {
229
+ this.eventHandler = eventHandler
230
+ this.observer = undefined
231
+ this.entryTypes = entryTypes || Object.keys(decoratorTypes)
231
232
  }
232
233
 
233
234
  start () {
234
235
  // if already started, do nothing
235
- if (this._observer) return
236
+ if (this.observer) return
236
237
 
237
238
  function add (items) {
238
239
  for (const item of items.getEntries()) {
239
- this.eventSerializer.addEvent(item)
240
+ this.eventHandler(item)
240
241
  }
241
242
  }
242
- this._observer = new PerformanceObserver(add.bind(this))
243
- this._observer.observe({ entryTypes: Object.keys(decoratorTypes) })
243
+
244
+ this.observer = new PerformanceObserver(add.bind(this))
245
+ this.observer.observe({ entryTypes: this.entryTypes })
246
+ }
247
+
248
+ stop () {
249
+ if (this.observer) {
250
+ this.observer.disconnect()
251
+ this.observer = undefined
252
+ }
253
+ }
254
+ }
255
+
256
+ class DatadogInstrumentationEventSource {
257
+ constructor (eventHandler) {
258
+ this.plugins = ['dns_lookup', 'dns_lookupservice', 'dns_resolve', 'dns_reverse', 'net'].map(m => {
259
+ const Plugin = require(`./event_plugins/${m}`)
260
+ return new Plugin(eventHandler)
261
+ })
262
+
263
+ this.started = false
264
+ }
265
+
266
+ start () {
267
+ if (!this.started) {
268
+ this.plugins.forEach(p => p.configure({ enabled: true }))
269
+ this.started = true
270
+ }
244
271
  }
245
272
 
246
273
  stop () {
247
- if (this._observer) {
248
- this._observer.disconnect()
249
- this._observer = undefined
274
+ if (this.started) {
275
+ this.plugins.forEach(p => p.configure({ enabled: false }))
276
+ this.started = false
250
277
  }
251
278
  }
279
+ }
280
+
281
+ class CompositeEventSource {
282
+ constructor (sources) {
283
+ this.sources = sources
284
+ }
285
+
286
+ start () {
287
+ this.sources.forEach(s => s.start())
288
+ }
289
+
290
+ stop () {
291
+ this.sources.forEach(s => s.stop())
292
+ }
293
+ }
294
+
295
+ /**
296
+ * This class generates pprof files with timeline events. It combines an event
297
+ * source with an event serializer.
298
+ */
299
+ class EventsProfiler {
300
+ constructor (options = {}) {
301
+ this.type = 'events'
302
+ this.eventSerializer = new EventSerializer()
303
+
304
+ const eventHandler = event => {
305
+ this.eventSerializer.addEvent(event)
306
+ }
307
+
308
+ if (options.codeHotspotsEnabled) {
309
+ // Use Datadog instrumentation to collect events with span IDs. Still use
310
+ // Node API for GC events.
311
+ this.eventSource = new CompositeEventSource([
312
+ new DatadogInstrumentationEventSource(eventHandler),
313
+ new NodeApiEventSource(eventHandler, ['gc'])
314
+ ])
315
+ } else {
316
+ // Use Node API instrumentation to collect events without span IDs
317
+ this.eventSource = new NodeApiEventSource(eventHandler)
318
+ }
319
+ }
320
+
321
+ start () {
322
+ this.eventSource.start()
323
+ }
324
+
325
+ stop () {
326
+ this.eventSource.stop()
327
+ }
252
328
 
253
329
  profile (restart, startDate, endDate) {
254
330
  if (!restart) {
@@ -6,6 +6,9 @@ const END_TIMESTAMP_LABEL = 'end_timestamp_ns'
6
6
  const THREAD_NAME_LABEL = 'thread name'
7
7
  const OS_THREAD_ID_LABEL = 'os thread id'
8
8
  const THREAD_ID_LABEL = 'thread id'
9
+ const SPAN_ID_LABEL = 'span id'
10
+ const LOCAL_ROOT_SPAN_ID_LABEL = 'local root span id'
11
+
9
12
  const threadNamePrefix = isMainThread ? 'Main' : `Worker #${threadId}`
10
13
  const eventLoopThreadName = `${threadNamePrefix} Event Loop`
11
14
 
@@ -38,6 +41,8 @@ module.exports = {
38
41
  THREAD_NAME_LABEL,
39
42
  THREAD_ID_LABEL,
40
43
  OS_THREAD_ID_LABEL,
44
+ SPAN_ID_LABEL,
45
+ LOCAL_ROOT_SPAN_ID_LABEL,
41
46
  threadNamePrefix,
42
47
  eventLoopThreadName,
43
48
  getNonJSThreadsLabels,
@@ -7,7 +7,13 @@ const { HTTP_METHOD, HTTP_ROUTE, RESOURCE_NAME, SPAN_TYPE } = require('../../../
7
7
  const { WEB } = require('../../../../../ext/types')
8
8
  const runtimeMetrics = require('../../runtime_metrics')
9
9
  const telemetryMetrics = require('../../telemetry/metrics')
10
- const { END_TIMESTAMP_LABEL, getNonJSThreadsLabels, getThreadLabels } = require('./shared')
10
+ const {
11
+ END_TIMESTAMP_LABEL,
12
+ SPAN_ID_LABEL,
13
+ LOCAL_ROOT_SPAN_ID_LABEL,
14
+ getNonJSThreadsLabels,
15
+ getThreadLabels
16
+ } = require('./shared')
11
17
 
12
18
  const beforeCh = dc.channel('dd-trace:storage:before')
13
19
  const enterCh = dc.channel('dd-trace:storage:enter')
@@ -275,10 +281,10 @@ class NativeWallProfiler {
275
281
  }
276
282
 
277
283
  if (spanId) {
278
- labels['span id'] = spanId
284
+ labels[SPAN_ID_LABEL] = spanId
279
285
  }
280
286
  if (rootSpanId) {
281
- labels['local root span id'] = rootSpanId
287
+ labels[LOCAL_ROOT_SPAN_ID_LABEL] = rootSpanId
282
288
  }
283
289
  if (webTags && Object.keys(webTags).length !== 0) {
284
290
  labels['trace endpoint'] = endpointNameFromTags(webTags)
@@ -3,44 +3,11 @@
3
3
  const telemetryMetrics = require('../telemetry/metrics')
4
4
  const profilersNamespace = telemetryMetrics.manager.namespace('profilers')
5
5
  const dc = require('dc-polyfill')
6
+ const log = require('../log')
6
7
 
7
8
  // If the process lives for at least 30 seconds, it's considered long-lived
8
9
  const DEFAULT_LONG_LIVED_THRESHOLD = 30000
9
10
 
10
- const EnablementChoice = {
11
- MANUALLY_ENABLED: Symbol('SSITelemetry.EnablementChoice.MANUALLY_ENABLED'),
12
- SSI_ENABLED: Symbol('SSITelemetry.EnablementChoice.SSI_ENABLED'),
13
- SSI_NOT_ENABLED: Symbol('SSITelemetry.EnablementChoice.SSI_NOT_ENABLED'),
14
- DISABLED: Symbol('SSITelemetry.EnablementChoice.DISABLED')
15
- }
16
- Object.freeze(EnablementChoice)
17
-
18
- function getEnablementChoiceFromConfig (config) {
19
- if (config.ssi === false || config.enabled === false) {
20
- return EnablementChoice.DISABLED
21
- } else if (config.heuristicsEnabled === true) {
22
- return EnablementChoice.SSI_ENABLED
23
- } else if (config.enabled === true) {
24
- return EnablementChoice.MANUALLY_ENABLED
25
- } else {
26
- return EnablementChoice.SSI_NOT_ENABLED
27
- }
28
- }
29
-
30
- function enablementChoiceToTagValue (enablementChoice) {
31
- switch (enablementChoice) {
32
- case EnablementChoice.MANUALLY_ENABLED:
33
- return 'manually_enabled'
34
- case EnablementChoice.SSI_ENABLED:
35
- return 'ssi_enabled'
36
- case EnablementChoice.SSI_NOT_ENABLED:
37
- return 'not_enabled'
38
- case EnablementChoice.DISABLED:
39
- // Can't emit this one as a tag
40
- throw new Error('Invalid enablement choice')
41
- }
42
- }
43
-
44
11
  /**
45
12
  * This class embodies the SSI profiler-triggering heuristics and also emits telemetry metrics about
46
13
  * the profiler behavior under SSI. It emits the following metrics:
@@ -56,25 +23,48 @@ function enablementChoiceToTagValue (enablementChoice) {
56
23
  */
57
24
  class SSIHeuristics {
58
25
  constructor (config) {
59
- this.enablementChoice = getEnablementChoiceFromConfig(config)
26
+ const injectionIncludesProfiler = config.injectionEnabled.includes('profiler')
27
+ this._heuristicsActive = injectionIncludesProfiler || config.profiling.enabled === 'auto'
28
+ this._emitsTelemetry = config.injectionEnabled.length > 0 && config.profiling.enabled !== 'false'
29
+
30
+ if (this._emitsTelemetry) {
31
+ if (config.profiling.enabled === 'true') {
32
+ this.enablementChoice = 'manually_enabled'
33
+ } else if (injectionIncludesProfiler) {
34
+ this.enablementChoice = 'ssi_enabled'
35
+ } else if (config.profiling.enabled === 'auto') {
36
+ this.enablementChoice = 'auto_enabled'
37
+ } else {
38
+ this.enablementChoice = 'ssi_not_enabled'
39
+ }
40
+ }
60
41
 
61
- const longLivedThreshold = config.longLivedThreshold || DEFAULT_LONG_LIVED_THRESHOLD
42
+ const longLivedThreshold = config.profiling.longLivedThreshold || DEFAULT_LONG_LIVED_THRESHOLD
62
43
  if (typeof longLivedThreshold !== 'number' || longLivedThreshold <= 0) {
63
- throw new Error('Long-lived threshold must be a positive number')
44
+ this.longLivedThreshold = DEFAULT_LONG_LIVED_THRESHOLD
45
+ log.warn(
46
+ `Invalid SSIHeuristics.longLivedThreshold value: ${config.profiling.longLivedThreshold}. ` +
47
+ `Using default value: ${DEFAULT_LONG_LIVED_THRESHOLD}`
48
+ )
49
+ } else {
50
+ this.longLivedThreshold = longLivedThreshold
64
51
  }
65
- this.longLivedThreshold = longLivedThreshold
66
52
 
67
53
  this.hasSentProfiles = false
68
54
  this.noSpan = true
69
55
  this.shortLived = true
70
56
  }
71
57
 
72
- enabled () {
73
- return this.enablementChoice !== EnablementChoice.DISABLED
58
+ get emitsTelemetry () {
59
+ return this._emitsTelemetry
60
+ }
61
+
62
+ get heuristicsActive () {
63
+ return this._heuristicsActive
74
64
  }
75
65
 
76
66
  start () {
77
- if (this.enabled()) {
67
+ if (this.heuristicsActive || this.emitsTelemetry) {
78
68
  // Used to determine short-livedness of the process. We could use the process start time as the
79
69
  // reference point, but the tracer initialization point is more relevant, as we couldn't be
80
70
  // collecting profiles earlier anyway. The difference is not particularly significant if the
@@ -85,13 +75,17 @@ class SSIHeuristics {
85
75
  }, this.longLivedThreshold).unref()
86
76
 
87
77
  this._onSpanCreated = this._onSpanCreated.bind(this)
88
- this._onProfileSubmitted = this._onProfileSubmitted.bind(this)
89
- this._onMockProfileSubmitted = this._onMockProfileSubmitted.bind(this)
90
- this._onAppClosing = this._onAppClosing.bind(this)
91
-
92
78
  dc.subscribe('dd-trace:span:start', this._onSpanCreated)
93
- dc.subscribe('datadog:profiling:profile-submitted', this._onProfileSubmitted)
94
- dc.subscribe('datadog:profiling:mock-profile-submitted', this._onMockProfileSubmitted)
79
+
80
+ if (this.emitsTelemetry) {
81
+ this._onProfileSubmitted = this._onProfileSubmitted.bind(this)
82
+ this._onMockProfileSubmitted = this._onMockProfileSubmitted.bind(this)
83
+
84
+ dc.subscribe('datadog:profiling:profile-submitted', this._onProfileSubmitted)
85
+ dc.subscribe('datadog:profiling:mock-profile-submitted', this._onMockProfileSubmitted)
86
+ }
87
+
88
+ this._onAppClosing = this._onAppClosing.bind(this)
95
89
  dc.subscribe('datadog:telemetry:app-closing', this._onAppClosing)
96
90
  }
97
91
  }
@@ -106,6 +100,8 @@ class SSIHeuristics {
106
100
  })
107
101
  break
108
102
  default:
103
+ // injection hardening: only usage is internal, one call site with
104
+ // a function and another with undefined, so we can throw here.
109
105
  throw new TypeError('callback must be a function or undefined')
110
106
  }
111
107
  }
@@ -152,7 +148,7 @@ class SSIHeuristics {
152
148
 
153
149
  const tags = [
154
150
  'installation:ssi',
155
- `enablement_choice:${enablementChoiceToTagValue(this.enablementChoice)}`,
151
+ `enablement_choice:${this.enablementChoice}`,
156
152
  `has_sent_profiles:${this.hasSentProfiles}`,
157
153
  `heuristic_hypothetical_decision:${decision.join('_')}`
158
154
  ]
@@ -163,9 +159,9 @@ class SSIHeuristics {
163
159
  if (
164
160
  !this._emittedRuntimeId &&
165
161
  decision[0] === 'triggered' &&
166
- // When enablement choice is SSI_ENABLED, hasSentProfiles can transition from false to true when the
162
+ // When heuristics are active, hasSentProfiles can transition from false to true when the
167
163
  // profiler gets started and the first profile is submitted, so we have to wait for it.
168
- (this.enablementChoice !== EnablementChoice.SSI_ENABLED || this.hasSentProfiles)
164
+ (!this.heuristicsActive || this.hasSentProfiles)
169
165
  ) {
170
166
  // Tags won't change anymore, so we can emit the runtime ID metric now.
171
167
  this._emittedRuntimeId = true
@@ -174,17 +170,20 @@ class SSIHeuristics {
174
170
  }
175
171
 
176
172
  _onAppClosing () {
177
- this._ensureProfileMetrics()
178
- // Last ditch effort to emit a runtime ID count metric
179
- if (!this._emittedRuntimeId) {
180
- this._emittedRuntimeId = true
181
- this._runtimeIdCount.inc()
173
+ if (this.emitsTelemetry) {
174
+ this._ensureProfileMetrics()
175
+ // Last ditch effort to emit a runtime ID count metric
176
+ if (!this._emittedRuntimeId) {
177
+ this._emittedRuntimeId = true
178
+ this._runtimeIdCount.inc()
179
+ }
180
+ // So we have the metrics in the final state
181
+ this._profileCount.inc(0)
182
+
183
+ dc.unsubscribe('datadog:profiling:profile-submitted', this._onProfileSubmitted)
184
+ dc.unsubscribe('datadog:profiling:mock-profile-submitted', this._onMockProfileSubmitted)
182
185
  }
183
- // So we have the metrics in the final state
184
- this._profileCount.inc(0)
185
186
 
186
- dc.unsubscribe('datadog:profiling:profile-submitted', this._onProfileSubmitted)
187
- dc.unsubscribe('datadog:profiling:mock-profile-submitted', this._onMockProfileSubmitted)
188
187
  dc.unsubscribe('datadog:telemetry:app-closing', this._onAppClosing)
189
188
  if (this.noSpan) {
190
189
  dc.unsubscribe('dd-trace:span:start', this._onSpanCreated)
@@ -192,4 +191,4 @@ class SSIHeuristics {
192
191
  }
193
192
  }
194
193
 
195
- module.exports = { SSIHeuristics, EnablementChoice }
194
+ module.exports = { SSIHeuristics }
@@ -5,6 +5,7 @@ const Config = require('./config')
5
5
  const runtimeMetrics = require('./runtime_metrics')
6
6
  const log = require('./log')
7
7
  const { setStartupLogPluginManager } = require('./startup-log')
8
+ const DynamicInstrumentation = require('./debugger')
8
9
  const telemetry = require('./telemetry')
9
10
  const nomenclature = require('./service-naming')
10
11
  const PluginManager = require('./plugin_manager')
@@ -14,7 +15,6 @@ const dogstatsd = require('./dogstatsd')
14
15
  const NoopDogStatsDClient = require('./noop/dogstatsd')
15
16
  const spanleak = require('./spanleak')
16
17
  const { SSIHeuristics } = require('./profiling/ssi-heuristics')
17
- const telemetryLog = require('dc-polyfill').channel('datadog:telemetry:log')
18
18
  const appsecStandalone = require('./appsec/standalone')
19
19
 
20
20
  class LazyModule {
@@ -84,7 +84,7 @@ class Tracer extends NoopProxy {
84
84
  if (config.remoteConfig.enabled && !config.isCiVisibility) {
85
85
  const rc = remoteConfig.enable(config, this._modules.appsec)
86
86
 
87
- rc.on('APM_TRACING', (action, conf) => {
87
+ rc.setProductHandler('APM_TRACING', (action, conf) => {
88
88
  if (action === 'unapply') {
89
89
  config.configure({}, true)
90
90
  } else {
@@ -93,7 +93,7 @@ class Tracer extends NoopProxy {
93
93
  this._enableOrDisableTracing(config)
94
94
  })
95
95
 
96
- rc.on('AGENT_CONFIG', (action, conf) => {
96
+ rc.setProductHandler('AGENT_CONFIG', (action, conf) => {
97
97
  if (!conf?.name?.startsWith('flare-log-level.')) return
98
98
 
99
99
  if (action === 'unapply') {
@@ -104,37 +104,49 @@ class Tracer extends NoopProxy {
104
104
  }
105
105
  })
106
106
 
107
- rc.on('AGENT_TASK', (action, conf) => {
107
+ rc.setProductHandler('AGENT_TASK', (action, conf) => {
108
108
  if (action === 'unapply' || !conf) return
109
109
  if (conf.task_type !== 'tracer_flare' || !conf.args) return
110
110
 
111
111
  this._flare.enable(config)
112
112
  this._flare.module.send(conf.args)
113
113
  })
114
+
115
+ if (config.dynamicInstrumentationEnabled) {
116
+ DynamicInstrumentation.start(config, rc)
117
+ }
114
118
  }
115
119
 
116
120
  if (config.isGCPFunction || config.isAzureFunction) {
117
121
  require('./serverless').maybeStartServerlessMiniAgent(config)
118
122
  }
119
123
 
120
- const ssiHeuristics = new SSIHeuristics(config.profiling)
121
- ssiHeuristics.start()
122
- if (config.profiling.enabled) {
123
- this._profilerStarted = this._startProfiler(config)
124
- } else if (config.profiling.ssi) {
125
- const mockProfiler = require('./profiling/ssi-telemetry-mock-profiler')
126
- mockProfiler.start(config)
124
+ if (config.profiling.enabled !== 'false') {
125
+ const ssiHeuristics = new SSIHeuristics(config)
126
+ ssiHeuristics.start()
127
+ let mockProfiler = null
128
+ if (config.profiling.enabled === 'true') {
129
+ this._profilerStarted = this._startProfiler(config)
130
+ } else if (ssiHeuristics.emitsTelemetry) {
131
+ // Start a mock profiler that emits mock profile-submitted events for the telemetry.
132
+ // It will be stopped if the real profiler is started by the heuristics.
133
+ mockProfiler = require('./profiling/ssi-telemetry-mock-profiler')
134
+ mockProfiler.start(config)
135
+ }
127
136
 
128
- if (config.profiling.heuristicsEnabled) {
137
+ if (ssiHeuristics.heuristicsActive) {
129
138
  ssiHeuristics.onTriggered(() => {
130
- mockProfiler.stop()
139
+ if (mockProfiler) {
140
+ mockProfiler.stop()
141
+ }
131
142
  this._startProfiler(config)
132
- ssiHeuristics.onTriggered()
143
+ ssiHeuristics.onTriggered() // deregister this callback
133
144
  })
134
145
  }
135
- }
136
- if (!this._profilerStarted) {
137
- this._profilerStarted = Promise.resolve(false)
146
+
147
+ if (!this._profilerStarted) {
148
+ this._profilerStarted = Promise.resolve(false)
149
+ }
138
150
  }
139
151
 
140
152
  if (config.runtimeMetrics) {
@@ -163,13 +175,6 @@ class Tracer extends NoopProxy {
163
175
  return require('./profiler').start(config)
164
176
  } catch (e) {
165
177
  log.error(e)
166
- if (telemetryLog.hasSubscribers) {
167
- telemetryLog.publish({
168
- message: e.message,
169
- level: 'ERROR',
170
- stack_trace: e.stack
171
- })
172
- }
173
178
  }
174
179
  }
175
180
 
@@ -196,12 +201,14 @@ class Tracer extends NoopProxy {
196
201
  if (this._tracingInitialized) {
197
202
  this._tracer.configure(config)
198
203
  this._pluginManager.configure(config)
204
+ DynamicInstrumentation.configure(config)
199
205
  setStartupLogPluginManager(this._pluginManager)
200
206
  }
201
207
  }
202
208
 
203
209
  profilerStarted () {
204
210
  if (!this._profilerStarted) {
211
+ // injection hardening: this is only ever invoked from tests.
205
212
  throw new Error('profilerStarted() must be called after init()')
206
213
  }
207
214
  return this._profilerStarted
@@ -127,7 +127,8 @@ class SpanStatsProcessor {
127
127
  url,
128
128
  env,
129
129
  tags,
130
- appsec
130
+ appsec,
131
+ version
131
132
  } = {}) {
132
133
  this.exporter = new SpanStatsExporter({
133
134
  hostname,
@@ -143,6 +144,7 @@ class SpanStatsProcessor {
143
144
  this.env = env
144
145
  this.tags = tags || {}
145
146
  this.sequence = 0
147
+ this.version = version
146
148
 
147
149
  if (this.enabled) {
148
150
  this.timer = setInterval(this.onInterval.bind(this), interval * 1e3)
@@ -157,7 +159,7 @@ class SpanStatsProcessor {
157
159
  this.exporter.export({
158
160
  Hostname: this.hostname,
159
161
  Env: this.env,
160
- Version: version,
162
+ Version: this.version || version,
161
163
  Stats: serialized,
162
164
  Lang: 'javascript',
163
165
  TracerVersion: pkg.version,