newrelic 13.8.0 → 13.9.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 (49) hide show
  1. package/NEWS.md +124 -69
  2. package/THIRD_PARTY_NOTICES.md +7 -7
  3. package/lib/config/default.js +0 -13
  4. package/lib/health-reporter.js +6 -1
  5. package/lib/instrumentations.js +0 -1
  6. package/lib/metrics/names.js +39 -12
  7. package/lib/otel/traces/segment-synthesis.js +6 -30
  8. package/lib/otel/traces/span-processor.js +80 -18
  9. package/lib/samplers/adaptive-sampler.js +12 -6
  10. package/lib/samplers/index.js +37 -1
  11. package/lib/samplers/ratio-based-sampler.js +1 -2
  12. package/lib/samplers/sampler.js +4 -2
  13. package/lib/serverless/aws-lambda.js +18 -12
  14. package/lib/spans/helpers.js +1 -63
  15. package/lib/spans/span-event-aggregator.js +10 -13
  16. package/lib/spans/span-event.js +210 -61
  17. package/lib/spans/streaming-span-event-aggregator.js +4 -10
  18. package/lib/spans/streaming-span-event.js +4 -10
  19. package/lib/spans/timed-event.js +77 -0
  20. package/lib/subscriber-configs.js +2 -0
  21. package/lib/subscribers/base.js +9 -2
  22. package/lib/subscribers/ioredis/index.js +2 -2
  23. package/lib/subscribers/iovalkey/config.js +21 -0
  24. package/lib/subscribers/iovalkey/index.js +15 -0
  25. package/lib/subscribers/langchain/base.js +161 -0
  26. package/lib/subscribers/langchain/chain-callback-manager.js +14 -0
  27. package/lib/subscribers/langchain/config.js +163 -0
  28. package/lib/subscribers/langchain/runnable-stream.js +149 -0
  29. package/lib/subscribers/langchain/runnable.js +56 -0
  30. package/lib/subscribers/langchain/tool-callback-manager.js +24 -0
  31. package/lib/subscribers/langchain/tool.js +115 -0
  32. package/lib/subscribers/langchain/vectorstore.js +133 -0
  33. package/lib/subscribers/openai/base.js +1 -1
  34. package/lib/subscribers/openai/chat.js +8 -0
  35. package/lib/subscribers/openai/embeddings.js +8 -0
  36. package/lib/subscribers/undici/index.js +11 -14
  37. package/lib/symbols.js +2 -0
  38. package/lib/tracking-packages.js +9 -1
  39. package/lib/transaction/index.js +18 -2
  40. package/lib/transaction/trace/index.js +4 -79
  41. package/lib/transaction/trace/partial-trace.js +243 -0
  42. package/lib/transaction/trace/segment.js +18 -1
  43. package/package.json +2 -2
  44. package/lib/instrumentation/langchain/callback-manager.js +0 -25
  45. package/lib/instrumentation/langchain/common.js +0 -69
  46. package/lib/instrumentation/langchain/nr-hooks.js +0 -42
  47. package/lib/instrumentation/langchain/runnable.js +0 -310
  48. package/lib/instrumentation/langchain/tools.js +0 -78
  49. package/lib/instrumentation/langchain/vectorstore.js +0 -141
@@ -11,8 +11,17 @@ const urltils = require('#agentlib/util/urltils.js')
11
11
 
12
12
  const AttributeReconciler = require('./attr-reconciler.js')
13
13
  const SegmentSynthesizer = require('./segment-synthesis.js')
14
+ const SpanLink = require('#agentlib/spans/span-link.js')
15
+ const TimedEvent = require('#agentlib/spans/timed-event.js')
16
+ const normalizeTimestamp = require('../normalize-timestamp.js')
14
17
  const { otelSynthesis } = require('#agentlib/symbols.js')
15
- const { assignToTarget, buildRuleMappings, extractAttributeValue, processRegex, transformTemplate } = require('./utils.js')
18
+ const {
19
+ assignToTarget,
20
+ buildRuleMappings,
21
+ extractAttributeValue,
22
+ processRegex,
23
+ transformTemplate
24
+ } = require('./utils.js')
16
25
  const defaultLogger = require('#agentlib/logger.js').child({ component: 'span-processor' })
17
26
 
18
27
  const {
@@ -44,15 +53,30 @@ module.exports = class NrSpanProcessor {
44
53
  }
45
54
 
46
55
  /**
47
- * Update the segment duration from span, handle errors and
48
- * map and reconcile any attributes that were added to the span.
49
- * @param {object} span otel span getting updated
56
+ * Finalize the mapping of the OTEL span to the NR `TraceSegment`
57
+ * by copying final attributes over, updating the duration, handling
58
+ * any errors, and ending the transaction if necessary.
59
+ *
60
+ * @param {object} span source otel span
50
61
  */
51
62
  onEnd(span) {
52
63
  if (span[otelSynthesis] && span[otelSynthesis].segment) {
53
64
  const { segment, transaction, rule } = span[otelSynthesis]
54
65
  const { instrumentationScope } = span
55
66
 
67
+ // OTEL spans have a status attribute. We copy this information over
68
+ // to our agent attributes as informational metadata.
69
+ let code
70
+ switch (span.status.code) {
71
+ case 0: { code = 'unset'; break }
72
+ case 1: { code = 'ok'; break }
73
+ case 2: { code = 'error'; break }
74
+ }
75
+ segment.addAttribute('status.code', code)
76
+ if (code === 'error') {
77
+ segment.addAttribute('status.description', span.status.message)
78
+ }
79
+
56
80
  // We always attach the instrumentation scope data as agent attributes
57
81
  // if the OTEL span has them set.
58
82
  // See https://opentelemetry.io/docs/specs/otel/common/mapping-to-non-otlp/#instrumentationscope
@@ -67,8 +91,14 @@ module.exports = class NrSpanProcessor {
67
91
 
68
92
  this.updateDuration(segment, span)
69
93
  this.handleError({ segment, transaction, span })
94
+ this.reconcileLinks({ otelSpan: span, segment })
95
+ this.reconcileEvents({ segment, span })
70
96
  this.reconcileAttributes({ segment, span, transaction, rule })
71
97
  delete span[otelSynthesis]
98
+
99
+ if (span.kind === SpanKind.SERVER || span.kind === SpanKind.CONSUMER) {
100
+ this.finalizeTransaction({ segment, span, transaction, rule, config: this.agent.config })
101
+ }
72
102
  }
73
103
  }
74
104
 
@@ -106,20 +136,52 @@ module.exports = class NrSpanProcessor {
106
136
  }
107
137
 
108
138
  /**
109
- * Adds attributes to the segment, transaction or trace based on the
110
- * transformation rule. If the span is a server or consumer span,
111
- * it will also finalize the transaction.
112
- * @param {object} params - The parameters for the reconciliation.
113
- * @param {object} params.segment - The segment to add attributes to.
114
- * @param {object} params.span - The span to add attributes from.
115
- * @param {object} params.transaction - The transaction to add attributes to.
116
- * @param {object} params.rule - The transformation rule to use.
139
+ * Iterates the span events that have been added to the span and attaches
140
+ * them to the `TraceSegment` in the format expected by the New Relic
141
+ * collector.
142
+ *
143
+ * @param {object} params Function parameters
144
+ * @param {object} params.span The OTEL span that may contain events.
145
+ * @param {object} params.segment The target segment to attach the events to.
117
146
  */
118
- reconcileAttributes({ segment, span, transaction, rule = {} }) {
119
- this.mapAttributes({ segment, span, transaction, rule })
147
+ reconcileEvents({ segment, span }) {
148
+ if (span.events.length < 1) {
149
+ return
150
+ }
151
+
152
+ const spanContext = span.spanContext()
153
+ for (let i = 0; i < span.events.length; i++) {
154
+ const event = new TimedEvent({
155
+ event: span.events[i],
156
+ spanContext
157
+ })
158
+ segment.addTimedEvent(event)
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Iterates the span links that have been added to the span and attaches
164
+ * them to the `TraceSegment` in the format expected by the New Relic
165
+ * collector.
166
+ *
167
+ * @param {object} params Function parameters
168
+ * @param {object} params.otelSpan The OTEL span that may contain links.
169
+ * @param {object} params.segment The target segment to attach the links to.
170
+ */
171
+ reconcileLinks({ otelSpan, segment }) {
172
+ if (otelSpan.links.length < 1) {
173
+ return
174
+ }
120
175
 
121
- if (span.kind === SpanKind.SERVER || span.kind === SpanKind.CONSUMER) {
122
- this.finalizeTransaction({ segment, span, transaction, rule, config: this.agent.config })
176
+ const spanContext = otelSpan.spanContext()
177
+ const timestamp = normalizeTimestamp(otelSpan.startTime)
178
+ for (let i = 0; i < otelSpan.links.length; i += 1) {
179
+ const link = new SpanLink({
180
+ link: otelSpan.links.at(i),
181
+ spanContext,
182
+ timestamp
183
+ })
184
+ segment.addSpanLink(link)
123
185
  }
124
186
  }
125
187
 
@@ -130,9 +192,9 @@ module.exports = class NrSpanProcessor {
130
192
  * @param {object} params.segment - The segment to add attributes to.
131
193
  * @param {object} params.span - The span to add attributes from.
132
194
  * @param {object} params.transaction - The transaction to add attributes to.
133
- * @param {object} params.rule - The transformation rule to use.
195
+ * @param {object} [params.rule] - The transformation rule to use.
134
196
  */
135
- mapAttributes({ segment, span, transaction, rule }) {
197
+ reconcileAttributes({ segment, span, transaction, rule = {} }) {
136
198
  const excludeAttributes = new Set()
137
199
  for (const attribute of rule.attributes) {
138
200
  const { key, target, name, highSecurity, regex } = attribute
@@ -108,19 +108,25 @@ class AdaptiveSampler extends Sampler {
108
108
  applySamplingDecision({ transaction, tracestate, partialType }) {
109
109
  if (!transaction) return
110
110
  transaction.partialType = partialType
111
- if (tracestate) {
111
+ if (tracestate?.intrinsics) {
112
112
  // Explicitly set sampled and priority from tracestate intrinsics if available
113
- transaction.sampled = tracestate?.intrinsics ? tracestate.isSampled : null
114
- transaction.priority = tracestate?.intrinsics ? tracestate.priority : null
113
+ transaction.sampled = tracestate.isSampled
114
+ let initPriority = tracestate.priority
115
+ // If tracestate has intrinsics but lacks a priority we must generate a random priority
116
+ // if sampled, we also have to increment accordingly based on full/partial traces
117
+ if (!initPriority && tracestate.isSampled) {
118
+ initPriority = Sampler.generatePriority()
119
+ initPriority = Sampler.incrementPriority(initPriority, partialType)
120
+ }
121
+
122
+ transaction.priority = initPriority
115
123
  return
116
124
  }
117
125
 
118
126
  // If a tracestate is not defined, then do our normal priority calculation.
119
-
120
127
  const initPriority = Sampler.generatePriority()
121
128
  transaction.sampled = this.shouldSample(initPriority)
122
- // only add 1 to priority when doing a full trace
123
- transaction.priority = transaction.sampled && !partialType ? Sampler.incrementPriority(initPriority, 1) : initPriority
129
+ transaction.priority = transaction.sampled ? Sampler.incrementPriority(initPriority, partialType) : initPriority
124
130
  }
125
131
 
126
132
  /**
@@ -11,6 +11,7 @@ const AlwaysOnSampler = require('./always-on-sampler')
11
11
  const TraceIdRatioBasedSampler = require('./ratio-based-sampler')
12
12
  const logger = require('../logger').child({ component: 'samplers' })
13
13
  const { PARTIAL_TYPES } = require('../transaction')
14
+ const { SAMPLERS } = require('#agentlib/metrics/names.js')
14
15
 
15
16
  /**
16
17
  * Manages the different samplers used for distributed tracing sampling decisions.
@@ -19,7 +20,8 @@ const { PARTIAL_TYPES } = require('../transaction')
19
20
  * @typedef {object} Samplers
20
21
  * @property {boolean} fullEnabled Whether full granularity sampling is enabled.
21
22
  * @property {boolean} partialEnabled Whether partial granularity sampling is enabled.
22
- * @property {AdaptiveSampler|null} adaptiveSampler The global adaptive sampler instance, this is shared if `sampling_target` is not defined for a sampler type of `adaptive`.
23
+ * @property {AdaptiveSampler|null} adaptiveSampler The global adaptive sampler instance;
24
+ * this is shared if `sampling_target` is not defined for a sampler type of `adaptive`.
23
25
  * @property {Sampler} root The root sampler for traces originating in application.
24
26
  * @property {Sampler} remoteParentSampled The sampler for traces with a remote parent that is sampled.
25
27
  * @property {Sampler} remoteParentNotSampled The sampler for traces with a remote parent that is not sampled.
@@ -39,6 +41,40 @@ class Samplers {
39
41
  this.partialRoot = this.determineSampler({ agent, sampler: 'root', isPartial: true })
40
42
  this.partialRemoteParentSampled = this.determineSampler({ agent, sampler: 'remote_parent_sampled', isPartial: true })
41
43
  this.partialRemoteParentNotSampled = this.determineSampler({ agent, sampler: 'remote_parent_not_sampled', isPartial: true })
44
+ this.#sendCoreTracingMetrics(agent)
45
+ }
46
+
47
+ /**
48
+ * Sends relevant Core Tracing metrics on startup in the form:
49
+ * `Supportability/Nodejs/<granularity type>/<sampler section>/<sampler type>`
50
+ * @param {Agent} agent agent instance with these `Samplers`
51
+ */
52
+ #sendCoreTracingMetrics(agent) {
53
+ const metrics = agent.metrics
54
+ if (this.partialEnabled) {
55
+ // Supportability/Nodejs/PartialGranularity/<sampler section>/<sampler type>
56
+ metrics.getOrCreateMetric(`${SAMPLERS.PARTIAL.ROOT}/${this.#determineSamplerType(this.partialRoot)}`)
57
+ metrics.getOrCreateMetric(`${SAMPLERS.PARTIAL.PARENT_SAMPLED}/${this.#determineSamplerType(this.partialRemoteParentSampled)}`)
58
+ metrics.getOrCreateMetric(`${SAMPLERS.PARTIAL.PARENT_NOT_SAMPLED}/${this.#determineSamplerType(this.partialRemoteParentNotSampled)}`)
59
+ }
60
+ if (this.fullEnabled) {
61
+ // Supportability/Nodejs/PartialGranularity/<sampler section>/<sampler type>
62
+ metrics.getOrCreateMetric(`${SAMPLERS.FULL.ROOT}/${this.#determineSamplerType(this.root)}`)
63
+ metrics.getOrCreateMetric(`${SAMPLERS.FULL.PARENT_SAMPLED}/${this.#determineSamplerType(this.remoteParentSampled)}`)
64
+ metrics.getOrCreateMetric(`${SAMPLERS.FULL.PARENT_NOT_SAMPLED}/${this.#determineSamplerType(this.remoteParentNotSampled)}`)
65
+ }
66
+ }
67
+
68
+ /**
69
+ *
70
+ * @param {Sampler} sampler an instance of a Sampler
71
+ * @returns {string} 'Adaptive/Shared', 'Adaptive', 'AlwaysOn', 'AlwaysOff', or 'TraceIdRatioBased'
72
+ */
73
+ #determineSamplerType(sampler) {
74
+ if (sampler === this.adaptiveSampler) {
75
+ return 'Adaptive/Shared'
76
+ }
77
+ return sampler.toString().replace('Sampler', '')
42
78
  }
43
79
 
44
80
  /**
@@ -26,8 +26,7 @@ class TraceIdRatioBasedSampler extends Sampler {
26
26
  transaction.partialType = partialType
27
27
  const initPriority = Sampler.generatePriority()
28
28
  transaction.sampled = this.shouldSample(transaction.traceId)
29
- // only add 1 to priority when doing a full trace
30
- transaction.priority = transaction.sampled && !partialType ? Sampler.incrementPriority(initPriority, 1) : initPriority
29
+ transaction.priority = transaction.sampled ? Sampler.incrementPriority(initPriority, partialType) : initPriority
31
30
  }
32
31
 
33
32
  /**
@@ -45,11 +45,13 @@ class Sampler {
45
45
 
46
46
  /**
47
47
  * Used to increment priority and truncate to 6 decimal places.
48
+ * Full traces are incremented by 2 and partial traces are incremented by 1
48
49
  * @param {number} priority the current priority
49
- * @param {number} increment the amount to increment by
50
+ * @param {string|null} partialType if a partial trace
50
51
  * @returns {number} the incremented priority
51
52
  */
52
- static incrementPriority(priority, increment) {
53
+ static incrementPriority(priority, partialType) {
54
+ const increment = partialType ? 1 : 2
53
55
  const newPriority = priority + increment
54
56
  return ((newPriority * 1e6) | 0) / 1e6
55
57
  }
@@ -200,14 +200,13 @@ class AwsLambda {
200
200
  return handler.apply(this, args)
201
201
  }
202
202
 
203
- const event = args[0]
204
- const context = args[2]
203
+ const [event, responseStream, context] = args
205
204
  logger.trace('In stream handler, lambda function name', context?.functionName)
206
205
  const { segment, txnEnder } = awsLambda.createSegment({ context, event, transaction, recorder: recordBackground })
207
206
  args[1] = awsLambda.wrapStreamAndCaptureError(
208
207
  transaction,
209
208
  txnEnder,
210
- args[1]
209
+ responseStream
211
210
  )
212
211
 
213
212
  let res
@@ -238,8 +237,7 @@ class AwsLambda {
238
237
  return handler.apply(this, args)
239
238
  }
240
239
 
241
- const event = args[0]
242
- const context = args[1]
240
+ const [event, context, callback] = args
243
241
  logger.trace('Lambda function name', context?.functionName)
244
242
  const isApiGatewayLambdaProxy = apiGateway.isLambdaProxyEvent(event)
245
243
  logger.trace('Is this Lambda event an API Gateway or ALB web proxy?', isApiGatewayLambdaProxy)
@@ -256,16 +254,24 @@ class AwsLambda {
256
254
  setWebRequest(transaction, webRequest)
257
255
  resultProcessor = getApiGatewayLambdaProxyResultProcessor(transaction)
258
256
  }
259
- const cbIndex = args.length - 1
260
- args[cbIndex] = awsLambda.wrapCallbackAndCaptureError(
261
- transaction,
262
- txnEnder,
263
- args[cbIndex],
264
- resultProcessor
265
- )
257
+
258
+ if (typeof callback === 'function') {
259
+ logger.trace('Wrapping Lambda handler callback')
260
+ const cbIndex = args.length - 1
261
+ args[cbIndex] = awsLambda.wrapCallbackAndCaptureError(
262
+ transaction,
263
+ txnEnder,
264
+ callback,
265
+ resultProcessor
266
+ )
267
+ } else {
268
+ logger.trace('Lambda handler callback not present, skip wrapping')
269
+ }
266
270
 
267
271
  // context.{done,fail,succeed} are all considered deprecated by
268
272
  // AWS, but are considered functional.
273
+ // TODO: Remove when we drop Node.js 22
274
+ // see: https://aws.amazon.com/blogs/compute/node-js-24-runtime-now-available-in-aws-lambda/
269
275
  context.done = awsLambda.wrapCallbackAndCaptureError(transaction, txnEnder, context.done)
270
276
  context.fail = awsLambda.wrapCallbackAndCaptureError(transaction, txnEnder, context.fail)
271
277
  shim.wrap(context, 'succeed', function wrapSucceed(shim, original) {
@@ -60,72 +60,10 @@ function addSpanKind({ segment, span }) {
60
60
  }
61
61
  }
62
62
 
63
- /**
64
- * Checks if the segment is an entry point span.
65
- * An entry point span is defined as the base segment of a transaction.
66
- * @param {object} params to function
67
- * @param {Transaction} params.transaction active transaction
68
- * @param {TraceSegment} params.segment segment that is creating span
69
- * @returns {boolean} true if the segment is an entry point span
70
- */
71
- function isEntryPointSpan({ transaction, segment }) {
72
- return transaction?.baseSegment === segment
73
- }
74
-
75
- /**
76
- * Checks if the segment is an exit span.
77
- * An exit span is defined as a segment that is an external call,
78
- * datastore operation, or message broker operation.
79
- * @param {TraceSegment} segment segment that is creating span
80
- * @returns {boolean} true if the segment is an exit span
81
- */
82
- function isExitSpan(segment) {
83
- return REGEXS.CLIENT.EXTERNAL.test(segment.name) || REGEXS.CLIENT.DATASTORE.test(segment.name) || REGEXS.PRODUCER.test(segment.name)
84
- }
85
-
86
- /**
87
- * Determines if a span should be created based on the segment and transaction.
88
- * If the segment is an entry point span or an exit span, a span should be created.
89
- * @param {object} params to function
90
- * @param {boolean} params.entryPoint true if the segment is an entry point span
91
- * @param {TraceSegment} params.segment segment that is creating span
92
- * @returns {boolean} true if a span should be created
93
- */
94
- function shouldCreateSpan({ entryPoint, segment }) {
95
- return entryPoint ||
96
- isExitSpan(segment)
97
- }
98
-
99
- /**
100
- * Reparents a span based on the transaction and inProcessSpans.
101
- * If inProcessSpans is true or the transaction has accepted distributed trace and it's the root segment,
102
- * the parentId is not changed.
103
- * If there is a parentId and the transaction has a base segment, the span is reparented to the base segment.
104
- * If there is no parentId, the span is not reparented(indicates an entry root span for a DT trace).
105
- * @param {object} params to function
106
- * @param {boolean} params.inProcessSpans true if in-process spans are enabled
107
- * @param {boolean} params.isRoot true if the segment is the root segment
108
- * @param {string} params.parentId the parent id of the segment
109
- * @param {Transaction} params.transaction active transaction
110
- * @returns {string|null} the new parentId for the span
111
- */
112
- function reparentSpan({ inProcessSpans, isRoot, parentId, transaction }) {
113
- if (inProcessSpans || (transaction.acceptedDistributedTrace && isRoot)) {
114
- return parentId
115
- } else if (parentId && transaction?.baseSegment?.id) {
116
- return transaction.baseSegment.id
117
- } else {
118
- return null
119
- }
120
- }
121
-
122
63
  module.exports = {
123
64
  HTTP_LIBRARY,
124
65
  CATEGORIES,
125
66
  SPAN_KIND,
126
67
  REGEXS,
127
- addSpanKind,
128
- isEntryPointSpan,
129
- reparentSpan,
130
- shouldCreateSpan
68
+ addSpanKind
131
69
  }
@@ -21,7 +21,6 @@ class SpanEventAggregator extends EventAggregator {
21
21
  opts.metricNames = opts.metricNames || NAMES.SPAN_EVENTS
22
22
 
23
23
  super(opts, agent)
24
- this.inProcessSpans = agent.config.distributed_tracing.in_process_spans.enabled
25
24
  }
26
25
 
27
26
  _toPayloadSync() {
@@ -45,6 +44,11 @@ class SpanEventAggregator extends EventAggregator {
45
44
  Array.prototype.push.apply(eventsPayload, event.spanLinks)
46
45
  delete event.spanLinks
47
46
  }
47
+
48
+ if (event.timedEvents.length > 0) {
49
+ Array.prototype.push.apply(eventsPayload, event.timedEvents)
50
+ delete event.timedEvents
51
+ }
48
52
  }
49
53
 
50
54
  return [this.runId, metrics, eventsPayload]
@@ -80,8 +84,9 @@ class SpanEventAggregator extends EventAggregator {
80
84
  * @param {Transaction} params.transaction active transaction
81
85
  * @param {string} [params.parentId] GUID of the parent span.
82
86
  * @param {boolean} params.isRoot if segment is root segment
87
+ * @param {boolean} params.isEntry if segment is entry point
83
88
  */
84
- addSegment({ segment, transaction, parentId, isRoot }) {
89
+ addSegment({ segment, transaction, parentId, isRoot, isEntry }) {
85
90
  // Check if the priority would be accepted before creating the event object.
86
91
 
87
92
  if (transaction.priority < this._items.getMinimumPriority()) {
@@ -91,19 +96,11 @@ class SpanEventAggregator extends EventAggregator {
91
96
  return false
92
97
  }
93
98
 
94
- const span = SpanEvent.fromSegment({ segment, transaction, parentId, isRoot, inProcessSpans: this.inProcessSpans })
99
+ const span = SpanEvent.fromSegment({ segment, transaction, parentId, isRoot, isEntry })
95
100
 
96
101
  // Do not add span to aggregator if it is part of a partial trace
97
- // We need to reparent spans or in the case of `compact`, associate
98
- // all exit spans dropped to the same entity as `nr.ids` and `nr.durations`
99
- if (transaction.partialType) {
100
- const prefix = `${this._metricNames.PARTIAL_PREFIX}/${transaction.partialType}`
101
- this._metrics.getOrCreateMetric(prefix).incrementCallCount()
102
- this._metrics.getOrCreateMetric(`${prefix}${this._metricNames.INSTRUMENTED}`).incrementCallCount()
103
- transaction.trace.addSpan({ span, id: segment.id, parentId })
104
- if (span) {
105
- this._metrics.getOrCreateMetric(`${prefix}${this._metricNames.KEPT}`).incrementCallCount()
106
- }
102
+ if (transaction.partialTrace) {
103
+ transaction.partialTrace.addSpan({ span, isEntry })
107
104
  } else if (span) {
108
105
  this.add(span, transaction.priority)
109
106
  }