newrelic 12.13.0 → 12.14.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,22 @@
1
+ ### v12.14.0 (2025-02-18)
2
+
3
+ #### Features
4
+
5
+ * Added support for region-prefixed Bedrock models ([#2947](https://github.com/newrelic/node-newrelic/pull/2947)) ([6acf535](https://github.com/newrelic/node-newrelic/commit/6acf5354d1e7f6786da88c0078699e82a714551d))
6
+ * Added attribute reconciliation for message producer spans ([#2942](https://github.com/newrelic/node-newrelic/pull/2942)) ([a9ba396](https://github.com/newrelic/node-newrelic/commit/a9ba39613c78b89c047ca35a218c2eedbb01e3d2))
7
+ * Added timeslice metrics for synthesized consumer segments ([#2938](https://github.com/newrelic/node-newrelic/pull/2938)) ([acfe953](https://github.com/newrelic/node-newrelic/commit/acfe953cf7a656fafb69104384c72f94ddc13000))
8
+
9
+ #### Bug fixes
10
+
11
+ * Fixed queueing of logs from child loggers ([#2945](https://github.com/newrelic/node-newrelic/pull/2945)) ([888cfe8](https://github.com/newrelic/node-newrelic/commit/888cfe84a1ffaf0287682cc9099a2fa762f7a37f))
12
+ * Fixed undici/fetch instrumentation to properly assign the parent-id portion of the `traceparent` header on outgoing requests to the active http external span id ([#2951](https://github.com/newrelic/node-newrelic/pull/2951)) ([bc714cf](https://github.com/newrelic/node-newrelic/commit/bc714cfa8fe6754abf25eb556b8f804c9982f986))
13
+ * Updated `api.recordLogEvent` to no longer truncate the message key in log event ([#2949](https://github.com/newrelic/node-newrelic/pull/2949)) ([8dd557b](https://github.com/newrelic/node-newrelic/commit/8dd557b4dfca2505b7e9f7f030ab094a6ae8019a))
14
+ * Thanks for your contribution @rChaoz 🎉
15
+
16
+ #### Documentation
17
+
18
+ * Updated compatibility report ([#2948](https://github.com/newrelic/node-newrelic/pull/2948)) ([8e32ed7](https://github.com/newrelic/node-newrelic/commit/8e32ed7d7bc456a0693485e7bdef955c25ead29a))
19
+
1
20
  ### v12.13.0 (2025-02-12)
2
21
 
3
22
  #### Features
package/api.js CHANGED
@@ -513,7 +513,6 @@ API.prototype.recordLogEvent = function recordLogEvent(logEvent = {}) {
513
513
  )
514
514
  return
515
515
  }
516
- logEvent.message = applicationLogging.truncate(logEvent.message)
517
516
 
518
517
  if (!logEvent.level) {
519
518
  logger.debug('no log level set, setting it to UNKNOWN')
@@ -136,8 +136,8 @@ function requestCreateHook(shim, { request }) {
136
136
  }
137
137
 
138
138
  try {
139
- addDTHeaders({ transaction, config, request })
140
139
  createExternalSegment({ shim, request, segment })
140
+ addDTHeaders({ transaction, config, request })
141
141
  } catch (err) {
142
142
  logger.warn(err, 'Unable to create external segment')
143
143
  }
@@ -124,11 +124,11 @@ class BedrockCommand {
124
124
  }
125
125
 
126
126
  isClaude() {
127
- return this.#modelId.startsWith('anthropic.claude-v')
127
+ return this.#modelId.split('.').slice(-2).join('.').startsWith('anthropic.claude-v')
128
128
  }
129
129
 
130
130
  isClaude3() {
131
- return this.#modelId.startsWith('anthropic.claude-3')
131
+ return this.#modelId.split('.').slice(-2).join('.').startsWith('anthropic.claude-3')
132
132
  }
133
133
 
134
134
  isCohere() {
@@ -7,6 +7,11 @@
7
7
 
8
8
  const NAMES = require('../../metrics/names.js')
9
9
 
10
+ /**
11
+ * @param {TraceSegment} segment
12
+ * @param {object} scope
13
+ * @param {Transaction} tx
14
+ */
10
15
  function recordMessageTransaction(segment, scope, tx) {
11
16
  if (tx.type !== 'message' || tx.baseSegment !== segment) {
12
17
  return
@@ -120,6 +120,13 @@ module.exports = {
120
120
  */
121
121
  ATTR_HTTP_STATUS_TEXT: 'http.status_text',
122
122
 
123
+ /**
124
+ * The correlation id
125
+ *
126
+ * @example MyConversationId
127
+ */
128
+ ATTR_MESSAGING_MESSAGE_CONVERSATION_ID: 'messaging.message.conversation_id',
129
+
123
130
  /**
124
131
  * The message destination name.
125
132
  *
@@ -141,6 +148,29 @@ module.exports = {
141
148
  */
142
149
  ATTR_MESSAGING_DESTINATION_NAME: 'messaging.destination.name',
143
150
 
151
+ /**
152
+ * Identifies the type of messaging consumer operation.
153
+ *
154
+ * {@link ATTR_MESSAGING_OPERATION_NAME} is newer and should be used.
155
+ */
156
+ ATTR_MESSAGING_OPERATION: 'messaging.operation',
157
+
158
+ /**
159
+ * Name of the operation being performed. Value is specific to the
160
+ * target messaging system.
161
+ *
162
+ * @example ack
163
+ * @example send
164
+ */
165
+ ATTR_MESSAGING_OPERATION_NAME: 'messaging.operation.name',
166
+
167
+ /**
168
+ * RabbitMQ message routing key
169
+ *
170
+ * @example myKey
171
+ */
172
+ ATTR_MESSAGING_RABBITMQ_DESTINATION_ROUTING_KEY: 'messaging.rabbitmq.destination.routing_key',
173
+
144
174
  /**
145
175
  * Target messaging system name.
146
176
  *
@@ -13,6 +13,7 @@ module.exports = createConsumerSegment
13
13
  // attributes according to our own internal specs.
14
14
 
15
15
  const Transaction = require('../../transaction/')
16
+ const recorder = require('../../metrics/recorders/message-transaction')
16
17
  const { DESTINATIONS, TYPES } = Transaction
17
18
 
18
19
  const {
@@ -23,12 +24,12 @@ const {
23
24
 
24
25
  function createConsumerSegment(agent, otelSpan) {
25
26
  const transaction = new Transaction(agent)
26
- transaction.type = TYPES.BG
27
+ transaction.type = TYPES.MESSAGE
27
28
 
28
29
  const system = otelSpan.attributes[ATTR_MESSAGING_SYSTEM] ?? 'unknown'
29
30
  const destination = otelSpan.attributes[ATTR_MESSAGING_DESTINATION] ?? 'unknown'
30
31
  const destKind = otelSpan.attributes[ATTR_MESSAGING_DESTINATION_KIND] ?? 'unknown'
31
- const segmentName = `OtherTransaction/Message/${system}/${destKind}/Named/${destination}`
32
+ const segmentName = `${system}/${destKind}/Named/${destination}`
32
33
 
33
34
  const txAttrs = transaction.trace.attributes
34
35
  txAttrs.addAttribute(DESTINATIONS.TRANS_SCOPE, 'message.queueName', destination)
@@ -37,10 +38,11 @@ function createConsumerSegment(agent, otelSpan) {
37
38
  // 'host',
38
39
  //
39
40
  // )
40
- transaction.name = segmentName
41
+ transaction.setPartialName(segmentName)
41
42
 
42
43
  const segment = agent.tracer.createSegment({
43
- name: segmentName,
44
+ recorder,
45
+ name: transaction.getFullName(),
44
46
  parent: transaction.trace.root,
45
47
  transaction
46
48
  })
@@ -17,6 +17,8 @@ const {
17
17
  ATTR_HTTP_ROUTE,
18
18
  ATTR_HTTP_STATUS_CODE,
19
19
  ATTR_HTTP_STATUS_TEXT,
20
+ ATTR_MESSAGING_MESSAGE_CONVERSATION_ID,
21
+ ATTR_MESSAGING_RABBITMQ_DESTINATION_ROUTING_KEY,
20
22
  ATTR_NET_PEER_NAME,
21
23
  ATTR_NET_PEER_PORT,
22
24
  ATTR_NET_HOST_NAME,
@@ -69,6 +71,8 @@ module.exports = class NrSpanProcessor {
69
71
  this.reconcileServerAttributes({ segment, span, transaction })
70
72
  } else if (span.kind === SpanKind.CLIENT && span.attributes[ATTR_DB_SYSTEM]) {
71
73
  this.reconcileDbAttributes({ segment, span })
74
+ } else if (span.kind === SpanKind.PRODUCER) {
75
+ this.reconcileProducerAttributes({ segment, span })
72
76
  }
73
77
  // TODO: add http external checks
74
78
  }
@@ -159,4 +163,26 @@ module.exports = class NrSpanProcessor {
159
163
  segment.addAttribute(key, sanitized)
160
164
  }
161
165
  }
166
+
167
+ reconcileProducerAttributes({ segment, span }) {
168
+ for (const [prop, value] of Object.entries(span.attributes)) {
169
+ let key = prop
170
+ let sanitized = value
171
+
172
+ if (prop === ATTR_SERVER_ADDRESS) {
173
+ key = 'host'
174
+ if (urltils.isLocalhost(sanitized)) {
175
+ sanitized = this.agent.config.getHostnameSafe(sanitized)
176
+ }
177
+ } else if (prop === ATTR_SERVER_PORT) {
178
+ key = 'port'
179
+ } else if (prop === ATTR_MESSAGING_MESSAGE_CONVERSATION_ID) {
180
+ key = 'correlation_id'
181
+ } else if (prop === ATTR_MESSAGING_RABBITMQ_DESTINATION_ROUTING_KEY) {
182
+ key = 'routing_key'
183
+ }
184
+
185
+ segment.addAttribute(key, sanitized)
186
+ }
187
+ }
162
188
  }
@@ -94,7 +94,8 @@ Object.keys(LEVELS).forEach(function buildLevel(_level) {
94
94
 
95
95
  function log(extra) {
96
96
  if (!this.options.configured) {
97
- this.logQueue.unshift({ level: _level, args: arguments })
97
+ // queue a log line with level, args passed to logger and their respective extras
98
+ this.logQueue.unshift({ level: _level, args: arguments, extra: this.extra })
98
99
  return
99
100
  }
100
101
 
@@ -175,8 +176,9 @@ Object.keys(LEVELS).forEach(function buildLevel(_level) {
175
176
 
176
177
  Logger.prototype._flushQueuedLogs = function _flushQueuedLogs() {
177
178
  while (this.logQueue.length) {
178
- const { level, args } = this.logQueue.shift()
179
- this[level].apply(this, args)
179
+ const { level, args, extra } = this.logQueue.shift()
180
+ // log an entry now that the logger has been configured
181
+ this[level](extra, ...args)
180
182
  }
181
183
  }
182
184
 
@@ -188,6 +190,7 @@ Logger.prototype.child = function child(extra) {
188
190
  childLogger.extra = Object.assign(Object.create(null), this.extra, extra)
189
191
 
190
192
  const parent = this
193
+ childLogger.logQueue = parent.logQueue
191
194
  childLogger.options = parent.options
192
195
 
193
196
  childLogger.write = function write(level, args, _extra) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "12.13.0",
3
+ "version": "12.14.0",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "license": "Apache-2.0",
6
6
  "contributors": [