dd-trace 4.30.0 → 4.32.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 (58) hide show
  1. package/index.d.ts +39 -0
  2. package/package.json +2 -2
  3. package/packages/datadog-instrumentations/src/apollo.js +101 -0
  4. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  5. package/packages/datadog-instrumentations/src/mongodb-core.js +1 -3
  6. package/packages/datadog-instrumentations/src/playwright.js +151 -6
  7. package/packages/datadog-plugin-apollo/src/gateway/execute.js +12 -0
  8. package/packages/datadog-plugin-apollo/src/gateway/fetch.js +36 -0
  9. package/packages/datadog-plugin-apollo/src/gateway/index.js +36 -0
  10. package/packages/datadog-plugin-apollo/src/gateway/plan.js +13 -0
  11. package/packages/datadog-plugin-apollo/src/gateway/postprocessing.js +12 -0
  12. package/packages/datadog-plugin-apollo/src/gateway/request.js +139 -0
  13. package/packages/datadog-plugin-apollo/src/gateway/validate.js +21 -0
  14. package/packages/datadog-plugin-apollo/src/index.js +15 -0
  15. package/packages/datadog-plugin-cucumber/src/index.js +2 -2
  16. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +2 -2
  17. package/packages/datadog-plugin-jest/src/index.js +2 -2
  18. package/packages/datadog-plugin-mocha/src/index.js +2 -2
  19. package/packages/datadog-plugin-playwright/src/index.js +10 -2
  20. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +23 -12
  21. package/packages/dd-trace/src/appsec/iast/path-line.js +9 -6
  22. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +1 -1
  23. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +6 -2
  24. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-telemetry.js +1 -1
  25. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +6 -7
  26. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -1
  27. package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +34 -27
  28. package/packages/dd-trace/src/appsec/iast/telemetry/namespaces.js +39 -11
  29. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +7 -6
  30. package/packages/dd-trace/src/appsec/reporter.js +24 -11
  31. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +6 -1
  32. package/packages/dd-trace/src/lambda/runtime/ritm.js +1 -1
  33. package/packages/dd-trace/src/opentracing/propagation/text_map.js +21 -2
  34. package/packages/dd-trace/src/opentracing/span.js +2 -0
  35. package/packages/dd-trace/src/opentracing/span_context.js +1 -0
  36. package/packages/dd-trace/src/plugin_manager.js +1 -2
  37. package/packages/dd-trace/src/plugins/apollo.js +50 -0
  38. package/packages/dd-trace/src/plugins/composite.js +1 -0
  39. package/packages/dd-trace/src/plugins/index.js +1 -0
  40. package/packages/dd-trace/src/plugins/tracing.js +8 -5
  41. package/packages/dd-trace/src/plugins/util/test.js +2 -2
  42. package/packages/dd-trace/src/priority_sampler.js +11 -6
  43. package/packages/dd-trace/src/profiling/profilers/events.js +79 -82
  44. package/packages/dd-trace/src/proxy.js +2 -0
  45. package/packages/dd-trace/src/service-naming/schemas/v0/web.js +24 -0
  46. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +24 -0
  47. package/packages/dd-trace/src/telemetry/index.js +15 -4
  48. package/CONTRIBUTING.md +0 -171
  49. package/MIGRATING.md +0 -224
  50. package/packages/dd-trace/src/external-logger/test/index.spec.js +0 -147
  51. package/scripts/check-proposal-labels.js +0 -71
  52. package/scripts/check_licenses.js +0 -69
  53. package/scripts/helpers/color.js +0 -8
  54. package/scripts/helpers/exec.js +0 -22
  55. package/scripts/helpers/title.js +0 -15
  56. package/scripts/install_plugin_modules.js +0 -248
  57. package/scripts/publish_docs.js +0 -21
  58. package/scripts/st.js +0 -105
@@ -171,6 +171,14 @@ class TextMapPropagator {
171
171
  carrier[traceparentKey] = spanContext.toTraceparent()
172
172
 
173
173
  ts.forVendor('dd', state => {
174
+ if (!spanContext._isRemote) {
175
+ // SpanContext was created by a ddtrace span.
176
+ // Last datadog span id should be set to the current span.
177
+ state.set('p', spanContext._spanId)
178
+ } else if (spanContext._trace.tags['_dd.parent_id']) {
179
+ // Propagate the last Datadog span id set on the remote span.
180
+ state.set('p', spanContext._trace.tags['_dd.parent_id'])
181
+ }
174
182
  state.set('s', priority)
175
183
  if (mechanism) {
176
184
  state.set('t.dm', `-${mechanism}`)
@@ -279,7 +287,8 @@ class TextMapPropagator {
279
287
  return new DatadogSpanContext({
280
288
  traceId: id(),
281
289
  spanId: null,
282
- sampling: { priority }
290
+ sampling: { priority },
291
+ isRemote: true
283
292
  })
284
293
  }
285
294
 
@@ -327,6 +336,7 @@ class TextMapPropagator {
327
336
  const spanContext = new DatadogSpanContext({
328
337
  traceId: id(traceId, 16),
329
338
  spanId: id(spanId, 16),
339
+ isRemote: true,
330
340
  sampling: { priority: parseInt(flags, 10) & 1 ? 1 : 0 },
331
341
  traceparent,
332
342
  tracestate
@@ -337,6 +347,10 @@ class TextMapPropagator {
337
347
  tracestate.forVendor('dd', state => {
338
348
  for (const [key, value] of state.entries()) {
339
349
  switch (key) {
350
+ case 'p': {
351
+ spanContext._trace.tags['_dd.parent_id'] = value
352
+ break
353
+ }
340
354
  case 's': {
341
355
  const priority = parseInt(value, 10)
342
356
  if (!Number.isInteger(priority)) continue
@@ -367,6 +381,10 @@ class TextMapPropagator {
367
381
  }
368
382
  })
369
383
 
384
+ if (!spanContext._trace.tags['_dd.parent_id']) {
385
+ spanContext._trace.tags['_dd.parent_id'] = '0000000000000000'
386
+ }
387
+
370
388
  this._extractBaggageItems(carrier, spanContext)
371
389
  return spanContext
372
390
  }
@@ -379,7 +397,8 @@ class TextMapPropagator {
379
397
 
380
398
  return new DatadogSpanContext({
381
399
  traceId: id(carrier[traceKey], radix),
382
- spanId: id(carrier[spanKey], radix)
400
+ spanId: id(carrier[spanKey], radix),
401
+ isRemote: true
383
402
  })
384
403
  }
385
404
 
@@ -266,6 +266,8 @@ class DatadogSpan {
266
266
  if (startTime) {
267
267
  spanContext._trace.startTime = startTime
268
268
  }
269
+ // SpanContext was NOT propagated from a remote parent
270
+ spanContext._isRemote = false
269
271
 
270
272
  return spanContext
271
273
  }
@@ -11,6 +11,7 @@ class DatadogSpanContext {
11
11
 
12
12
  this._traceId = props.traceId
13
13
  this._spanId = props.spanId
14
+ this._isRemote = props.isRemote ?? true
14
15
  this._parentId = props.parentId || null
15
16
  this._name = props.name
16
17
  this._isFinished = props.isFinished || false
@@ -4,7 +4,6 @@ const { channel } = require('dc-polyfill')
4
4
  const { isFalse } = require('./util')
5
5
  const plugins = require('./plugins')
6
6
  const log = require('./log')
7
- const Nomenclature = require('./service-naming')
8
7
 
9
8
  const loadChannel = channel('dd-trace:instrumentation:load')
10
9
 
@@ -102,7 +101,7 @@ module.exports = class PluginManager {
102
101
  // like instrumenter.enable()
103
102
  configure (config = {}) {
104
103
  this._tracerConfig = config
105
- Nomenclature.configure(config)
104
+ this._tracer._nomenclature.configure(config)
106
105
 
107
106
  for (const name in pluginClasses) {
108
107
  this.loadPlugin(name)
@@ -0,0 +1,50 @@
1
+ const TracingPlugin = require('./tracing')
2
+ const { storage } = require('../../../datadog-core')
3
+
4
+ class ApolloBasePlugin extends TracingPlugin {
5
+ static get id () { return 'apollo.gateway' }
6
+ static get type () { return 'web' }
7
+ static get kind () { return 'server' }
8
+
9
+ bindStart (ctx) {
10
+ const store = storage.getStore()
11
+ const childOf = store ? store.span : null
12
+
13
+ const span = this.startSpan(this.getOperationName(), {
14
+ childOf,
15
+ service: this.getServiceName(),
16
+ type: this.constructor.type,
17
+ kind: this.constructor.kind,
18
+ meta: {}
19
+ }, false)
20
+
21
+ ctx.parentStore = store
22
+ ctx.currentStore = { ...store, span }
23
+
24
+ return ctx.currentStore
25
+ }
26
+
27
+ end (ctx) {
28
+ ctx?.currentStore?.span.finish()
29
+ }
30
+
31
+ asyncStart (ctx) {
32
+ ctx?.currentStore?.span.finish()
33
+ return ctx.parentStore
34
+ }
35
+
36
+ getServiceName () {
37
+ return this.serviceName({
38
+ id: `${this.constructor.id}.${this.constructor.operation}`,
39
+ pluginConfig: this.config
40
+ })
41
+ }
42
+
43
+ getOperationName () {
44
+ return this.operationName({
45
+ id: `${this.constructor.id}.${this.constructor.operation}`
46
+ })
47
+ }
48
+ }
49
+
50
+ module.exports = ApolloBasePlugin
@@ -12,6 +12,7 @@ class CompositePlugin extends Plugin {
12
12
  }
13
13
 
14
14
  configure (config) {
15
+ super.configure(config)
15
16
  for (const name in this.constructor.plugins) {
16
17
  const pluginConfig = config[name] === false ? false : {
17
18
  ...config,
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  module.exports = {
4
+ get '@apollo/gateway' () { return require('../../../datadog-plugin-apollo/src') },
4
5
  get '@aws-sdk/smithy-client' () { return require('../../../datadog-plugin-aws-sdk/src') },
5
6
  get '@cucumber/cucumber' () { return require('../../../datadog-plugin-cucumber/src') },
6
7
  get '@playwright/test' () { return require('../../../datadog-plugin-playwright/src') },
@@ -4,7 +4,6 @@ const Plugin = require('./plugin')
4
4
  const { storage } = require('../../../datadog-core')
5
5
  const analyticsSampler = require('../analytics_sampler')
6
6
  const { COMPONENT } = require('../constants')
7
- const Nomenclature = require('../service-naming')
8
7
 
9
8
  class TracingPlugin extends Plugin {
10
9
  constructor (...args) {
@@ -29,7 +28,7 @@ class TracingPlugin extends Plugin {
29
28
  kind = this.constructor.kind
30
29
  } = opts
31
30
 
32
- return Nomenclature.serviceName(type, kind, id, opts)
31
+ return this._tracer._nomenclature.serviceName(type, kind, id, opts)
33
32
  }
34
33
 
35
34
  operationName (opts = {}) {
@@ -39,7 +38,7 @@ class TracingPlugin extends Plugin {
39
38
  kind = this.constructor.kind
40
39
  } = opts
41
40
 
42
- return Nomenclature.opName(type, kind, id, opts)
41
+ return this._tracer._nomenclature.opName(type, kind, id, opts)
43
42
  }
44
43
 
45
44
  configure (config) {
@@ -58,8 +57,12 @@ class TracingPlugin extends Plugin {
58
57
  this.activeSpan?.finish()
59
58
  }
60
59
 
61
- error (error) {
62
- this.addError(error)
60
+ error (ctxOrError) {
61
+ if (ctxOrError?.currentStore) {
62
+ ctxOrError.currentStore?.span.setTag('error', ctxOrError?.error)
63
+ return
64
+ }
65
+ this.addError(ctxOrError)
63
66
  }
64
67
 
65
68
  addTraceSubs () {
@@ -52,7 +52,7 @@ const TEST_SKIPPED_BY_ITR = 'test.skipped_by_itr'
52
52
  const TEST_CONFIGURATION_BROWSER_NAME = 'test.configuration.browser_name'
53
53
  // Early flake detection
54
54
  const TEST_IS_NEW = 'test.is_new'
55
- const TEST_EARLY_FLAKE_IS_RETRY = 'test.early_flake.is_retry'
55
+ const TEST_IS_RETRY = 'test.is_retry'
56
56
  const TEST_EARLY_FLAKE_IS_ENABLED = 'test.early_flake.is_enabled'
57
57
 
58
58
  const CI_APP_ORIGIN = 'ciapp-test'
@@ -101,7 +101,7 @@ module.exports = {
101
101
  TEST_SKIPPED_BY_ITR,
102
102
  TEST_CONFIGURATION_BROWSER_NAME,
103
103
  TEST_IS_NEW,
104
- TEST_EARLY_FLAKE_IS_RETRY,
104
+ TEST_IS_RETRY,
105
105
  TEST_EARLY_FLAKE_IS_ENABLED,
106
106
  getTestEnvironmentMetadata,
107
107
  getTestParametersString,
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const RateLimiter = require('./rate_limiter')
3
4
  const Sampler = require('./sampler')
4
5
  const { setSamplingRules } = require('./startup-log')
5
6
  const SamplingRule = require('./sampling_rule')
@@ -43,6 +44,7 @@ class PrioritySampler {
43
44
  configure (env, { sampleRate, rateLimit = 100, rules = [] } = {}) {
44
45
  this._env = env
45
46
  this._rules = this._normalizeRules(rules, sampleRate, rateLimit)
47
+ this._limiter = new RateLimiter(rateLimit)
46
48
 
47
49
  setSamplingRules(this._rules)
48
50
  }
@@ -136,14 +138,17 @@ class PrioritySampler {
136
138
  context._trace[SAMPLING_RULE_DECISION] = rule.sampleRate
137
139
  context._sampling.mechanism = SAMPLING_MECHANISM_RULE
138
140
 
139
- const sampled = rule.sample()
140
- const priority = sampled ? USER_KEEP : USER_REJECT
141
+ return rule.sample() && this._isSampledByRateLimit(context)
142
+ ? USER_KEEP
143
+ : USER_REJECT
144
+ }
141
145
 
142
- if (sampled) {
143
- context._trace[SAMPLING_LIMIT_DECISION] = rule.effectiveRate
144
- }
146
+ _isSampledByRateLimit (context) {
147
+ const allowed = this._limiter.isAllowed()
148
+
149
+ context._trace[SAMPLING_LIMIT_DECISION] = this._limiter.effectiveRate()
145
150
 
146
- return priority
151
+ return allowed
147
152
  }
148
153
 
149
154
  _getPriorityByAgent (context) {
@@ -15,6 +15,8 @@ const MS_TO_NS = 1000000
15
15
  const pprofValueType = 'timeline'
16
16
  const pprofValueUnit = 'nanoseconds'
17
17
 
18
+ const dateOffset = BigInt(Math.round(performance.timeOrigin * MS_TO_NS))
19
+
18
20
  function labelFromStr (stringTable, key, valStr) {
19
21
  return new Label({ key, str: stringTable.dedup(valStr) })
20
22
  }
@@ -146,6 +148,76 @@ if (node16) {
146
148
  decoratorTypes.net = NetDecorator
147
149
  }
148
150
 
151
+ // Translates performance entries into pprof samples.
152
+ class EventSerializer {
153
+ constructor () {
154
+ this.stringTable = new StringTable()
155
+ this.samples = []
156
+ this.locations = []
157
+ this.functions = []
158
+ this.decorators = {}
159
+
160
+ // A synthetic single-frame location to serve as the location for timeline
161
+ // samples. We need these as the profiling backend (mimicking official pprof
162
+ // tool's behavior) ignores these.
163
+ const fn = new Function({ id: this.functions.length + 1, name: this.stringTable.dedup('') })
164
+ this.functions.push(fn)
165
+ const line = new Line({ functionId: fn.id })
166
+ const location = new Location({ id: this.locations.length + 1, line: [line] })
167
+ this.locations.push(location)
168
+ this.locationId = [location.id]
169
+
170
+ this.timestampLabelKey = this.stringTable.dedup(END_TIMESTAMP_LABEL)
171
+ }
172
+
173
+ addEvent (item) {
174
+ const { entryType, startTime, duration } = item
175
+ let decorator = this.decorators[entryType]
176
+ if (!decorator) {
177
+ const DecoratorCtor = decoratorTypes[entryType]
178
+ if (DecoratorCtor) {
179
+ decorator = new DecoratorCtor(this.stringTable)
180
+ decorator.eventTypeLabel = labelFromStrStr(this.stringTable, 'event', entryType)
181
+ this.decorators[entryType] = decorator
182
+ } else {
183
+ // Shouldn't happen but it's better to not rely on observer only getting
184
+ // requested event types.
185
+ return
186
+ }
187
+ }
188
+ const endTime = startTime + duration
189
+ const sampleInput = {
190
+ value: [Math.round(duration * MS_TO_NS)],
191
+ locationId: this.locationId,
192
+ label: [
193
+ decorator.eventTypeLabel,
194
+ new Label({ key: this.timestampLabelKey, num: dateOffset + BigInt(Math.round(endTime * MS_TO_NS)) })
195
+ ]
196
+ }
197
+ decorator.decorateSample(sampleInput, item)
198
+ this.samples.push(new Sample(sampleInput))
199
+ }
200
+
201
+ createProfile (startDate, endDate) {
202
+ const timeValueType = new ValueType({
203
+ type: this.stringTable.dedup(pprofValueType),
204
+ unit: this.stringTable.dedup(pprofValueUnit)
205
+ })
206
+
207
+ return new Profile({
208
+ sampleType: [timeValueType],
209
+ timeNanos: endDate.getTime() * MS_TO_NS,
210
+ periodType: timeValueType,
211
+ period: 1,
212
+ durationNanos: (endDate.getTime() - startDate.getTime()) * MS_TO_NS,
213
+ sample: this.samples,
214
+ location: this.locations,
215
+ function: this.functions,
216
+ stringTable: this.stringTable
217
+ })
218
+ }
219
+ }
220
+
149
221
  /**
150
222
  * This class generates pprof files with timeline events sourced from Node.js
151
223
  * performance measurement APIs.
@@ -155,7 +227,7 @@ class EventsProfiler {
155
227
  this.type = 'events'
156
228
  this._flushIntervalNanos = (options.flushInterval || 60000) * 1e6 // 60 sec
157
229
  this._observer = undefined
158
- this.entries = []
230
+ this.eventSerializer = new EventSerializer()
159
231
  }
160
232
 
161
233
  start () {
@@ -163,7 +235,9 @@ class EventsProfiler {
163
235
  if (this._observer) return
164
236
 
165
237
  function add (items) {
166
- this.entries.push(...items.getEntries())
238
+ for (const item of items.getEntries()) {
239
+ this.eventSerializer.addEvent(item)
240
+ }
167
241
  }
168
242
  this._observer = new PerformanceObserver(add.bind(this))
169
243
  this._observer.observe({ entryTypes: Object.keys(decoratorTypes) })
@@ -177,89 +251,12 @@ class EventsProfiler {
177
251
  }
178
252
 
179
253
  profile (restart, startDate, endDate) {
180
- if (this.entries.length === 0) {
181
- // No events in the period; don't produce a profile
182
- return null
183
- }
184
-
185
- const stringTable = new StringTable()
186
- const locations = []
187
- const functions = []
188
-
189
- // A synthetic single-frame location to serve as the location for timeline
190
- // samples. We need these as the profiling backend (mimicking official pprof
191
- // tool's behavior) ignores these.
192
- const locationId = (() => {
193
- const fn = new Function({ id: functions.length + 1, name: stringTable.dedup('') })
194
- functions.push(fn)
195
- const line = new Line({ functionId: fn.id })
196
- const location = new Location({ id: locations.length + 1, line: [line] })
197
- locations.push(location)
198
- return [location.id]
199
- })()
200
-
201
- const decorators = {}
202
- for (const [eventType, DecoratorCtor] of Object.entries(decoratorTypes)) {
203
- const decorator = new DecoratorCtor(stringTable)
204
- decorator.eventTypeLabel = labelFromStrStr(stringTable, 'event', eventType)
205
- decorators[eventType] = decorator
206
- }
207
- const timestampLabelKey = stringTable.dedup(END_TIMESTAMP_LABEL)
208
-
209
- const dateOffset = BigInt(Math.round(performance.timeOrigin * MS_TO_NS))
210
- const lateEntries = []
211
- const perfEndDate = endDate.getTime() - performance.timeOrigin
212
- const samples = this.entries.map((item) => {
213
- const decorator = decorators[item.entryType]
214
- if (!decorator) {
215
- // Shouldn't happen but it's better to not rely on observer only getting
216
- // requested event types.
217
- return null
218
- }
219
- const { startTime, duration } = item
220
- if (startTime >= perfEndDate) {
221
- // An event past the current recording end date; save it for the next
222
- // profile. Not supposed to happen as long as there's no async activity
223
- // between capture of the endDate value in profiler.js _collect() and
224
- // here, but better be safe than sorry.
225
- lateEntries.push(item)
226
- return null
227
- }
228
- const endTime = startTime + duration
229
- const sampleInput = {
230
- value: [Math.round(duration * MS_TO_NS)],
231
- locationId,
232
- label: [
233
- decorator.eventTypeLabel,
234
- new Label({ key: timestampLabelKey, num: dateOffset + BigInt(Math.round(endTime * MS_TO_NS)) })
235
- ]
236
- }
237
- decorator.decorateSample(sampleInput, item)
238
- return new Sample(sampleInput)
239
- }).filter(v => v)
240
-
241
- this.entries = lateEntries
242
-
243
- const timeValueType = new ValueType({
244
- type: stringTable.dedup(pprofValueType),
245
- unit: stringTable.dedup(pprofValueUnit)
246
- })
247
-
248
254
  if (!restart) {
249
255
  this.stop()
250
256
  }
251
-
252
- return new Profile({
253
- sampleType: [timeValueType],
254
- timeNanos: endDate.getTime() * MS_TO_NS,
255
- periodType: timeValueType,
256
- period: 1,
257
- durationNanos: (endDate.getTime() - startDate.getTime()) * MS_TO_NS,
258
- sample: samples,
259
- location: locations,
260
- function: functions,
261
- stringTable: stringTable
262
- })
257
+ const profile = this.eventSerializer.createProfile(startDate, endDate)
258
+ this.eventSerializer = new EventSerializer()
259
+ return profile
263
260
  }
264
261
 
265
262
  encode (profile) {
@@ -6,6 +6,7 @@ const runtimeMetrics = require('./runtime_metrics')
6
6
  const log = require('./log')
7
7
  const { setStartupLogPluginManager } = require('./startup-log')
8
8
  const telemetry = require('./telemetry')
9
+ const nomenclature = require('./service-naming')
9
10
  const PluginManager = require('./plugin_manager')
10
11
  const remoteConfig = require('./appsec/remote_config')
11
12
  const AppsecSdk = require('./appsec/sdk')
@@ -17,6 +18,7 @@ class Tracer extends NoopProxy {
17
18
  super()
18
19
 
19
20
  this._initialized = false
21
+ this._nomenclature = nomenclature
20
22
  this._pluginManager = new PluginManager(this)
21
23
  this.dogstatsd = new dogstatsd.NoopDogStatsDClient()
22
24
  this._tracingInitialized = false
@@ -33,6 +33,30 @@ const web = {
33
33
  }
34
34
  },
35
35
  server: {
36
+ 'apollo.gateway.request': {
37
+ opName: () => 'apollo.gateway.request',
38
+ serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
39
+ },
40
+ 'apollo.gateway.plan': {
41
+ opName: () => 'apollo.gateway.plan',
42
+ serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
43
+ },
44
+ 'apollo.gateway.validate': {
45
+ opName: () => 'apollo.gateway.validate',
46
+ serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
47
+ },
48
+ 'apollo.gateway.execute': {
49
+ opName: () => 'apollo.gateway.execute',
50
+ serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
51
+ },
52
+ 'apollo.gateway.fetch': {
53
+ opName: () => 'apollo.gateway.fetch',
54
+ serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
55
+ },
56
+ 'apollo.gateway.postprocessing': {
57
+ opName: () => 'apollo.gateway.postprocessing',
58
+ serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
59
+ },
36
60
  grpc: {
37
61
  opName: () => DD_MAJOR <= 2 ? 'grpc.request' : 'grpc.server',
38
62
  serviceName: identityService
@@ -32,6 +32,30 @@ const web = {
32
32
  }
33
33
  },
34
34
  server: {
35
+ 'apollo.gateway.request': {
36
+ opName: () => 'apollo.gateway.request',
37
+ serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
38
+ },
39
+ 'apollo.gateway.plan': {
40
+ opName: () => 'apollo.gateway.plan',
41
+ serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
42
+ },
43
+ 'apollo.gateway.validate': {
44
+ opName: () => 'apollo.gateway.validate',
45
+ serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
46
+ },
47
+ 'apollo.gateway.execute': {
48
+ opName: () => 'apollo.gateway.execute',
49
+ serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
50
+ },
51
+ 'apollo.gateway.fetch': {
52
+ opName: () => 'apollo.gateway.fetch',
53
+ serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
54
+ },
55
+ 'apollo.gateway.postprocessing': {
56
+ opName: () => 'apollo.gateway.postprocessing',
57
+ serviceName: ({ pluginConfig, tracerService }) => pluginConfig.service || tracerService
58
+ },
35
59
  grpc: {
36
60
  opName: () => 'grpc.server.request',
37
61
  serviceName: identityService
@@ -304,30 +304,41 @@ function updateConfig (changes, config) {
304
304
  const application = createAppObject(config)
305
305
  const host = createHostObject()
306
306
 
307
- const names = {
307
+ const nameMapping = {
308
308
  sampleRate: 'DD_TRACE_SAMPLE_RATE',
309
309
  logInjection: 'DD_LOG_INJECTION',
310
310
  headerTags: 'DD_TRACE_HEADER_TAGS',
311
311
  tags: 'DD_TAGS'
312
312
  }
313
313
 
314
+ const namesNeedFormatting = new Set(['DD_TAGS', 'peerServiceMapping'])
315
+
314
316
  const configuration = []
317
+ const names = [] // list of config names whose values have been changed
315
318
 
316
319
  for (const change of changes) {
317
- const name = names[change.name] || change.name
320
+ const name = nameMapping[change.name] || change.name
321
+ names.push(name)
318
322
  const { origin, value } = change
319
323
  const entry = { name, value, origin }
320
324
 
321
325
  if (Array.isArray(value)) entry.value = value.join(',')
322
- if (entry.name === 'DD_TAGS') entry.value = formatMapForTelemetry(entry.value)
326
+ if (namesNeedFormatting.has(entry.name)) entry.value = formatMapForTelemetry(entry.value)
323
327
  if (entry.name === 'url' && entry.value) entry.value = entry.value.toString()
324
- if (entry.name === 'peerServiceMapping' || entry.name === 'tags') entry.value = formatMapForTelemetry(entry.value)
325
328
 
326
329
  configuration.push(entry)
327
330
  }
331
+
332
+ function isNotModified (entry) {
333
+ return !names.includes(entry.name)
334
+ }
335
+
328
336
  if (!configWithOrigin.length) {
329
337
  configWithOrigin = configuration
330
338
  } else {
339
+ // update configWithOrigin to contain up-to-date full list of config values for app-extended-heartbeat
340
+ configWithOrigin = configWithOrigin.filter(isNotModified)
341
+ configWithOrigin = configWithOrigin.concat(configuration)
331
342
  const { reqType, payload } = createPayload('app-client-configuration-change', { configuration })
332
343
  sendData(config, application, host, reqType, payload, updateRetryData)
333
344
  }