newrelic 12.15.0 → 12.16.1

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.
package/NEWS.md CHANGED
@@ -1,3 +1,49 @@
1
+ ### v12.16.1 (2025-03-24)
2
+
3
+ #### Features
4
+
5
+ * Assign all attributes on internal spans to segment ([#3000](https://github.com/newrelic/node-newrelic/pull/3000)) ([5403018](https://github.com/newrelic/node-newrelic/commit/54030185ba5630099b76182a52c629276af4d93e))
6
+
7
+ #### Bug fixes
8
+
9
+ * Fixed `tracer.transactionNestProxy` to create a new transaction if there is no transaction or transaction is not active ([#3007](https://github.com/newrelic/node-newrelic/pull/3007)) ([ced9e22](https://github.com/newrelic/node-newrelic/commit/ced9e22a23ddeed860c1fd88f0afb10bb94343cb))
10
+
11
+ #### Documentation
12
+
13
+ * Updated compatibility report ([#2993](https://github.com/newrelic/node-newrelic/pull/2993)) ([cdfa042](https://github.com/newrelic/node-newrelic/commit/cdfa042325a7636e84a7b19a34874b20915dac63))
14
+
15
+ #### Tests
16
+
17
+ * Fixed handling require esm for >20.19.0+ ([#3002](https://github.com/newrelic/node-newrelic/pull/3002)) ([b808e00](https://github.com/newrelic/node-newrelic/commit/b808e00c399d3c924eeeb19f9c6b9fba4695a720))
18
+
19
+ ### v12.16.0 (2025-03-17)
20
+
21
+ #### Features
22
+
23
+ * Added support for response streaming Lambda functions ([#2981](https://github.com/newrelic/node-newrelic/pull/2981)) ([61dbbf9](https://github.com/newrelic/node-newrelic/commit/61dbbf9b4a6cf69f5378387fc9c17c31671e9da4))
24
+ * Added AWS entity linking segment attributes for otel bridge ([#2978](https://github.com/newrelic/node-newrelic/pull/2978)) ([6bf1ccc](https://github.com/newrelic/node-newrelic/commit/6bf1ccc657a955b4064a7a3a473bf24948d4ff56))
25
+ * Added error handling on transactions for otel spans ([#2985](https://github.com/newrelic/node-newrelic/pull/2985)) ([4e61e09](https://github.com/newrelic/node-newrelic/commit/4e61e0935394744345c39f6b581ee86e66d0f82c))
26
+
27
+ #### Code refactoring
28
+
29
+ * Updated span event generation to assign the appropriate `span.kind` based on the segment name ([#2976](https://github.com/newrelic/node-newrelic/pull/2976)) ([697b17e](https://github.com/newrelic/node-newrelic/commit/697b17e0553111aa494d08bc33eb7043cdfa8ca6))
30
+
31
+ #### Documentation
32
+
33
+ * Updated compatibility report ([#2988](https://github.com/newrelic/node-newrelic/pull/2988)) ([ed17a6d](https://github.com/newrelic/node-newrelic/commit/ed17a6df1152a8e54cb8c8570fec0015990a4247)
34
+
35
+ #### Miscellaneous chores
36
+
37
+ * Clarified supported next.js middleware versions in docs ([#2984](https://github.com/newrelic/node-newrelic/pull/2984)) ([15cb454](https://github.com/newrelic/node-newrelic/commit/15cb454f9cc38ccc22089d62aaeea54713159aa7))
38
+ * Clarified system metrics sampler naming ([#2987](https://github.com/newrelic/node-newrelic/pull/2987)) ([8647d43](https://github.com/newrelic/node-newrelic/commit/8647d43f097d6d3d68a372824d9feb325604be96))
39
+ * Refactored loops to be simpler ([#2990](https://github.com/newrelic/node-newrelic/pull/2990)) ([79fb8e9](https://github.com/newrelic/node-newrelic/commit/79fb8e90802954b617c1c00aecc866aa065aee12))
40
+ * Removed unused transaction method ([#2986](https://github.com/newrelic/node-newrelic/pull/2986)) ([cb4e2f7](https://github.com/newrelic/node-newrelic/commit/cb4e2f7a8b84adb6d744a2083b083c92e306fbd5))
41
+ * Reverted restriction in NestJS versioned tests ([#2979](https://github.com/newrelic/node-newrelic/pull/2979)) ([ffddcab](https://github.com/newrelic/node-newrelic/commit/ffddcab6d77bfc10c0df9cbfa724bc1c8f5fb251))
42
+
43
+ #### Tests
44
+
45
+ * Fixed fastify assertions around span kind while running security agent ([#2983](https://github.com/newrelic/node-newrelic/pull/2983)) ([c641645](https://github.com/newrelic/node-newrelic/commit/c6416451f1fa6126b7dfd59f6b9267f9d2188ad0))
46
+
1
47
  ### v12.15.0 (2025-03-03)
2
48
 
3
49
  #### Features
package/README.md CHANGED
@@ -31,7 +31,7 @@ To use New Relic's Node.js agent entails these three steps, which are described
31
31
 
32
32
  3. Now, add your New Relic license key and application/service name to that file:
33
33
 
34
- ```js
34
+ ```js
35
35
  /* File: newrelic.js */
36
36
  'use strict'
37
37
  /**
@@ -45,30 +45,29 @@ To use New Relic's Node.js agent entails these three steps, which are described
45
45
  license_key: 'your new relic license key',
46
46
  /* ... rest of configuration .. */
47
47
  }
48
- ```
48
+ ```
49
49
 
50
50
  4. Finally, run your program with the `newrelic` module loaded first by using node's `-r/--require` flag.
51
51
 
52
- ```
53
- $ node -r newrelic your-program.js
54
- ```
52
+ ```sh
53
+ $ node -r newrelic your-program.js
54
+ ```
55
55
 
56
- If you cannot control how your program is run, you can load the `newrelic` module _before any other module_ in your program.
56
+ If you cannot control how your program is run, you can load the `newrelic` module _before any other module_ in your program.
57
57
 
58
- ```js
58
+ ```js
59
59
  const newrelic = require('newrelic')
60
60
 
61
61
  /* ... the rest of your program ... */
62
- ```
62
+ ```
63
63
 
64
64
  ## Next.js instrumentation
65
65
  **Note**: The minimum supported Next.js version is [12.0.9](https://github.com/vercel/next.js/releases/tag/v12.0.9). If you are using Next.js middleware the minimum supported version is [12.2.0](https://github.com/vercel/next.js/releases/tag/v12.2.0).
66
66
 
67
- The New Relic Node.js agent provides instrumentation for Next.js The instrumentation provides telemetry for server-side rendering via [getServerSideProps](https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props), [middleware](https://nextjs.org/docs/middleware), and New Relic transaction naming for both page and server requests. It does not provide any instrumentation for actions occurring during build or in client-side code. If you want telemetry data on actions occurring on the client (browser), you can [inject the browser agent](./documentation/nextjs/faqs/browser-agent.md).
67
+ The New Relic Node.js agent provides instrumentation for Next.js The instrumentation provides telemetry for server-side rendering via [`getServerSideProps`](https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props), [middleware](https://nextjs.org/docs/middleware) (limited to Next.js versions [12.2.0](https://github.com/vercel/next.js/releases/tag/v12.2.0) - [13.4.12](https://github.com/vercel/next.js/releases/tag/v13.4.12)), and New Relic transaction naming for both page and server requests. It does not provide any instrumentation for actions occurring during build or in client-side code. If you want telemetry data on actions occurring on the client (browser), you can [inject the browser agent](./documentation/nextjs/faqs/browser-agent.md).
68
68
 
69
69
  Here are documents for more in-depth explanations about [transaction naming](./documentation/nextjs/transactions.md), and [segments/spans](./documentation/nextjs/segments-and-spans.md).
70
70
 
71
-
72
71
  ### Setup
73
72
  Typically you are running a Next.js app with the `next` cli and you must load the agent via `NODE_OPTIONS`:
74
73
 
@@ -102,15 +101,15 @@ The New Relic Node.js agent includes ***_experimental_*** support for ES Modules
102
101
 
103
102
  1. If you rely on a configuration file to run the agent, you must rename the file from `newrelic.js` to `newrelic.cjs` so it can be properly loaded. All the contents of the configuration file will behave the same once you rename. See [CommonJS modules in ESM](https://nodejs.org/api/modules.html#enabling) for more details.
104
103
 
105
- ```sh
106
- $ mv newrelic.js newrelic.cjs
107
- ```
104
+ ```sh
105
+ $ mv newrelic.js newrelic.cjs
106
+ ```
108
107
 
109
108
  2. To use the newrelic ESM loader, start your program with node and use the `--experimental-loader` flag and a path to the loader file, like this:
110
109
 
111
- ```sh
112
- $ node --experimental-loader newrelic/esm-loader.mjs -r newrelic your-program.js
113
- ```
110
+ ```sh
111
+ $ node --experimental-loader newrelic/esm-loader.mjs -r newrelic your-program.js
112
+ ```
114
113
 
115
114
  **Note**: Unlike the CommonJS methods listed above, there are no alternatives to running the agent without the `--experimental-loader` flag.
116
115
 
@@ -122,11 +121,11 @@ The agent supports adding your own custom instrumentation to ES module applicati
122
121
  import newrelic from 'newrelic'
123
122
  newrelic.instrument({ moduleName: 'parse-json', isEsm: true }, function wrap(shim, parseJson, moduleName) {
124
123
  shim.wrap(parseJson.default, function wrapParseJson(shim, orig) {
125
- return function wrappedParseJson() {
126
- const result = orig.apply(this, arguments)
127
- result.instrumented = true
128
- return true
129
- }
124
+ return function wrappedParseJson() {
125
+ const result = orig.apply(this, arguments)
126
+ result.instrumented = true
127
+ return true
128
+ }
130
129
  })
131
130
  })
132
131
  ```
@@ -151,11 +150,11 @@ For more information on getting started, [check the Node.js docs](https://docs.n
151
150
 
152
151
  There are modules that can be installed and configured to accompany the Node.js agent:
153
152
 
154
- * [@newrelic/apollo-server-plugin](https://github.com/newrelic/newrelic-node-apollo-server-plugin): New Relic's official Apollo Server plugin for use with the Node.js agent.
153
+ * [`@newrelic/apollo-server-plugin`](https://github.com/newrelic/newrelic-node-apollo-server-plugin): New Relic's official Apollo Server plugin for use with the Node.js agent.
155
154
 
156
155
  There are modules included within the Node.js agent to add more instrumentation for 3rd party modules:
157
156
 
158
- * [@newrelic/native-metrics](https://github.com/newrelic/node-native-metrics): Provides hooks into the native v8 layer of Node.js to provide metrics to the Node.js agent.
157
+ * [`@newrelic/native-metrics`](https://github.com/newrelic/node-native-metrics): Provides hooks into the native v8 layer of Node.js to provide metrics to the Node.js agent.
159
158
 
160
159
  ## Usage
161
160
 
@@ -164,10 +163,10 @@ There are modules included within the Node.js agent to add more instrumentation
164
163
  The `newrelic` module returns an object with the Node.js agent's API methods attached.
165
164
 
166
165
  ```js
167
- const newrelic = require('newrelic')
166
+ const newrelic = require('newrelic')
168
167
 
169
- /* ... */
170
- newrelic.addCustomAttribute('some-attribute', 'some-value')
168
+ /* ... */
169
+ newrelic.addCustomAttribute('some-attribute', 'some-value')
171
170
  ```
172
171
 
173
172
  You can read more about using the API over on the [New Relic documentation](https://docs.newrelic.com/docs/agents/nodejs-agent/api-guides/guide-using-nodejs-agent-api) site.
@@ -182,12 +181,16 @@ These are the steps to work on core agent features, with more detail below:
182
181
 
183
182
  1. [Fork](https://github.com/newrelic/node-newrelic/fork) and clone this GitHub repository:
184
183
 
184
+ ```sh
185
185
  $ git clone git@github.com:your-user-name/node-newrelic.git
186
186
  $ cd node-newrelic
187
+ ```
187
188
 
188
189
  2. Install the project's dependencies:
189
190
 
191
+ ```sh
190
192
  $ npm install
193
+ ```
191
194
 
192
195
  Then you're all set to start programming.
193
196
 
@@ -199,11 +202,13 @@ Then you're all set to start programming.
199
202
 
200
203
  Available test suites include:
201
204
 
202
- $ npm run unit
203
- $ npm run integration
204
- $ npm run versioned
205
- $ npm run lint
206
- $ npm run smoke
205
+ ```sh
206
+ $ npm run unit
207
+ $ npm run integration
208
+ $ npm run versioned
209
+ $ npm run lint
210
+ $ npm run smoke
211
+ ```
207
212
 
208
213
  ## Further Reading
209
214
 
package/api.js CHANGED
@@ -992,6 +992,7 @@ API.prototype.startWebTransaction = function startWebTransaction(url, handle) {
992
992
  transaction: tx,
993
993
  parent
994
994
  })
995
+ tx.baseSegment.spanKind = 'server'
995
996
  const newContext = context.enterSegment({ transaction: tx, segment: tx.baseSegment })
996
997
  tx.baseSegment.start()
997
998
 
@@ -1101,6 +1102,7 @@ function startBackgroundTransaction(name, group, handle) {
1101
1102
  parent
1102
1103
  })
1103
1104
  const newContext = context.enterSegment({ transaction: tx, segment: tx.baseSegment })
1105
+ tx.baseSegment.spanKind = 'server'
1104
1106
  tx.baseSegment.partialName = group
1105
1107
  tx.baseSegment.start()
1106
1108
 
package/lib/agent.js CHANGED
@@ -21,7 +21,7 @@ const MetricNormalizer = require('./metrics/normalizer')
21
21
  const MetricAggregator = require('./metrics/metric-aggregator')
22
22
  const NAMES = require('./metrics/names')
23
23
  const QueryTraceAggregator = require('./db/query-trace-aggregator')
24
- const sampler = require('./sampler')
24
+ const systemMetricsSampler = require('./system-metrics-sampler')
25
25
  const TransactionTraceAggregator = require('./transaction/trace/aggregator')
26
26
  const TransactionEventAggregator = require('./transaction/transaction-event-aggregator')
27
27
  const Tracer = require('./transaction/tracer')
@@ -334,7 +334,7 @@ Agent.prototype.start = function start(callback) {
334
334
  return process.nextTick(callback)
335
335
  }
336
336
 
337
- sampler.start(agent)
337
+ systemMetricsSampler.start(agent)
338
338
 
339
339
  if (this.config.serverless_mode.enabled) {
340
340
  return this._serverlessModeStart(callback)
@@ -349,7 +349,7 @@ Agent.prototype.start = function start(callback) {
349
349
  this.healthReporter.setStatus(HealthReporter.STATUS_LICENSE_KEY_MISSING)
350
350
 
351
351
  this.setState('errored')
352
- sampler.stop()
352
+ systemMetricsSampler.stop()
353
353
  return process.nextTick(function onNextTick() {
354
354
  agent.healthReporter.stop(() => {
355
355
  callback(new Error('Not starting without license key!'))
@@ -362,7 +362,7 @@ Agent.prototype.start = function start(callback) {
362
362
  if (error || response.shouldShutdownRun()) {
363
363
  agent.healthReporter.setStatus(HealthReporter.STATUS_CONNECT_ERROR)
364
364
  agent.setState('errored')
365
- sampler.stop()
365
+ systemMetricsSampler.stop()
366
366
  callback(error || new Error('Failed to connect to collector'), response && response.payload)
367
367
  return
368
368
  }
@@ -482,7 +482,7 @@ Agent.prototype.stop = function stop(callback) {
482
482
 
483
483
  this.harvester.stop()
484
484
 
485
- sampler.stop()
485
+ systemMetricsSampler.stop()
486
486
 
487
487
  this.healthReporter.setStatus(HealthReporter.STATUS_AGENT_SHUTDOWN)
488
488
  this.healthReporter.stop(() => {
@@ -29,7 +29,7 @@ module.exports = class Context {
29
29
  * @returns {Context} a newly constructed context
30
30
  */
31
31
  enterSegment({ segment, transaction = this._transaction }) {
32
- return new this.constructor(transaction, segment, this._otelCtx)
32
+ return new this.constructor(transaction, segment)
33
33
  }
34
34
 
35
35
  /**
@@ -215,8 +215,9 @@ class ErrorCollector {
215
215
  * @param {?Transaction} transaction Transaction associated with the error.
216
216
  * @param {Error} error The error to be traced.
217
217
  * @param {?object} customAttributes Custom attributes associated with the request (optional).
218
+ * @param {object} segment The segment associated with the error (optional). Only used in otel bridge.
218
219
  */
219
- add(transaction, error, customAttributes) {
220
+ add(transaction, error, customAttributes, segment) {
220
221
  if (!error) {
221
222
  return
222
223
  }
@@ -236,7 +237,7 @@ class ErrorCollector {
236
237
  const exception = new Exception({ error, timestamp, customAttributes })
237
238
 
238
239
  if (transaction) {
239
- transaction.addException(exception)
240
+ transaction.addException(exception, segment)
240
241
  } else {
241
242
  this.collect(transaction, exception)
242
243
  }
@@ -42,7 +42,7 @@ function wrapConsumer(shim, orig) {
42
42
  consumer,
43
43
  'run',
44
44
  new MessageSubscribeSpec({
45
- name: `${SEGMENT_PREFIX}#run`,
45
+ name: `${SEGMENT_PREFIX}run`,
46
46
  destinationType: shim.TOPIC,
47
47
  promise: true,
48
48
  consumer: shim.FIRST,
@@ -61,6 +61,14 @@ module.exports = {
61
61
  */
62
62
  ATTR_DB_SYSTEM: 'db.system',
63
63
 
64
+ /**
65
+ * The cloud provider of the invoked function.
66
+ *
67
+ * @example aws
68
+ * @see https://github.com/open-telemetry/opentelemetry-specification/blob/v1.7.0/specification/trace/semantic_conventions/faas.md#outgoing-invocations
69
+ */
70
+ ATTR_FAAS_INVOKED_PROVIDER: 'faas.invoked_provider',
71
+
64
72
  /**
65
73
  * The full resource URL.
66
74
  *
@@ -182,7 +190,8 @@ module.exports = {
182
190
  * Target messaging system name.
183
191
  *
184
192
  * @example kafka
185
- * @see https://opentelemetry.io/docs/specs/semconv/messaging/messaging-spans/
193
+ * @example aws_sqs
194
+ * @see https://opentelemetry.io/docs/specs/semconv/messaging/messaging-spans/#messaging-attributes
186
195
  */
187
196
  ATTR_MESSAGING_SYSTEM: 'messaging.system',
188
197
 
@@ -233,7 +242,7 @@ module.exports = {
233
242
  ATTR_NET_HOST_NAME: 'net.host.name',
234
243
 
235
244
  /**
236
- * Poort of the local HTTP server that received the request.
245
+ * Port of the local HTTP server that received the request.
237
246
  *
238
247
  * @example 80
239
248
  */
@@ -322,11 +331,35 @@ module.exports = {
322
331
  VERTICA: 'vertica',
323
332
  },
324
333
 
334
+ /**
335
+ * The exception message
336
+ */
337
+ EXCEPTION_MESSAGE: 'exception.message',
338
+
339
+ /**
340
+ * The exception stacktrace
341
+ */
342
+ EXCEPTION_STACKTRACE: 'exception.stacktrace',
343
+
344
+ /**
345
+ * The exception type
346
+ */
347
+ EXCEPTION_TYPE: 'exception.type',
348
+
325
349
  /**
326
350
  * Kinds of messaging system destinations.
327
351
  */
328
352
  MESSAGING_SYSTEM_KIND_VALUES: {
329
353
  QUEUE: 'queue',
330
354
  TOPIC: 'topic'
355
+ },
356
+
357
+ /**
358
+ * An enumeration of status codes
359
+ */
360
+ SPAN_STATUS_CODE: {
361
+ UNSET: 0,
362
+ OK: 1,
363
+ ERROR: 2
331
364
  }
332
365
  }
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  'use strict'
7
- const customRecorder = require('../../metrics/recorders/custom')
7
+ const genericRecorder = require('../../metrics/recorders/generic')
8
8
 
9
9
  module.exports = function createInternalSegment(agent, otelSpan) {
10
10
  const context = agent.tracer.getContext()
@@ -13,7 +13,7 @@ module.exports = function createInternalSegment(agent, otelSpan) {
13
13
  id: otelSpan?.spanContext()?.spanId,
14
14
  name,
15
15
  parent: context.segment,
16
- recorder: customRecorder,
16
+ recorder: genericRecorder,
17
17
  transaction: context.transaction
18
18
  })
19
19
  return { segment, transaction: context.transaction }
@@ -16,6 +16,7 @@ const {
16
16
  ATTR_DB_NAME,
17
17
  ATTR_DB_STATEMENT,
18
18
  ATTR_DB_SYSTEM,
19
+ ATTR_FAAS_INVOKED_PROVIDER,
19
20
  ATTR_FULL_URL,
20
21
  ATTR_GRPC_STATUS_CODE,
21
22
  ATTR_HTTP_METHOD,
@@ -34,9 +35,15 @@ const {
34
35
  ATTR_NET_PEER_NAME,
35
36
  ATTR_NET_PEER_PORT,
36
37
  ATTR_RPC_SYSTEM,
38
+ ATTR_RPC_SERVICE,
37
39
  ATTR_SERVER_ADDRESS,
38
40
  ATTR_SERVER_PORT,
39
41
  ATTR_URL_QUERY,
42
+ DB_SYSTEM_VALUES,
43
+ EXCEPTION_MESSAGE,
44
+ EXCEPTION_STACKTRACE,
45
+ EXCEPTION_TYPE,
46
+ SPAN_STATUS_CODE
40
47
  } = require('./constants')
41
48
  const { DESTINATIONS } = require('../config/attribute-filter')
42
49
 
@@ -62,19 +69,33 @@ module.exports = class NrSpanProcessor {
62
69
  }
63
70
 
64
71
  /**
65
- * Update the segment duration from span and reconcile
66
- * any attributes that were added after the start
72
+ * Update the segment duration from span, handle errors and
73
+ * reconcile any attributes that were added after the start
67
74
  * @param {object} span otel span getting updated
68
75
  */
69
76
  onEnd(span) {
70
77
  if (span[otelSynthesis] && span[otelSynthesis].segment) {
71
78
  const { segment, transaction } = span[otelSynthesis]
72
79
  this.updateDuration(segment, span)
80
+ this.handleError({ segment, transaction, span })
73
81
  this.reconcileAttributes({ segment, span, transaction })
74
82
  delete span[otelSynthesis]
75
83
  }
76
84
  }
77
85
 
86
+ handleError({ segment, transaction, span }) {
87
+ if (span?.status?.code === SPAN_STATUS_CODE.ERROR) {
88
+ const errorEvents = span.events.filter((event) => event.name === 'exception')
89
+ errorEvents.forEach((err) => {
90
+ const msg = err.attributes[EXCEPTION_MESSAGE] || err.attributes[EXCEPTION_TYPE] || `Error from ${segment.name}`
91
+ const stack = err.attributes[EXCEPTION_STACKTRACE]
92
+ const error = new Error(msg)
93
+ error.stack = stack
94
+ this.agent.errors.add(transaction, error, null, segment)
95
+ })
96
+ }
97
+ }
98
+
78
99
  updateDuration(segment, span) {
79
100
  segment.touch()
80
101
  const duration = hrTimeToMilliseconds(span.duration)
@@ -92,11 +113,15 @@ module.exports = class NrSpanProcessor {
92
113
  this.reconcileProducerAttributes({ segment, span })
93
114
  } else if (span.kind === SpanKind.CLIENT && (span.attributes[ATTR_HTTP_METHOD] || span.attributes[ATTR_HTTP_REQUEST_METHOD])) {
94
115
  this.reconcileHttpExternalAttributes({ segment, span })
116
+ } else {
117
+ this.#reconciler.reconcile({ segment, otelSpan: span })
95
118
  }
119
+
120
+ this.addAWSLinkingAttributes({ segment, span })
96
121
  }
97
122
 
98
123
  reconcileHttpExternalAttributes({ segment, span }) {
99
- const noOpMapper = () => {}
124
+ const noOpMapper = () => { }
100
125
  const statusCode = (value) => segment.addSpanAttribute('http.statusCode', value)
101
126
  const statusText = (value) => segment.addSpanAttribute('http.statusText', value)
102
127
  const mapper = {
@@ -147,6 +172,7 @@ module.exports = class NrSpanProcessor {
147
172
  [ATTR_MESSAGING_DESTINATION_NAME]: queueNameMapper,
148
173
  [ATTR_MESSAGING_DESTINATION]: queueNameMapper
149
174
  }
175
+
150
176
  this.#reconciler.reconcile({ segment: baseSegment, otelSpan: span, mapper })
151
177
 
152
178
  transaction.end()
@@ -240,7 +266,7 @@ module.exports = class NrSpanProcessor {
240
266
  * to be skipped because it will be the raw value.
241
267
  */
242
268
  },
243
- [ATTR_DB_STATEMENT]: () => {}
269
+ [ATTR_DB_STATEMENT]: () => { }
244
270
  }
245
271
  this.#reconciler.reconcile({ segment, otelSpan: span, mapper })
246
272
  }
@@ -252,6 +278,44 @@ module.exports = class NrSpanProcessor {
252
278
  [ATTR_MESSAGING_MESSAGE_CONVERSATION_ID]: (value) => segment.addAttribute('correlation_id', value),
253
279
  [ATTR_MESSAGING_RABBITMQ_DESTINATION_ROUTING_KEY]: (value) => segment.addAttribute('routing_key', value)
254
280
  }
281
+
255
282
  this.#reconciler.reconcile({ segment, otelSpan: span, mapper })
256
283
  }
284
+
285
+ addAWSLinkingAttributes({ segment, span }) {
286
+ const accountId = this.agent?.config?.cloud?.aws?.account_id
287
+ const region = span.attributes['faas.invoked_region'] ?? span.attributes['aws.region'] // faas.invoked_region is for Lambda and takes precedence
288
+
289
+ // DynamoDB
290
+ if (span.attributes[ATTR_DB_SYSTEM] === DB_SYSTEM_VALUES.DYNAMODB ||
291
+ span.attributes[ATTR_RPC_SERVICE]?.toLowerCase() === 'dynamodb'
292
+ ) {
293
+ const collection = span.attributes?.['aws.dynamodb.table_names'][0]
294
+ if (region && accountId && collection) {
295
+ segment.addAttribute('cloud.resource_id', `arn:aws:dynamodb:${region}:${accountId}:table/${collection}`)
296
+ }
297
+ }
298
+
299
+ // Lambda
300
+ if (span.attributes[ATTR_FAAS_INVOKED_PROVIDER] === 'aws') {
301
+ const functionName = span.attributes['faas.invoked_name']
302
+ if (region && accountId && functionName) {
303
+ segment.addAttribute('cloud.platform', 'aws_lambda')
304
+ segment.addAttribute('cloud.resource_id', `arn:aws:lambda:${region}:${accountId}:function:${functionName}`)
305
+ }
306
+ }
307
+
308
+ // SQS
309
+ if (span.attributes[ATTR_RPC_SERVICE]?.toLowerCase() === 'sqs') {
310
+ if (accountId) {
311
+ segment.addAttribute('cloud.account.id', accountId)
312
+ }
313
+ if (region) {
314
+ segment.addAttribute('cloud.region', region)
315
+ }
316
+ const messagingDestination = span.attributes[ATTR_MESSAGING_DESTINATION]
317
+ segment.addAttribute('messaging.destination.name', messagingDestination)
318
+ segment.addAttribute('messaging.system', 'aws_sqs')
319
+ }
320
+ }
257
321
  }
@@ -24,6 +24,7 @@ const EVENT_SOURCE_TYPE_KEY = `${EVENT_SOURCE_PREFIX}.eventType`
24
24
  const NAMES = require('../metrics/names')
25
25
 
26
26
  const EVENT_SOURCE_INFO = require('./event-sources')
27
+ const HANDLER_STREAMING = Symbol.for('aws.lambda.runtime.handler.streaming')
27
28
 
28
29
  // A function with no references used to stub out closures
29
30
  function cleanClosure() {}
@@ -35,7 +36,7 @@ let transactionEnders = []
35
36
  // the invocation transaction.
36
37
  let uncaughtException = null
37
38
 
38
- // Tracking the first time patchLambdaHandler is called for one off functionality
39
+ // Tracking the first time patchLambdaHandler is called for one-off functionality
39
40
  let patchCalled = false
40
41
  let coldStartRecorded = false
41
42
 
@@ -105,6 +106,45 @@ class AwsLambda {
105
106
  })
106
107
  }
107
108
 
109
+ /**
110
+ * Response-streaming handlers are identified by symbol properties on the function.
111
+ * We propagate any symbols if they're present, so that the handler keeps its signatue for any AWS features that rely on symbols
112
+ * @param handler
113
+ * @param nrHandler
114
+ */
115
+ propagateSymbols(handler, nrHandler) {
116
+ for (const symbol of Object.getOwnPropertySymbols(handler)) {
117
+ logger.trace(`Setting symbol ${symbol.toString()} on handler`)
118
+ nrHandler[symbol] = handler[symbol]
119
+ }
120
+ }
121
+
122
+ createSegment({ event, context, transaction, recorder }) {
123
+ const shim = this.shim
124
+ const functionName = context.functionName
125
+ const group = NAMES.FUNCTION.PREFIX
126
+ const transactionName = group + functionName
127
+
128
+ const activeSegment = shim.tracer.getSegment()
129
+
130
+ transaction.setPartialName(transactionName)
131
+ const txnEnder = endTransaction.bind(null, transaction, transactionEnders.length)
132
+
133
+ transactionEnders.push(txnEnder)
134
+ const segment = shim.createSegment(functionName, recorder, activeSegment)
135
+ transaction.baseSegment = segment
136
+ const awsAttributes = this._getAwsAgentAttributes(event, context)
137
+ transaction.trace.attributes.addAttributes(ATTR_DEST.TRANS_COMMON, awsAttributes)
138
+
139
+ shim.agent.setLambdaArn(context.invokedFunctionArn)
140
+
141
+ shim.agent.setLambdaFunctionVersion(context.functionVersion)
142
+ segment.addSpanAttributes(awsAttributes)
143
+
144
+ segment.start()
145
+ return { segment, txnEnder }
146
+ }
147
+
108
148
  patchLambdaHandler(handler) {
109
149
  const awsLambda = this
110
150
  const shim = this.shim
@@ -114,6 +154,11 @@ class AwsLambda {
114
154
  return handler
115
155
  }
116
156
 
157
+ const isStreamHandler = handler[HANDLER_STREAMING] === 'response'
158
+ if (isStreamHandler) {
159
+ this.agent.recordSupportability('Nodejs/Serverless/Lambda/ResponseStreaming')
160
+ }
161
+
117
162
  if (!patchCalled) {
118
163
  // Only wrap emit on process the first time patch is called.
119
164
  patchCalled = true
@@ -122,52 +167,87 @@ class AwsLambda {
122
167
  this.wrapFatal()
123
168
  }
124
169
 
125
- return shim.bindCreateTransaction(wrappedHandler, new specs.TransactionSpec({ type: shim.BG }))
170
+ const wrapper = isStreamHandler ? wrappedStreamHandler : wrappedHandler
171
+ const nrHandler = shim.bindCreateTransaction(wrapper, new specs.TransactionSpec({ type: shim.BG }))
172
+ awsLambda.propagateSymbols(handler, nrHandler)
173
+
174
+ return nrHandler
175
+
176
+ /**
177
+ * Wraps a response streaming lambda handler.
178
+ *
179
+ * Creates and applies segment based on function name, assigns attributes to transaction trace,
180
+ * listen when stream errors(log error), ends(end transaction)
181
+ *
182
+ * **Note**: AWS doesn't support response streaming with API gateway invoked lambdas.
183
+ * This means we do not handle that as it would require intercepting the stream to parse
184
+ * the response code and headers.
185
+ */
186
+ function wrappedStreamHandler() {
187
+ const transaction = shim.tracer.getTransaction()
188
+ if (!transaction) {
189
+ logger.trace('No active transaction, not wrapping streaming handler')
190
+ return handler.apply(this, arguments)
191
+ }
126
192
 
127
- function wrappedHandler() {
128
193
  const args = shim.argsToArray.apply(shim, arguments)
129
-
130
194
  const event = args[0]
131
- const context = args[1]
195
+ const context = args[2]
196
+ logger.trace('In stream handler, lambda function name', context?.functionName)
197
+ const { segment, txnEnder } = awsLambda.createSegment({ context, event, transaction, recorder: recordBackground })
198
+ args[1] = awsLambda.wrapStreamAndCaptureError(
199
+ transaction,
200
+ txnEnder,
201
+ args[1]
202
+ )
132
203
 
133
- const functionName = context.functionName
134
- const group = NAMES.FUNCTION.PREFIX
135
- const transactionName = group + functionName
204
+ let res
205
+ try {
206
+ res = shim.applySegment(handler, segment, false, this, args)
207
+ } catch (err) {
208
+ uncaughtException = err
209
+ txnEnder()
210
+ throw err
211
+ }
212
+
213
+ return res
214
+ }
136
215
 
216
+ /**
217
+ * Wraps a non response streaming lambda handler.
218
+ *
219
+ * Creates and applies segment based on function name, assigns attributes to transaction trace,
220
+ * adds handlers if api gateway to wrap request/response
221
+ * wraps the callback(if present), wraps the context `done`, `succeed`, `fail methods`, intercepts promise
222
+ * and properly ends transaction
223
+ */
224
+ function wrappedHandler() {
137
225
  const transaction = shim.tracer.getTransaction()
138
226
  if (!transaction) {
227
+ logger.trace('No active transaction, not wrapping handler')
139
228
  return handler.apply(this, arguments)
140
229
  }
141
- const activeSegment = shim.tracer.getSegment()
142
-
143
- transaction.setPartialName(transactionName)
230
+ const args = shim.argsToArray.apply(shim, arguments)
144
231
 
232
+ const event = args[0]
233
+ const context = args[1]
234
+ logger.trace('Lambda function name', context?.functionName)
145
235
  const isApiGatewayLambdaProxy = apiGateway.isLambdaProxyEvent(event)
236
+ logger.trace('Is this Lambda event an API Gateway or ALB web proxy?', isApiGatewayLambdaProxy)
237
+ logger.trace('Lambda event keys', Object.keys(event))
146
238
  const segmentRecorder = isApiGatewayLambdaProxy ? recordWeb : recordBackground
147
- const segment = shim.createSegment(functionName, segmentRecorder, activeSegment)
148
- transaction.baseSegment = segment
239
+ const { segment, txnEnder } = awsLambda.createSegment({ context, event, transaction, recorder: segmentRecorder })
240
+
149
241
  // resultProcessor is used to execute additional logic based on the
150
242
  // payload supplied to the callback.
151
243
  let resultProcessor
152
244
 
153
- logger.trace('Is this Lambda event an API Gateway or ALB web proxy?', isApiGatewayLambdaProxy)
154
- logger.trace('Lambda event keys', Object.keys(event))
155
-
156
245
  if (isApiGatewayLambdaProxy) {
157
246
  const webRequest = new apiGateway.LambdaProxyWebRequest(event)
158
247
  setWebRequest(shim, transaction, webRequest)
159
248
  resultProcessor = getApiGatewayLambdaProxyResultProcessor(transaction)
160
249
  }
161
-
162
250
  const cbIndex = args.length - 1
163
-
164
- // Add transaction ending closure to the list of functions to be called on
165
- // beforeExit (i.e. in the case that context.{done,fail,succeed} or callback
166
- // were not called).
167
- const txnEnder = endTransaction.bind(null, transaction, transactionEnders.length)
168
-
169
- transactionEnders.push(txnEnder)
170
-
171
251
  args[cbIndex] = awsLambda.wrapCallbackAndCaptureError(
172
252
  transaction,
173
253
  txnEnder,
@@ -186,16 +266,6 @@ class AwsLambda {
186
266
  }
187
267
  })
188
268
 
189
- const awsAttributes = awsLambda._getAwsAgentAttributes(event, context)
190
- transaction.trace.attributes.addAttributes(ATTR_DEST.TRANS_COMMON, awsAttributes)
191
-
192
- shim.agent.setLambdaArn(context.invokedFunctionArn)
193
-
194
- shim.agent.setLambdaFunctionVersion(context.functionVersion)
195
- segment.addSpanAttributes(awsAttributes)
196
-
197
- segment.start()
198
-
199
269
  let res
200
270
  try {
201
271
  res = shim.applySegment(handler, segment, false, this, args)
@@ -251,6 +321,18 @@ class AwsLambda {
251
321
  }
252
322
  }
253
323
 
324
+ wrapStreamAndCaptureError(transaction, txnEnder, stream) {
325
+ const shim = this.shim
326
+ stream.on('error', (error) => {
327
+ shim.agent.errors.add(transaction, error)
328
+ })
329
+
330
+ stream.on('close', () => {
331
+ txnEnder()
332
+ })
333
+ return stream
334
+ }
335
+
254
336
  _getAwsAgentAttributes(event, context) {
255
337
  const attributes = {
256
338
  'aws.lambda.arn': context.invokedFunctionArn,
@@ -372,7 +454,7 @@ function lowercaseObjectKeys(original) {
372
454
  }
373
455
 
374
456
  function endTransaction(transaction, enderIndex) {
375
- if (transactionEnders[enderIndex] === cleanClosure) {
457
+ if (transactionEnders.length === 0 || transactionEnders[enderIndex] === cleanClosure) {
376
458
  // In the case where we have already been called, we return early. There may be a
377
459
  // case where this is called more than once, given the lambda is left in a dirty
378
460
  // state after thread suspension (e.g. timeouts)
@@ -411,7 +493,7 @@ function setWebResponse(transaction, response) {
411
493
 
412
494
  // We are adding http.statusCode to base segment as
413
495
  // we found in testing async invoked lambdas, the
414
- // active segement is not available at this point.
496
+ // active segment is not available at this point.
415
497
  const segment = transaction.baseSegment
416
498
 
417
499
  segment.addSpanAttribute('http.statusCode', responseCode)
@@ -0,0 +1,69 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ const HTTP_LIBRARY = 'http'
7
+ const CATEGORIES = {
8
+ HTTP: 'http',
9
+ DATASTORE: 'datastore',
10
+ GENERIC: 'generic'
11
+ }
12
+
13
+ const SPAN_KIND = {
14
+ CONSUMER: 'consumer',
15
+ CLIENT: 'client',
16
+ INTERNAL: 'internal',
17
+ PRODUCER: 'producer',
18
+ SERVER: 'server'
19
+ }
20
+
21
+ const REGEXS = {
22
+ CONSUMER: /^(?:Truncated\/)?OtherTransaction\/Message\//,
23
+ CLIENT: {
24
+ EXTERNAL: /^(?:Truncated\/)?External\//,
25
+ DATASTORE: /^(?:Truncated\/)?Datastore\//,
26
+ },
27
+ PRODUCER: /^(?:Truncated\/)?MessageBroker\//,
28
+ SERVER: /^(?:Truncated\/)?(WebTransaction)\//
29
+ }
30
+
31
+ /**
32
+ * Assigns the appropriate span kind based on the segment name.
33
+ * Does not handle client kind as this is done in the `HttpSpanEvent` and `DatastoreSpanEvent`
34
+ * Our agent has conventions for naming all types of segments.
35
+ * The only place this convention does not exist is within the `api.startWebTransaction`
36
+ * and `api.startBackgroundTransaction`. For those, we have assigned a `spanKind` property
37
+ * on the segment. We default to `internal` if it cannot match a regex.
38
+ *
39
+ * @param {object} params to function
40
+ * @param {TraceSegment} params.segment segment that is creating span
41
+ * @param {object} params.span span to add `intrinsics['span.kind']`
42
+ */
43
+ function addSpanKind({ segment, span }) {
44
+ const intrinsics = span.getIntrinsicAttributes()
45
+ if (!intrinsics['span.kind']) {
46
+ let spanKind
47
+ if (segment.spanKind) {
48
+ spanKind = segment.spanKind
49
+ } else if (REGEXS.CONSUMER.test(segment.name)) {
50
+ spanKind = SPAN_KIND.CONSUMER
51
+ } else if (REGEXS.PRODUCER.test(segment.name)) {
52
+ spanKind = SPAN_KIND.PRODUCER
53
+ } else if (REGEXS.SERVER.test(segment.name)) {
54
+ spanKind = SPAN_KIND.SERVER
55
+ } else {
56
+ spanKind = SPAN_KIND.INTERNAL
57
+ }
58
+
59
+ span.addIntrinsicAttribute('span.kind', spanKind)
60
+ }
61
+ }
62
+
63
+ module.exports = {
64
+ HTTP_LIBRARY,
65
+ CATEGORIES,
66
+ SPAN_KIND,
67
+ REGEXS,
68
+ addSpanKind
69
+ }
@@ -9,18 +9,7 @@ const Config = require('../config')
9
9
  const { truncate } = require('../util/byte-limit')
10
10
 
11
11
  const { DESTINATIONS } = require('../config/attribute-filter')
12
-
13
- const HTTP_LIBRARY = 'http'
14
- const CLIENT_KIND = 'client'
15
- const CATEGORIES = {
16
- HTTP: 'http',
17
- DATASTORE: 'datastore',
18
- GENERIC: 'generic'
19
- }
20
-
21
- const EXTERNAL_REGEX = /^(?:Truncated\/)?External\//
22
- const DATASTORE_REGEX = /^(?:Truncated\/)?Datastore\//
23
-
12
+ const { addSpanKind, HTTP_LIBRARY, REGEXS, SPAN_KIND, CATEGORIES } = require('./helpers')
24
13
  const EMPTY_USER_ATTRS = Object.freeze(Object.create(null))
25
14
  const SERVER_ADDRESS = 'server.address'
26
15
 
@@ -74,6 +63,14 @@ class SpanEvent {
74
63
  }
75
64
  }
76
65
 
66
+ getIntrinsicAttributes() {
67
+ return this.intrinsics
68
+ }
69
+
70
+ addIntrinsicAttribute(key, value) {
71
+ this.intrinsics[key] = value
72
+ }
73
+
77
74
  static get CATEGORIES() {
78
75
  return CATEGORIES
79
76
  }
@@ -153,6 +150,7 @@ class SpanEvent {
153
150
  span.intrinsics.timestamp = segment.timer.start
154
151
  span.intrinsics.duration = segment.timer.getDurationInMillis() / 1000
155
152
 
153
+ addSpanKind({ segment, span })
156
154
  return span
157
155
  }
158
156
 
@@ -193,7 +191,7 @@ class HttpSpanEvent extends SpanEvent {
193
191
 
194
192
  this.intrinsics.category = CATEGORIES.HTTP
195
193
  this.intrinsics.component = attributes.library || HTTP_LIBRARY
196
- this.intrinsics['span.kind'] = CLIENT_KIND
194
+ this.intrinsics['span.kind'] = SPAN_KIND.CLIENT
197
195
 
198
196
  if (attributes.library) {
199
197
  attributes.library = null
@@ -217,7 +215,7 @@ class HttpSpanEvent extends SpanEvent {
217
215
  }
218
216
 
219
217
  static testSegment(segment) {
220
- return EXTERNAL_REGEX.test(segment.name)
218
+ return REGEXS.CLIENT.EXTERNAL.test(segment.name)
221
219
  }
222
220
  }
223
221
 
@@ -232,7 +230,7 @@ class DatastoreSpanEvent extends SpanEvent {
232
230
  super(attributes, customAttributes)
233
231
 
234
232
  this.intrinsics.category = CATEGORIES.DATASTORE
235
- this.intrinsics['span.kind'] = CLIENT_KIND
233
+ this.intrinsics['span.kind'] = SPAN_KIND.CLIENT
236
234
 
237
235
  if (attributes.product) {
238
236
  this.intrinsics.component = attributes.product
@@ -279,7 +277,7 @@ class DatastoreSpanEvent extends SpanEvent {
279
277
  }
280
278
 
281
279
  static testSegment(segment) {
282
- return DATASTORE_REGEX.test(segment.name)
280
+ return REGEXS.CLIENT.DATASTORE.test(segment.name)
283
281
  }
284
282
  }
285
283
 
@@ -10,17 +10,7 @@ const { truncate } = require('../util/byte-limit')
10
10
  const Config = require('../config')
11
11
 
12
12
  const { DESTINATIONS } = require('../config/attribute-filter')
13
-
14
- const HTTP_LIBRARY = 'http'
15
- const CLIENT_KIND = 'client'
16
- const CATEGORIES = {
17
- HTTP: 'http',
18
- DATASTORE: 'datastore',
19
- GENERIC: 'generic'
20
- }
21
-
22
- const EXTERNAL_REGEX = /^(?:Truncated\/)?External\//
23
- const DATASTORE_REGEX = /^(?:Truncated\/)?Datastore\//
13
+ const { addSpanKind, HTTP_LIBRARY, REGEXS, SPAN_KIND, CATEGORIES } = require('./helpers')
24
14
 
25
15
  /**
26
16
  * Specialized span event class for use with infinite streaming.
@@ -59,7 +49,7 @@ class StreamingSpanEvent {
59
49
  }
60
50
 
61
51
  /**
62
- * Add a key/value pair to the Span's instrinisics collection.
52
+ * Add a key/value pair to the Span's intrinsics collection.
63
53
  *
64
54
  * @param {string} key Name of the attribute to be stored.
65
55
  * @param {string|boolean|number} value Value of the attribute to be stored.
@@ -68,6 +58,10 @@ class StreamingSpanEvent {
68
58
  this._intrinsicAttributes.addAttribute(key, value)
69
59
  }
70
60
 
61
+ getIntrinsicAttributes() {
62
+ return this._intrinsicAttributes
63
+ }
64
+
71
65
  /**
72
66
  * Add a key/value pair to the Span's custom/user attributes collection.
73
67
  *
@@ -169,6 +163,7 @@ class StreamingSpanEvent {
169
163
  // Timestamp in milliseconds, duration in seconds. Yay consistency!
170
164
  span.addIntrinsicAttribute('timestamp', segment.timer.start)
171
165
  span.addIntrinsicAttribute('duration', segment.timer.getDurationInMillis() / 1000)
166
+ addSpanKind({ segment, span })
172
167
 
173
168
  return span
174
169
  }
@@ -196,7 +191,7 @@ class StreamingHttpSpanEvent extends StreamingSpanEvent {
196
191
 
197
192
  this.addIntrinsicAttribute('category', CATEGORIES.HTTP)
198
193
  this.addIntrinsicAttribute('component', library || HTTP_LIBRARY)
199
- this.addIntrinsicAttribute('span.kind', CLIENT_KIND)
194
+ this.addIntrinsicAttribute('span.kind', SPAN_KIND.CLIENT)
200
195
 
201
196
  if (url) {
202
197
  this.addAgentAttribute('http.url', url)
@@ -213,7 +208,7 @@ class StreamingHttpSpanEvent extends StreamingSpanEvent {
213
208
  }
214
209
 
215
210
  static isHttpSegment(segment) {
216
- return EXTERNAL_REGEX.test(segment.name)
211
+ return REGEXS.CLIENT.EXTERNAL.test(segment.name)
217
212
  }
218
213
  }
219
214
 
@@ -248,7 +243,7 @@ class StreamingDatastoreSpanEvent extends StreamingSpanEvent {
248
243
  super(traceId, agentAttrs, customAttributes)
249
244
 
250
245
  this.addIntrinsicAttribute('category', CATEGORIES.DATASTORE)
251
- this.addIntrinsicAttribute('span.kind', CLIENT_KIND)
246
+ this.addIntrinsicAttribute('span.kind', SPAN_KIND.CLIENT)
252
247
 
253
248
  if (product) {
254
249
  this.addIntrinsicAttribute('component', product)
@@ -288,7 +283,7 @@ class StreamingDatastoreSpanEvent extends StreamingSpanEvent {
288
283
  /* eslint-enable camelcase */
289
284
 
290
285
  static isDatastoreSegment(segment) {
291
- return DATASTORE_REGEX.test(segment.name)
286
+ return REGEXS.CLIENT.DATASTORE.test(segment.name)
292
287
  }
293
288
  }
294
289
 
@@ -5,10 +5,11 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- const NAMES = require('./metrics/names')
9
- const logger = require('./logger').child({ component: 'sampler' })
8
+ const os = require('node:os')
9
+ const logger = require('./logger').child({ component: 'system-metrics-sampler' })
10
10
  const Timer = require('./timer')
11
- const os = require('os')
11
+
12
+ const NAMES = require('./metrics/names')
12
13
 
13
14
  /*
14
15
  *
@@ -22,12 +23,12 @@ const SAMPLE_INTERVAL = 15 * MILLIS
22
23
 
23
24
  let samplers = []
24
25
 
25
- function Sampler(sampler, interval) {
26
+ function SystemMetricsSampler(sampler, interval) {
26
27
  this.id = setInterval(sampler, interval)
27
28
  this.id.unref()
28
29
  }
29
30
 
30
- Sampler.prototype.stop = function stop() {
31
+ SystemMetricsSampler.prototype.stop = function stop() {
31
32
  clearInterval(this.id)
32
33
  }
33
34
 
@@ -148,8 +149,8 @@ module.exports = {
148
149
  nativeMetrics: null,
149
150
 
150
151
  start: function start(agent) {
151
- samplers.push(new Sampler(sampleMemory(agent), 5 * MILLIS))
152
- samplers.push(new Sampler(checkEvents(agent), SAMPLE_INTERVAL))
152
+ samplers.push(new SystemMetricsSampler(sampleMemory(agent), 5 * MILLIS))
153
+ samplers.push(new SystemMetricsSampler(checkEvents(agent), SAMPLE_INTERVAL))
153
154
 
154
155
  // This requires a native module which may have failed to build.
155
156
  if (agent.config.plugins.native_metrics.enabled && !this.nativeMetrics) {
@@ -175,17 +176,17 @@ module.exports = {
175
176
 
176
177
  // Add GC events if available.
177
178
  if (this.nativeMetrics.gcEnabled) {
178
- samplers.push(new Sampler(sampleGc(agent, this.nativeMetrics), SAMPLE_INTERVAL))
179
+ samplers.push(new SystemMetricsSampler(sampleGc(agent, this.nativeMetrics), SAMPLE_INTERVAL))
179
180
  }
180
181
 
181
182
  // Add loop metrics if available.
182
183
  if (this.nativeMetrics.loopEnabled) {
183
- samplers.push(new Sampler(sampleLoop(agent, this.nativeMetrics), SAMPLE_INTERVAL))
184
+ samplers.push(new SystemMetricsSampler(sampleLoop(agent, this.nativeMetrics), SAMPLE_INTERVAL))
184
185
  }
185
186
  }
186
187
 
187
188
  // Add CPU sampling using the built-in data (introduced in 6.1.0)
188
- samplers.push(new Sampler(sampleCpu(agent), SAMPLE_INTERVAL))
189
+ samplers.push(new SystemMetricsSampler(sampleCpu(agent), SAMPLE_INTERVAL))
189
190
 
190
191
  this.state = 'running'
191
192
  },
@@ -15,7 +15,6 @@ const props = require('../util/properties')
15
15
  const Timer = require('../timer')
16
16
  const Trace = require('./trace')
17
17
  const synthetics = require('../synthetics')
18
- const url = require('url')
19
18
  const urltils = require('../util/urltils')
20
19
  const TraceContext = require('./tracecontext').TraceContext
21
20
  const Logs = require('./logs')
@@ -609,49 +608,6 @@ Transaction.prototype.getFullName = function getFullName() {
609
608
  return fullName
610
609
  }
611
610
 
612
- /**
613
- * Returns the full URL of the transaction with query, search, or hash portions
614
- * removed. This is only applicable for web transactions.
615
- *
616
- * Caches to ._scrubbedUrl, pulls in from .parsedUrl if it is available,
617
- * otherwise it will parse .url, store it on .parsedUrl, then scrub the URL and
618
- * store it in the cache.
619
- *
620
- * @returns {(string|undefined)} Returns a string or undefined.
621
- */
622
- Transaction.prototype.getScrubbedUrl = function getScrubbedUrl() {
623
- if (!this.isWeb()) {
624
- return
625
- }
626
- if (this._scrubbedUrl) {
627
- return this._scrubbedUrl
628
- }
629
-
630
- // If we don't have a parsedUrl, lets populate it from .url
631
- if (!this.parsedUrl) {
632
- // At time of writing .url should always be set by the time we get here
633
- // because that is what .isWeb() checks against. In the future it may be
634
- // instead checking a enum or other property so guard ourselves just in
635
- // case.
636
- if (!this.url) {
637
- return
638
- }
639
-
640
- this.parsedUrl = url.parse(this.url)
641
- }
642
-
643
- const scrubbedParsedUrl = Object.assign(Object.create(null), this.parsedUrl)
644
- scrubbedParsedUrl.search = null
645
- scrubbedParsedUrl.query = null
646
- scrubbedParsedUrl.href = null
647
- scrubbedParsedUrl.path = null
648
- scrubbedParsedUrl.hash = null
649
-
650
- this._scrubbedUrl = url.format(scrubbedParsedUrl)
651
-
652
- return this._scrubbedUrl
653
- }
654
-
655
611
  /**
656
612
  * The instrumentation associates metrics with the different kinds of trace
657
613
  * segments. The metrics recorders are dependent on the transaction name to
@@ -779,11 +735,12 @@ Transaction.prototype.alternatePathHashes = function alternatePathHashes() {
779
735
  * an attribute onto the exception.
780
736
  *
781
737
  * @param {Exception} exception The exception object to be collected.
738
+ * @param {Segment} segment The segment to which the exception is linked.
782
739
  */
783
740
  Transaction.prototype._linkExceptionToSegment = _linkExceptionToSegment
784
741
 
785
- function _linkExceptionToSegment(exception) {
786
- const segment = this.agent.tracer.getSegment()
742
+ function _linkExceptionToSegment(exception, segment) {
743
+ segment = segment || this.agent.tracer.getSegment()
787
744
  if (!segment) {
788
745
  return
789
746
  }
@@ -806,16 +763,17 @@ function _linkExceptionToSegment(exception) {
806
763
  * the exception will be collected along with the transaction details.
807
764
  *
808
765
  * @param {Exception} exception The exception object to be collected.
766
+ * @param {Segment} segment The segment to which the exception is linked.
809
767
  */
810
768
  Transaction.prototype.addException = _addException
811
769
 
812
- function _addException(exception) {
770
+ function _addException(exception, segment) {
813
771
  if (!this.isActive()) {
814
772
  logger.trace('Transaction is not active. Not capturing error: ', exception)
815
773
  return
816
774
  }
817
775
 
818
- this._linkExceptionToSegment(exception)
776
+ this._linkExceptionToSegment(exception, segment)
819
777
  this.exceptions.push(exception)
820
778
  }
821
779
 
@@ -64,6 +64,9 @@ function TraceSegment({ id, config, name, collect, parentId, root, isRoot = fals
64
64
  this.state = STATE.EXTERNAL
65
65
  this.async = true
66
66
  this.ignore = false
67
+ // only use to specify spanKind on segments created with `api.startBackgroundTransaction` and `api.startWebTransaction`
68
+ // we typically determine the span kind based on the segment name
69
+ this.spanKind = null
67
70
  }
68
71
 
69
72
  TraceSegment.prototype.getSpanContext = function getSpanContext() {
@@ -222,10 +222,11 @@ function transactionNestProxy(type, handler) {
222
222
 
223
223
  // don't nest transactions, reuse existing ones
224
224
  let context = tracer.getContext()
225
+ const transaction = tracer.getTransaction()
225
226
 
226
227
  let createNew = false
227
228
 
228
- if (!context?.transaction || context?.transaction.type !== type) {
229
+ if (!transaction || transaction.type !== type) {
229
230
  createNew = true
230
231
  }
231
232
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "12.15.0",
3
+ "version": "12.16.1",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "license": "Apache-2.0",
6
6
  "contributors": [