newrelic 12.22.0 → 12.24.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.
package/NEWS.md CHANGED
@@ -1,3 +1,38 @@
1
+ ### v12.24.0 (2025-07-07)
2
+
3
+ #### Features
4
+
5
+ * Implemented configurable attribute value size limit ([#3206](https://github.com/newrelic/node-newrelic/pull/3206)) ([08a6eca](https://github.com/newrelic/node-newrelic/commit/08a6eca7cd35811f5bcb4e6c13264eac156b302f))
6
+
7
+ #### Documentation
8
+
9
+ * Updated compatibility report ([#3190](https://github.com/newrelic/node-newrelic/pull/3190)) ([72c492c](https://github.com/newrelic/node-newrelic/commit/72c492cf78b6cc52791604ac354e34718c8ae926))
10
+
11
+ #### Miscellaneous chores
12
+
13
+ * Rename GCP ID config flag ([#3205](https://github.com/newrelic/node-newrelic/pull/3205)) ([8446f29](https://github.com/newrelic/node-newrelic/commit/8446f29fc6ed469d663e5634540a4afe4932765b))
14
+
15
+ ### v12.23.0 (2025-06-30)
16
+
17
+ #### Features
18
+
19
+ * Added ability to report only on entry and exit spans ([#3184](https://github.com/newrelic/node-newrelic/pull/3184)) ([1f909d3](https://github.com/newrelic/node-newrelic/commit/1f909d389f790733c8787a9db8b0ee71c26bb5ed))
20
+ * Added Node.js 24 support ([#3080](https://github.com/newrelic/node-newrelic/pull/3080)) ([a538c2a](https://github.com/newrelic/node-newrelic/commit/a538c2a5e23b96be40fa3c014e60b912f695423e))
21
+
22
+ #### Documentation
23
+
24
+ * Updated compatibility report ([#3186](https://github.com/newrelic/node-newrelic/pull/3186)) ([5498c15](https://github.com/newrelic/node-newrelic/commit/5498c15bb6cf3329fe50595da46ab4f2b1385170))
25
+
26
+ #### Miscellaneous chores
27
+
28
+ * Fix issues on Node 24 ([#3181](https://github.com/newrelic/node-newrelic/pull/3181)) ([24832df](https://github.com/newrelic/node-newrelic/commit/24832df2984df960a4e44375525a8127a00a637c))
29
+ * Removed force labels on main CI workflow ([#3183](https://github.com/newrelic/node-newrelic/pull/3183)) ([3aaee17](https://github.com/newrelic/node-newrelic/commit/3aaee17586f817ae1a2ab0573570a740bd6397ef))
30
+ * Updated undici tests to unblock CI ([#3185](https://github.com/newrelic/node-newrelic/pull/3185)) ([08261eb](https://github.com/newrelic/node-newrelic/commit/08261ebcdbafdb625a451f4ccec9b0cba7641cc2))
31
+
32
+ #### Continuous integration
33
+
34
+ * Fixed the name of todo colum in add to board reusable workflow ([#3178](https://github.com/newrelic/node-newrelic/pull/3178)) ([f216e0c](https://github.com/newrelic/node-newrelic/commit/f216e0c48adf0291bfa38b3edbb04c9dcc96f92c))
35
+
1
36
  ### v12.22.0 (2025-06-24)
2
37
 
3
38
  #### Features
package/index.js CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- const HealthReporter = require('./lib/health-reporter')
8
+ const HealthReporter = require('./lib/health-reporter.js')
9
9
 
10
10
  // Record opening times before loading any other files.
11
11
  const preAgentTime = process.uptime()
@@ -64,8 +64,8 @@ function initialize() {
64
64
  throw new Error(message)
65
65
  }
66
66
 
67
- // TODO: Update this check when Node v24 support is added
68
- if (psemver.satisfies('>=23.0.0')) {
67
+ // TODO: Update this check when Node v26 support is added
68
+ if (psemver.satisfies('>=25.0.0')) {
69
69
  logger.warn(
70
70
  'New Relic for Node.js %s has not been tested on Node.js %s. Please ' +
71
71
  'update the agent or downgrade your version of Node.js',
package/lib/attributes.js CHANGED
@@ -12,6 +12,7 @@ const byteUtils = require('./util/byte-limit')
12
12
  const properties = require('./util/properties')
13
13
 
14
14
  const MAXIMUM_CUSTOM_ATTRIBUTES = 64
15
+ const MAXIMUM_ATTR_VALUE_LENGTH = 4_096
15
16
 
16
17
  /**
17
18
  * @class
@@ -19,17 +20,25 @@ const MAXIMUM_CUSTOM_ATTRIBUTES = 64
19
20
  */
20
21
  class Attributes {
21
22
  /**
22
- * @param {string} scope
23
+ * @param {object} params Constructor parameters.
24
+ * @param {string} params.scope
23
25
  * The scope of the attributes this will collect. Must be `transaction` or
24
26
  * `segment`.
25
- * @param {number} [limit]
27
+ * @param {number} [params.limit]
26
28
  * The maximum number of attributes to retrieve for each destination.
29
+ * @param {number} [params.valueLengthLimit] The maximum length allowed for
30
+ * attribute values.
27
31
  */
28
- constructor(scope, limit = Infinity) {
32
+ constructor({ scope, limit = Infinity, valueLengthLimit = 256 } = {}) {
29
33
  this.filter = makeFilter(scope)
30
34
  this.limit = limit
31
35
  this.attributes = Object.create(null)
32
36
  this.attributeCount = 0
37
+ this.attributeValueLimit = valueLengthLimit
38
+
39
+ if (this.attributeValueLimit > MAXIMUM_ATTR_VALUE_LENGTH) {
40
+ this.attributeValueLimit = MAXIMUM_ATTR_VALUE_LENGTH
41
+ }
33
42
  }
34
43
 
35
44
  /**
@@ -73,7 +82,7 @@ class Attributes {
73
82
 
74
83
  attrs[key] =
75
84
  typeof attr.value === 'string' && !attr.truncateExempt
76
- ? byteUtils.truncate(attr.value, 255)
85
+ ? byteUtils.truncate(attr.value, this.attributeValueLimit)
77
86
  : attr.value
78
87
  }
79
88
 
@@ -233,6 +233,18 @@ defaultConfig.definition = () => ({
233
233
  default: true
234
234
  },
235
235
 
236
+ /**
237
+ * Defines the number of characters allowed for each individual attribute's
238
+ * value. The default is 256 characters, with a maximum of 4,096.
239
+ */
240
+ value_size_limit: {
241
+ formatter: int,
242
+ /**
243
+ * Maximum value is 4,096.
244
+ */
245
+ default: 256
246
+ },
247
+
236
248
  /**
237
249
  * Prefix of attributes to exclude from all destinations. Allows * as wildcard at end.
238
250
  *
@@ -538,6 +550,15 @@ defaultConfig.definition = () => ({
538
550
  total_ram_mib: {
539
551
  formatter: int,
540
552
  default: null
553
+ },
554
+
555
+ /**
556
+ * When enabled, it will use GCP metadata id
557
+ * to set the hostname of the running application.
558
+ */
559
+ gcp_use_instance_as_host: {
560
+ default: true,
561
+ formatter: boolean
541
562
  }
542
563
  },
543
564
  transaction_tracer: {
@@ -1120,6 +1141,19 @@ defaultConfig.definition = () => ({
1120
1141
  default: false
1121
1142
  },
1122
1143
 
1144
+ /**
1145
+ * Controls whether the agent will generate spans for in-process actions.
1146
+ * When disabled, this will only create spans for entry and exit spans.
1147
+ * entry spans - initial actions for web servers and messsage queue consumption.
1148
+ * exit spans - all outgoing calls to external services(external and database calls)
1149
+ */
1150
+ in_process_spans: {
1151
+ enabled: {
1152
+ formatter: boolean,
1153
+ default: true
1154
+ }
1155
+ },
1156
+
1123
1157
  sampler: {
1124
1158
  /**
1125
1159
  * When set to `always_on`, the sampled flag in the `traceparent` header
@@ -1605,17 +1639,6 @@ defaultConfig.definition = () => ({
1605
1639
  }
1606
1640
  },
1607
1641
 
1608
- /**
1609
- * When enabled, it will use GCP metadata id
1610
- * to set the hostname of the running application
1611
- */
1612
- gcp_cloud_run: {
1613
- use_instance_as_host: {
1614
- default: true,
1615
- formatter: boolean
1616
- }
1617
- },
1618
-
1619
1642
  /**
1620
1643
  * When enabled, it will allow loading of the agent
1621
1644
  * in worker threads.
@@ -989,7 +989,7 @@ function getHostnameSafe(gcpId = null) {
989
989
  // If not applicable, use the os.hostname()
990
990
  if (config.heroku.use_dyno_names && process.env.DYNO) {
991
991
  _hostname = process.env.DYNO || os.hostname()
992
- } else if (config.gcp_cloud_run.use_instance_as_host && process.env.K_SERVICE && gcpId) {
992
+ } else if (config.utilization.gcp_use_instance_as_host && process.env.K_SERVICE && gcpId) {
993
993
  _hostname = gcpId
994
994
  } else {
995
995
  _hostname = os.hostname()
@@ -60,10 +60,72 @@ 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
+
63
122
  module.exports = {
64
123
  HTTP_LIBRARY,
65
124
  CATEGORIES,
66
125
  SPAN_KIND,
67
126
  REGEXS,
68
- addSpanKind
127
+ addSpanKind,
128
+ isEntryPointSpan,
129
+ reparentSpan,
130
+ shouldCreateSpan
69
131
  }
@@ -21,6 +21,7 @@ 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
24
25
  }
25
26
 
26
27
  _toPayloadSync() {
@@ -66,7 +67,6 @@ class SpanEventAggregator extends EventAggregator {
66
67
  * @param {Transaction} params.transaction active transaction
67
68
  * @param {string} [params.parentId] GUID of the parent span.
68
69
  * @param {boolean} params.isRoot if segment is root segment
69
- * @returns {boolean} True if the segment was added, or false if it was discarded.
70
70
  */
71
71
  addSegment({ segment, transaction, parentId, isRoot }) {
72
72
  // Check if the priority would be accepted before creating the event object.
@@ -77,8 +77,11 @@ class SpanEventAggregator extends EventAggregator {
77
77
 
78
78
  return false
79
79
  }
80
- const span = SpanEvent.fromSegment(segment, transaction, parentId || null, isRoot)
81
- return this.add(span, transaction.priority)
80
+ const span = SpanEvent.fromSegment({ segment, transaction, parentId, isRoot, inProcessSpans: this.inProcessSpans })
81
+
82
+ if (span) {
83
+ this.add(span, transaction.priority)
84
+ }
82
85
  }
83
86
 
84
87
  /**
@@ -9,7 +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
- const { addSpanKind, HTTP_LIBRARY, REGEXS, SPAN_KIND, CATEGORIES } = require('./helpers')
12
+ const { addSpanKind, isEntryPointSpan, reparentSpan, shouldCreateSpan, HTTP_LIBRARY, REGEXS, SPAN_KIND, CATEGORIES } = require('./helpers')
13
13
  const EMPTY_USER_ATTRS = Object.freeze(Object.create(null))
14
14
  const SERVER_ADDRESS = 'server.address'
15
15
 
@@ -93,9 +93,15 @@ class SpanEvent {
93
93
  * @param {Transaction} transaction active transaction
94
94
  * @param {?string} [parentId] ID of the segment's parent.
95
95
  * @param {boolean} isRoot if segment is root segment
96
+ * @param {boolean} inProcessSpans if the segment is in-process, create span
96
97
  * @returns {SpanEvent} The constructed event.
97
98
  */
98
- static fromSegment(segment, transaction, parentId = null, isRoot = false) {
99
+ static fromSegment({ segment, transaction, parentId = null, isRoot = false, inProcessSpans }) {
100
+ const entryPoint = isEntryPointSpan({ segment, transaction })
101
+ if (!inProcessSpans && !shouldCreateSpan({ entryPoint, segment, transaction })) {
102
+ return null
103
+ }
104
+
99
105
  const spanContext = segment.getSpanContext()
100
106
 
101
107
  // Since segments already hold span agent attributes and we want to leverage
@@ -128,7 +134,7 @@ class SpanEvent {
128
134
 
129
135
  span.intrinsics.traceId = transaction.traceId
130
136
  span.intrinsics.guid = segment.id
131
- span.intrinsics.parentId = parentId
137
+ span.intrinsics.parentId = reparentSpan({ inProcessSpans, isRoot, segment, transaction, parentId })
132
138
  span.intrinsics.transactionId = transaction.id
133
139
  span.intrinsics.sampled = transaction.sampled
134
140
  span.intrinsics.priority = transaction.priority
@@ -142,7 +148,7 @@ class SpanEvent {
142
148
  }
143
149
 
144
150
  // Only set this if it will be `true`. Must be `null` otherwise.
145
- if (transaction.baseSegment === segment) {
151
+ if (entryPoint) {
146
152
  span.intrinsics['nr.entryPoint'] = true
147
153
  }
148
154
 
@@ -44,6 +44,7 @@ class StreamingSpanEventAggregator extends Aggregator {
44
44
  this.metrics = metrics
45
45
  this.started = false
46
46
  this.isStream = true
47
+ this.inProcessSpans = agent.config.distributed_tracing.in_process_spans.enabled
47
48
  }
48
49
 
49
50
  /**
@@ -112,8 +113,11 @@ class StreamingSpanEventAggregator extends Aggregator {
112
113
  return
113
114
  }
114
115
 
115
- const span = StreamingSpanEvent.fromSegment(segment, transaction, parentId, isRoot)
116
- this.stream.write(span)
116
+ const span = StreamingSpanEvent.fromSegment({ segment, transaction, parentId, isRoot, inProcessSpans: this.inProcessSpans })
117
+
118
+ if (span) {
119
+ this.stream.write(span)
120
+ }
117
121
  }
118
122
 
119
123
  reconfigure(config) {
@@ -10,7 +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
- const { addSpanKind, HTTP_LIBRARY, REGEXS, SPAN_KIND, CATEGORIES } = require('./helpers')
13
+ const { addSpanKind, isEntryPointSpan, reparentSpan, shouldCreateSpan, HTTP_LIBRARY, REGEXS, SPAN_KIND, CATEGORIES } = require('./helpers')
14
14
 
15
15
  /**
16
16
  * Specialized span event class for use with infinite streaming.
@@ -108,7 +108,12 @@ class StreamingSpanEvent {
108
108
  }
109
109
  }
110
110
 
111
- static fromSegment(segment, transaction, parentId = null, isRoot = false) {
111
+ static fromSegment({ segment, transaction, parentId = null, isRoot = false, inProcessSpans }) {
112
+ const entryPoint = isEntryPointSpan({ segment, transaction })
113
+ if (!inProcessSpans && !shouldCreateSpan({ entryPoint, segment, transaction })) {
114
+ return null
115
+ }
116
+
112
117
  const spanContext = segment.getSpanContext()
113
118
 
114
119
  // Since segments already hold span agent attributes and we want to leverage
@@ -141,8 +146,9 @@ class StreamingSpanEvent {
141
146
  span.addIntrinsicAttribute(key, value)
142
147
  }
143
148
 
149
+ const newParentId = reparentSpan({ inProcessSpans, isRoot, segment, transaction, parentId })
144
150
  span.addIntrinsicAttribute('guid', segment.id)
145
- span.addIntrinsicAttribute('parentId', parentId)
151
+ span.addIntrinsicAttribute('parentId', newParentId)
146
152
  span.addIntrinsicAttribute('transactionId', transaction.id)
147
153
  span.addIntrinsicAttribute('sampled', transaction.sampled)
148
154
  span.addIntrinsicAttribute('priority', transaction.priority)
@@ -156,7 +162,7 @@ class StreamingSpanEvent {
156
162
  }
157
163
 
158
164
  // Only set this if it will be `true`. Must be `null` otherwise.
159
- if (transaction.baseSegment === segment) {
165
+ if (entryPoint) {
160
166
  span.addIntrinsicAttribute('nr.entryPoint', true)
161
167
  }
162
168
 
@@ -44,8 +44,15 @@ function Trace(transaction) {
44
44
  this.root = this.segments.root.segment
45
45
  this.totalTimeCache = null
46
46
 
47
- this.custom = new Attributes(ATTRIBUTE_SCOPE, MAXIMUM_CUSTOM_ATTRIBUTES)
48
- this.attributes = new Attributes(ATTRIBUTE_SCOPE)
47
+ this.custom = new Attributes({
48
+ scope: ATTRIBUTE_SCOPE,
49
+ limit: MAXIMUM_CUSTOM_ATTRIBUTES,
50
+ valueLengthLimit: transaction.agent.config.attributes.value_size_limit
51
+ })
52
+ this.attributes = new Attributes({
53
+ scope: ATTRIBUTE_SCOPE,
54
+ valueLengthLimit: transaction.agent.config.attributes.value_size_limit
55
+ })
49
56
 
50
57
  // sending displayName if set by user
51
58
  const displayName = transaction.agent.config.getDisplayHost()
@@ -43,7 +43,10 @@ function TraceSegment({ id, config, name, collect, parentId, root, isRoot = fals
43
43
  this.isRoot = isRoot
44
44
  this.root = root
45
45
  this.name = name
46
- this.attributes = new Attributes(ATTRIBUTE_SCOPE)
46
+ this.attributes = new Attributes({
47
+ scope: ATTRIBUTE_SCOPE,
48
+ valueLengthLimit: config?.attributes.value_size_limit
49
+ })
47
50
  this.spansEnabled = config?.distributed_tracing?.enabled && config?.span_events?.enabled
48
51
 
49
52
  // Generate a unique id for use in span events.
@@ -52,7 +52,7 @@ clmUtils.addCLMAttributes = function addCLMAttributes(fn, segment) {
52
52
  }
53
53
 
54
54
  try {
55
- const { funcInfo } = require('@contrast/fn-inspect')
55
+ const { funcInfo } = require('@newrelic/fn-inspect')
56
56
  const { lineNumber, method, file: filePath, column } = funcInfo(fn)
57
57
  const fnName = setFunctionName(method)
58
58
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "12.22.0",
3
+ "version": "12.24.0",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "license": "Apache-2.0",
6
6
  "contributors": [
@@ -223,8 +223,8 @@
223
223
  "winston-transport": "^4.5.0"
224
224
  },
225
225
  "optionalDependencies": {
226
- "@contrast/fn-inspect": "^4.2.0",
227
- "@newrelic/native-metrics": "^11.0.0",
226
+ "@newrelic/fn-inspect": "^4.4.0",
227
+ "@newrelic/native-metrics": "^11.1.0",
228
228
  "@prisma/prisma-fmt-wasm": "^4.17.0-16.27eb2449f178cd9fe1a4b892d732cc4795f75085"
229
229
  },
230
230
  "devDependencies": {